The Error

java.lang.NullPointerException:
  Cannot invoke "org.openqa.selenium.WebElement.sendKeys(java.lang.CharSequence[])"
  because "this.username" is null

Your locator is right. Your element exists. And it's null.


What It Means

If you try to access or interact with a web element without initializing it through Page Factory (or another method), you get a NullPointerException — the exception thrown when performing operations on an object that hasn't been instantiated or assigned a value.

The subtlety: @FindBy is just an annotation. It's metadata. It doesn't do anything on its own.

Advertisement
@FindBy(id = "username")
private WebElement username;      // ← this is null. Right now. Always.

Something has to read that annotation and populate the field. That something is PageFactory.initElements(). Without it, your field stays exactly as Java left it: null.


The Fix: initElements ⭐

public class LoginPage {
    WebDriver driver;

    @FindBy(id = "username") private WebElement username;
    @FindBy(id = "password") private WebElement password;
    @FindBy(id = "login")    private WebElement loginBtn;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);   // ⭐ THE LINE
    }

    public void login(String user, String pass) {
        username.sendKeys(user);
        password.sendKeys(pass);
        loginBtn.click();
    }
}

That one line is what makes @FindBy mean anything. Proper initialization of Page Object elements before use is essential.


The Causes

Missing initElements()

The above. ~60% of cases.

Driver is null

// ❌ Field never assigned
public class BaseTest {
    WebDriver driver;                 // null

    @BeforeMethod
    public void setUp() {
        WebDriver driver = new ChromeDriver();   // ⚠️ NEW local variable!
    }
}

// ✅ Assign the field
@BeforeMethod
public void setUp() {
    driver = new ChromeDriver();
}

The shadowed-variable bug: you declared a local driver instead of assigning the field. Compiles fine. NPEs at runtime.

Page Object created before the driver

// ❌ driver is still null here
LoginPage login = new LoginPage(driver);
driver = new ChromeDriver();

// ✅
driver = new ChromeDriver();
LoginPage login = new LoginPage(driver);

ThreadLocal driver not set in parallel

private static ThreadLocal<WebDriver> tlDriver = new ThreadLocal<>();
public static WebDriver getDriver() {
    return tlDriver.get();   // null on a thread that never called set()
}

ThreadLocal.get() returns null on any thread where set() wasn't called — so a @BeforeSuite that sets the driver won't help @Test methods on other threads. Set it in @BeforeMethod.

Manual findElement result never assigned

WebElement btn;                    // null

if (someCondition) {
    btn = driver.findElement(By.id("x"));
}
btn.click();                       // 💥 if the condition was false

PageFactory vs Plain Locators — the Honest Take

// PageFactory style
@FindBy(id = "username") private WebElement username;
// requires initElements(), lazily re-locates on each call

// Plain By style — ⭐ what most modern frameworks use
private By username = By.id("username");

public void enterUser(String u) {
    driver.findElement(username).sendKeys(u);
}

// no init needed, no NPE, no staleness

The By approach can't throw this NPE at all — there's nothing to initialize. It also sidesteps StaleElementReferenceException, because you resolve the element fresh every call.

If you're hitting NPEs and stale-element errors in the same framework, that's a strong signal to move from @FindBy to By.


Quick Diagnosis

The NPE names… Cause Fix
A @FindBy field No initElements() Add it to the constructor
driver Driver never assigned Check for a shadowed local variable
A field in a parallel run ThreadLocal not set() on this thread Set in @BeforeMethod
A conditionally-assigned element The branch never ran Assign unconditionally
A @FindBy field only sometimes Page Object built before the driver Reorder

The Interview Answer

"In a Page Object framework, @FindBy is only an annotation — the WebElement fields stay null until PageFactory.initElements(driver, this) populates them, usually in the constructor. Miss that and every element throws NullPointerException. The other common cause is a null driver — often a shadowed local variable in @BeforeMethod, or a ThreadLocal driver never set on that thread in parallel runs. Personally I prefer storing By locators over @FindBy WebElements — there's nothing to initialize, so this class of NPE and stale-element issues can't occur."


FAQs

Why is my @FindBy element null?

Because @FindBy is only metadata. You must call PageFactory.initElements(driver, this) — normally in the Page Object constructor — for the fields to be populated.

Where should initElements go?

In the Page Object's constructor, right after assigning the driver.

Why is my driver null in @BeforeMethod?

Usually a shadowed variable — WebDriver driver = new ChromeDriver(); creates a local, leaving the field null. Drop the type:

driver = new ChromeDriver();

Why NPE only in parallel runs?

ThreadLocal.get() returns null on threads where set() was never called. Initialize per test method, not per suite.

Can I avoid this entirely?

Yes — store By locators instead of @FindBy WebElements. Nothing to initialize, and no staleness either.