Java for Selenium Automation: Complete Tutorial (2026)
The 2026 complete Java tutorial for Selenium automation. Step-by-step from JDK setup to OOP, collections, exception handling, TestNG, Maven, Page Object Model, and design patterns.

What you'll master: By the end of this Java for Selenium tutorial you will have a working Java 17 + Maven + TestNG + Selenium 4 setup, the OOP concepts you need to write clean test code, the collections and exception handling patterns every Selenium framework uses, and a production-grade Page Object Model with TestNG data providers and listeners.
1. Why Java for Selenium Automation in 2026
Java has been the dominant language for Selenium since 2010. In 2026 it remains the most widely used, despite competition from Python and JavaScript. Here's why:
- Mature ecosystem — TestNG, JUnit, Maven, Gradle, Spring, Lombok
- Type safety — catches bugs at compile time that Python and JS miss
- Enterprise adoption — most enterprise QA jobs list Java
- Selenium Manager — Selenium 4 auto-resolves the right driver, no driver pain
- IDE support — IntelliJ IDEA is the gold standard for Java + Selenium
Choose Java if you're targeting enterprise roles, want maximum library support, prefer type safety, or your team already uses Java. Choose Python or TypeScript for greenfield projects or faster iteration.
For a deep comparison, see our Playwright vs Selenium guide and our Playwright complete guide.
🚀 Skip the boilerplate. Practise Java + Selenium rounds live with our AI mock interview, and review Selenium interview questions before your next round.
2. Install Java and Maven Step by Step
Install JDK 17 or 21
macOS:
brew install openjdk@17
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> ~/.zshrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
java -versionUbuntu/Debian:
sudo apt update
sudo apt install openjdk-17-jdk
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
java -versionWindows: download the JDK 17 MSI from Adoptium and set JAVA_HOME in System Environment Variables.
Install Maven
# macOS
brew install maven
# Ubuntu/Debian
sudo apt install maven
# Windows: download from maven.apache.org, extract, add bin/ to PATH
mvn -version3. Set Up IntelliJ IDEA for Selenium
Download the Community Edition (free) from jetbrains.com/idea. Create a new Maven project (JDK 17, name selenium-java-demo), then edit pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qa.selenium</groupId>
<artifactId>selenium-java-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<selenium.version>4.25.0</selenium.version>
<testng.version>7.10.2</testng.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>Click Maven → Reload All Maven Projects. For more on Selenium 4 itself, see our Selenium WebDriver guide.
4. Java Fundamentals for Testers
Variables, methods, and control flow
int retryCount = 3;
double timeout = 10.5;
boolean isHeadless = true;
String url = "https://example.com";
String[] browsers = {"chrome", "firefox", "edge"};
public String buildUrl(String host, String path) {
return "https://" + host + path;
}
for (String browser : browsers) {
System.out.println("Testing in " + browser);
}
try {
driver.findElement(By.id("submit")).click();
} catch (NoSuchElementException e) {
takeScreenshot("submit-not-found");
throw e;
}Strings (the most common test-data type)
String email = "admin@example.com";
boolean isValid = email.contains("@");
String domain = email.substring(email.indexOf("@") + 1);
String[] parts = "a,b,c".split(",");The var keyword (Java 10+)
var driver = new ChromeDriver();
var wait = new WebDriverWait(driver, Duration.ofSeconds(10));Use var for local variables when the type is obvious; skip it when the type adds clarity.
5. OOP Concepts Every Selenium Engineer Must Know
Classes and objects
public class LoginPage {
private WebDriver driver;
public LoginPage(WebDriver driver) { this.driver = driver; }
public void login(String email, String password) {
driver.findElement(By.id("email")).sendKeys(email);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("submit")).click();
}
}Inheritance
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
public abstract boolean isLoaded();
}
public class LoginPage extends BasePage {
public LoginPage(WebDriver driver) { super(driver); }
@Override
public boolean isLoaded() {
return wait.until(ExpectedConditions.urlContains("/login")) != null;
}
}Polymorphism via interfaces
public interface Browser { WebDriver createDriver(); }
public class ChromeBrowser implements Browser {
public WebDriver createDriver() { return new ChromeDriver(); }
}
public class FirefoxBrowser implements Browser {
public WebDriver createDriver() { return new FirefoxDriver(); }
}Abstract class = partial implementation (use for BasePage); interface = pure contract (use for Browser, Listener, Config).
6. Collections You Will Use Daily
// List — ordered, allows duplicates
List<WebElement> rows = driver.findElements(By.cssSelector("tr"));
int rowCount = rows.size();
// Map — key-value pairs
Map<String, String> credentials = new HashMap<>();
credentials.put("admin", "Sup3rSecret!");
String adminPassword = credentials.get("admin");
// Set — unique values, no order
Set<String> uniqueEmails = new HashSet<>();
uniqueEmails.add("a@example.com");
// Stream API
List<WebElement> enabledButtons = driver.findElements(By.tagName("button"))
.stream()
.filter(WebElement::isEnabled)
.filter(WebElement::isDisplayed)
.collect(Collectors.toList());| Collection | Use case |
|---|---|
List | Ordered collection, duplicates OK (rows, browser list) |
Map | Lookup by key (config, credentials, test data) |
Set | Unique values (visited URLs, error codes) |
Queue | FIFO processing (to-be-processed items) |
7. Exception Handling the Right Way
public void clickSafely(By locator) {
try {
wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
} catch (TimeoutException e) {
takeScreenshot("click-timeout");
throw new AssertionError("Element not clickable: " + locator, e);
} catch (StaleElementReferenceException e) {
// retry once
wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
}
}Custom exceptions for test code
public class LoginFailedException extends RuntimeException {
public LoginFailedException(String message, Throwable cause) {
super(message, cause);
}
}TestNG assertions throw AssertionError; don't wrap them in try/catch — let TestNG report the failure.
Screenshot helper
public class ScreenshotHelper {
public static String takeScreenshot(WebDriver driver, String name) {
var src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
var dest = new File("screenshots/" + name + "_" + Instant.now().toEpochMilli() + ".png");
dest.getParentFile().mkdirs();
try { Files.copy(src.toPath(), dest.toPath()); }
catch (IOException e) { throw new RuntimeException("Failed to save screenshot", e); }
return dest.getAbsolutePath();
}
}8. File I/O and Properties for Test Data
src/test/resources/config.properties:
base.url=https://staging.example.com
admin.email=admin@example.com
admin.password=Sup3rSecret!
browser=chrome
timeout.seconds=10public class Config {
private final Properties props = new Properties();
public Config() {
try (var input = new FileInputStream("src/test/resources/config.properties")) {
props.load(input);
} catch (IOException e) { throw new RuntimeException("Failed to load config", e); }
}
public String get(String key) { return props.getProperty(key); }
public int getInt(String key) { return Integer.parseInt(get(key)); }
}Reading a JSON test-data file with Jackson
public class TestDataLoader {
private final ObjectMapper mapper = new ObjectMapper();
public List<TestData> loadUsers(String file) throws IOException {
return mapper.readValue(
new File("src/test/resources/" + file),
mapper.getTypeFactory().constructCollectionType(List.class, TestData.class)
);
}
}Use environment variables for secrets and properties files for non-sensitive config. For API-side test data patterns, see our API testing tutorial.
9. TestNG Framework Deep Dive
TestNG is the dominant Java test framework. JUnit 5 is the alternative; pick TestNG if you need data providers and listeners out of the box.
Annotations
public class LoginTest {
@BeforeSuite public void beforeSuite() {}
@BeforeClass public void beforeClass() {}
@BeforeMethod public void beforeMethod() { driver = new ChromeDriver(); }
@Test(priority = 1, groups = "smoke")
public void loginValid() { /* ... */ }
@Test(priority = 2, groups = "regression", dependsOnMethods = "loginValid")
public void loginInvalid() { /* ... */ }
@AfterMethod public void afterMethod() { if (driver != null) driver.quit(); }
}Data providers (the killer feature)
@DataProvider(name = "userCredentials")
public Object[][] userCredentials() {
return new Object[][] {
{"admin@example.com", "Sup3rSecret!", "Welcome, admin"},
{"viewer@example.com", "ViewerPass1!", "Welcome, viewer"},
};
}
@Test(dataProvider = "userCredentials")
public void loginWithMultipleUsers(String email, String password, String expectedGreeting) {
driver.get("https://example.com/login");
driver.findElement(By.id("email")).sendKeys(email);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("submit")).click();
assertEquals(driver.findElement(By.tagName("h1")).getText(), expectedGreeting);
}Listeners
public class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
var driver = (WebDriver) result.getTestContext().getAttribute("driver");
if (driver != null) ScreenshotHelper.takeScreenshot(driver, result.getName());
}
}Parallel execution
<suite name="Suite" parallel="methods" thread-count="4">
<test name="Regression">
<classes>
<class name="com.qa.selenium.LoginTest"/>
<class name="com.qa.selenium.CheckoutTest"/>
</classes>
</test>
</suite>Run with mvn test -DsuiteXmlFile=testng.xml.
10. Maven for Selenium Projects
Standard project structure
selenium-java-demo/
├── pom.xml
├── src/
│ ├── main/java/com/qa/selenium/
│ │ ├── pages/ (BasePage, LoginPage, DashboardPage)
│ │ └── utils/ (Config, ScreenshotHelper)
│ └── test/
│ ├── java/com/qa/selenium/ (LoginTest, CheckoutTest, listeners/)
│ └── resources/ (config.properties, testdata/users.json)Profiles for different environments
<profiles>
<profile>
<id>staging</id>
<properties>
<env>staging</env>
<base.url>https://staging.example.com</base.url>
</properties>
</profile>
</profiles>Run with mvn test -Pstaging.
Surefire plugin (runs tests)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<suiteXmlFiles><suiteXmlFile>testng.xml</suiteXmlFile></suiteXmlFiles>
<parallel>methods</parallel>
<threadCount>4</threadCount>
</configuration>
</plugin>11. Page Object Model in Java
Base page
public abstract class BasePage {
protected final WebDriver driver;
protected final WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(15));
PageFactory.initElements(driver, this);
}
public abstract boolean isLoaded();
public void waitForLoaded() { wait.until(d -> isLoaded()); }
}Login page
public class LoginPage extends BasePage {
@FindBy(id = "email") private WebElement emailField;
@FindBy(id = "password") private WebElement passwordField;
@FindBy(id = "submit") private WebElement submitButton;
@FindBy(css = ".error-message") private WebElement errorMessage;
public LoginPage(WebDriver driver) { super(driver); }
@Override
public boolean isLoaded() {
return wait.until(ExpectedConditions.visibilityOf(emailField)) != null;
}
public DashboardPage loginAs(String email, String password) {
emailField.clear(); emailField.sendKeys(email);
passwordField.clear(); passwordField.sendKeys(password);
submitButton.click();
return new DashboardPage(driver);
}
public String getErrorMessage() { return errorMessage.getText(); }
}Test that uses POM
public class LoginTest extends BaseTest {
@Test
public void loginFlow() {
driver.get("https://example.com/login");
var login = new LoginPage(driver);
login.waitForLoaded();
var dashboard = login.loginAs("admin@example.com", "Sup3rSecret!");
assertTrue(dashboard.getWelcomeMessage().contains("Welcome"));
}
}For framework architecture, see our framework setup guide — the patterns map closely to Java.
12. Design Patterns for Test Automation
Factory Pattern
public class PageFactory {
public static <T extends BasePage> T create(Class<T> pageClass, WebDriver driver) {
try {
return pageClass.getDeclaredConstructor(WebDriver.class).newInstance(driver);
} catch (Exception e) {
throw new RuntimeException("Failed to create page: " + pageClass.getName(), e);
}
}
}Builder Pattern (for complex test data)
public class UserBuilder {
private String email = "default@example.com";
private String password = "DefaultPass1!";
private String role = "viewer";
public UserBuilder withEmail(String email) { this.email = email; return this; }
public UserBuilder withRole(String role) { this.role = role; return this; }
public User build() { return new User(email, password, role); }
}
var admin = new UserBuilder().withEmail("admin@example.com").withRole("admin").build();Strategy Pattern (cross-browser)
public interface DriverStrategy { WebDriver createDriver(); }
public class ChromeStrategy implements DriverStrategy {
public WebDriver createDriver() {
var options = new ChromeOptions();
options.addArguments("--headless=new");
return new ChromeDriver(options);
}
}Singleton + Decorator
Use Singleton sparingly — it makes testing harder. Prefer dependency injection. Decorator is useful for adding logging or retries to a WebDriver without modifying it.
13. Java Best Practices for Selenium in 2026
Code style
- 4 spaces, not tabs; always use braces
- Use Lombok
@Data/@Builderto reduce boilerplate - Prefer composition over inheritance and immutability where possible
- Use
Optionalfor nullable returns - Use
varfor local variables when the type is obvious
Selenium-specific
- Use explicit waits — never
Thread.sleep - Use the Page Object Model for any non-trivial app
- Prefer accessibility IDs and relative locators (Selenium 4)
- Let Selenium Manager resolve drivers automatically
- Reset state between tests (cookies, localStorage, database)
Testing-specific
- Use TestNG data providers for data-driven testing
- Use TestNG listeners for cross-cutting concerns
- Use Maven profiles for environment-specific config
- Use Surefire's parallel mode for parallel execution
Pair this with the SDET career roadmap to turn Java + Selenium into a specialty employers pay for, and run mock rounds at AI mock interview.
Frequently asked questions
1.Do I need Java to learn Selenium?
2.Java vs Python for Selenium — which is better?
3.Which version of Java should I use in 2026?
4.What is the best IDE for Java + Selenium?
5.What is the difference between TestNG and JUnit?
6.Do I need Maven for Selenium projects?
7.How do I run Selenium tests in parallel?
8.What is the Page Object Model?
9.How do I handle flaky Selenium tests?
10.Where can I practice Java + Selenium?
Practice these questions
Work through 300+ Selenium questions with Java code snippets, Selenium 4, Grid, framework patterns and CI/CD scenarios.
Was this article helpful?
Keep building your QA edge
Pillar guides- Playwright PillarPlaywright interview questions300 Playwright Q&A, framework design, and migration guides.
- XPath & CSS Selector GeneratorXPath and CSS selector generatorInteractive locator generator for Playwright, Selenium and Cypress — with Page Object export.
- Automation QA Engineer RoleAutomation QA Engineer career guideAutomation QA Engineer job scope, tools, salary, and hiring pipeline.
- QA Skills Hubstructured learning tracks for testersStructured skill tracks — Selenium, Playwright, Cypress, API, JMeter, SQL, Java, Python for testers.
- QA & Testing GlossaryQA terminology explained in plain English500+ software testing terms defined — from ISTQB vocabulary to CI/CD, AI testing, and framework jargon.
Practice these questions live
Rehearse with an AI QA interviewer that scores your answers in real time.
Continue reading

Playwright Locator Best Practices (2026) — The Only Guide You Need
11 min read
How to Migrate a Postman Collection to Playwright API Tests (2026 Guide)
12 min read
Why Every QA Engineer Must Master CI/CD Pipelines in 2026 (Or Risk Obsolescence)
12 min readRelated concepts, tools & standards around Automation Testing
A quick reference of the people, companies, frameworks and technologies most often mentioned alongside Automation Testing in real QA teams — useful when you're mapping a learning path, preparing for interviews, or scoping a new project.
Join the QA Community
Connect with fellow testers, share job leads, and get career advice.
Discussion
Ask a question, share your experience, or correct us. Be kind — real people are reading.