On this page7 sections
CommandsIntermediate6-8 min reference

Core Java for Testers

A focused tour of the Java features you'll actually use in test automation: data types, collections, exception handling, streams, and the patterns that show up in every Selenium/REST-Assured/JUnit project.

Data Types & Variables

Primitive types

int     n  = 42;                  // 32-bit integer (-2^31 .. 2^31-1)
long    id = 9_000_000_000L;      // 64-bit integer, requires L suffix
double  pi = 3.14159;             // 64-bit floating point
float   f  = 1.5f;                // 32-bit float, requires f suffix
boolean ok = true;                // true | false
char    c  = 'A';                 // single 16-bit Unicode character
byte    b  = 127;                 // 8-bit (-128 .. 127)
short   s  = 30000;               // 16-bit

String creation, methods, and pitfalls

String name = "QA Engineer";
 
name.length();                    // 11
name.substring(3, 8);             // "Engin"
name.contains("Engineer");        // true
name.startsWith("QA");            // true
name.endsWith("er");              // true
name.toLowerCase();               // "qa engineer"
name.toUpperCase();
name.trim();                      // strip leading/trailing whitespace
name.strip();                     // Unicode-aware (Java 11+)
name.replace("QA", "Test");       // "Test Engineer"
name.split(" ");                  // ["QA", "Engineer"]
String.join(", ", "a", "b", "c"); // "a, b, c"
String.format("user=%s id=%d", name, 42);

== vs .equals()

String a = "qa";
String b = "qa";
String c = new String("qa");
 
a == b;           // true   — same string pool reference
a == c;           // false  — different objects
a.equals(c);      // true   — value comparison
a.equalsIgnoreCase("QA");   // true

Always use .equals() for string comparison. == checks reference identity, not content.

Type casting

double d = 3.99;
int    i = (int) d;                // 3 (truncates, no rounding)
 
int n = Integer.parseInt("123");
double x = Double.parseDouble("3.14");
String s = String.valueOf(42);     // "42"
 
// Boxing / unboxing
Integer boxed = 42;                // auto-box int → Integer
int unboxed = boxed;               // auto-unbox Integer → int

var (Java 10+)

var list = new ArrayList<String>();          // ArrayList<String>
var users = Map.of("alice", 1, "bob", 2);    // Map<String, Integer>
var response = restTemplate.getForObject(url, User.class);
 
// var is local-only — can't use for fields, params, or null without explicit type

OOP Fundamentals

Class and object

public class User {
  private String name;
  private int age;
 
  public User(String name, int age) {       // constructor
    this.name = name;
    this.age = age;
  }
 
  public User() {                            // overloaded default constructor
    this("Anonymous", 0);
  }
 
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
}
 
User u = new User("Ada", 36);

Access modifiers

ModifierSame classSame packageSubclassAnywhere
public
protected
(default)
private

Inheritance

public class BasePage {
  protected WebDriver driver;
 
  public BasePage(WebDriver driver) {
    this.driver = driver;
  }
 
  public String getTitle() {
    return driver.getTitle();
  }
}
 
public class LoginPage extends BasePage {
  public LoginPage(WebDriver driver) {
    super(driver);                        // call parent constructor
  }
 
  @Override
  public String getTitle() {              // method override
    return "Login — " + super.getTitle(); // call parent method
  }
}

Abstract classes

public abstract class TestBase {
  protected WebDriver driver;
 
  @BeforeEach
  public abstract void setUp();           // subclasses must implement
 
  @AfterEach
  public void tearDown() {                // shared concrete method
    if (driver != null) driver.quit();
  }
}

Interfaces

public interface Searchable {
  void search(String term);
 
  default boolean isEmpty(String term) {  // default method (Java 8+)
    return term == null || term.isBlank();
  }
}
 
public class GoogleSearch implements Searchable {
  @Override
  public void search(String term) { /* ... */ }
}

Polymorphism: overloading vs overriding

// Overloading — same method name, different parameters
public class Logger {
  public void log(String msg) { /* ... */ }
  public void log(String msg, int level) { /* ... */ }
  public void log(Throwable err) { /* ... */ }
}
 
// Overriding — subclass replaces parent's method
@Override
public String toString() {
  return "User[" + name + "]";
}

instanceof and pattern matching (Java 16+)

Object o = getValue();
 
if (o instanceof String s) {              // pattern matching binds 's'
  System.out.println(s.toUpperCase());
} else if (o instanceof Integer i && i > 0) {
  System.out.println("positive int: " + i);
}

static

public class TestConfig {
  public static final String BASE_URL = "https://staging.example.com";
  private static TestConfig instance;
 
  public static TestConfig getInstance() { /* ... */ }
 
  public static long timestamp() {
    return System.currentTimeMillis();
  }
}
 
TestConfig.timestamp();                   // call without instance
TestConfig.BASE_URL;                      // access constant directly

Collections Framework

List

List<String> users = new ArrayList<>();
users.add("alice");
users.add("bob");
users.add(0, "admin");                    // insert at index
users.get(0);                             // "admin"
users.set(1, "ada");                      // replace at index
users.remove("bob");
users.size();
users.contains("ada");                    // true
users.isEmpty();                          // false
users.indexOf("ada");                     // 1
 
// Immutable
List<String> roles = List.of("admin", "editor", "viewer");
 
// Convert array → list
List<String> fromArray = Arrays.asList("a", "b", "c");

ArrayList for fast random access; LinkedList only when you need Queue/Deque semantics.

Set

Set<String> tags = new HashSet<>();
tags.add("smoke");
tags.add("regression");
tags.add("smoke");                        // ignored — duplicate
tags.size();                              // 2
tags.contains("smoke");                   // true
tags.remove("regression");
 
// Sorted
Set<String> sorted = new TreeSet<>(tags);
 
// Immutable
Set<String> envs = Set.of("dev", "stg", "prod");

Map

Map<String, Integer> scores = new HashMap<>();
scores.put("alice", 95);
scores.put("bob", 88);
scores.get("alice");                      // 95
scores.getOrDefault("missing", 0);        // 0
scores.containsKey("alice");
scores.containsValue(95);
scores.remove("bob");
scores.size();
 
// Iterate
for (Map.Entry<String, Integer> e : scores.entrySet()) {
  System.out.println(e.getKey() + " = " + e.getValue());
}
 
scores.keySet();                          // Set<String>
scores.values();                          // Collection<Integer>
 
// Immutable
Map<String, String> env = Map.of("BASE_URL", "https://api.example.com",
                                  "TIMEOUT", "30");

Queue (via LinkedList)

Queue<String> q = new LinkedList<>();
q.offer("first");                         // add to tail
q.offer("second");
q.peek();                                 // "first" — head, no remove
q.poll();                                 // "first" — head, remove

Iterating

List<String> users = List.of("ada", "bob", "cy");
 
for (int i = 0; i < users.size(); i++) {
  System.out.println(users.get(i));
}
 
for (String u : users) {                  // for-each
  System.out.println(u);
}
 
users.forEach(System.out::println);       // method reference
 
Iterator<String> it = users.iterator();
while (it.hasNext()) {
  System.out.println(it.next());
}

Sorting and unmodifiable

List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
Collections.sort(nums);                       // [1, 2, 3]
Collections.sort(nums, Comparator.reverseOrder());
nums.sort((a, b) -> a - b);
 
List<Integer> readonly = Collections.unmodifiableList(nums);
// readonly.add(4);  → UnsupportedOperationException

Exception Handling

try / catch / finally

try {
  driver.findElement(By.id("submit")).click();
} catch (NoSuchElementException e) {
  log.warn("Submit not found", e);
} catch (ElementNotInteractableException | StaleElementReferenceException e) {
  log.warn("Element issue", e);
} catch (Exception e) {
  log.error("Unexpected", e);
  throw new RuntimeException("Test failed", e);
} finally {
  driver.quit();                          // always runs
}

throw and throws

public User loadUser(String id) throws UserNotFoundException {
  if (id == null) throw new IllegalArgumentException("id required");
  User u = repo.find(id);
  if (u == null) throw new UserNotFoundException(id);
  return u;
}

Checked vs unchecked

  • Checked (extends Exception, not RuntimeException) — caller must declare throws or catch. Examples: IOException, SQLException.
  • Unchecked (extends RuntimeException) — propagate freely. Examples: NullPointerException, IllegalStateException, ArrayIndexOutOfBoundsException, ClassCastException.

Common exceptions

NullPointerException           // calling a method on null
ArrayIndexOutOfBoundsException // array index < 0 or >= length
ClassCastException             // bad cast
NumberFormatException          // Integer.parseInt("abc")
IllegalArgumentException       // invalid arg passed by caller
IllegalStateException          // method called at wrong lifecycle point
IOException                    // file/network errors (checked)

Custom exception

public class TestSetupException extends RuntimeException {
  public TestSetupException(String msg) { super(msg); }
  public TestSetupException(String msg, Throwable cause) { super(msg, cause); }
}

try-with-resources

Auto-closes any AutoCloseable (files, JDBC connections, HTTP clients):

try (BufferedReader r = Files.newBufferedReader(Path.of("test.csv"))) {
  String line;
  while ((line = r.readLine()) != null) {
    process(line);
  }
}                                         // r.close() called automatically

Streams & Lambdas (Java 8+)

Lambda syntax

Runnable r1 = () -> System.out.println("run");
Consumer<String> c1 = s -> System.out.println(s);
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Function<String, Integer> len = s -> {     // multi-line block
  String trimmed = s.trim();
  return trimmed.length();
};

Functional interfaces

Predicate<String> notBlank = s -> s != null && !s.isBlank();
Function<String, Integer> length = String::length;
Consumer<String> print = System.out::println;
Supplier<LocalDateTime> now = LocalDateTime::now;
BiFunction<String, String, String> concat = (a, b) -> a + b;

Stream pipeline

List<User> users = repo.all();
 
List<String> activeAdmins = users.stream()
  .filter(u -> u.isActive())
  .filter(u -> u.getRole() == Role.ADMIN)
  .map(User::getEmail)
  .sorted()
  .distinct()
  .limit(50)
  .collect(Collectors.toList());

Common terminal operations

long count = users.stream().filter(User::isActive).count();
 
Optional<User> first = users.stream()
  .filter(u -> u.getId() == 42)
  .findFirst();
 
boolean anyAdmin = users.stream().anyMatch(u -> u.getRole() == Role.ADMIN);
boolean allActive = users.stream().allMatch(User::isActive);
boolean noneBanned = users.stream().noneMatch(User::isBanned);
 
users.stream().forEach(System.out::println);
 
int totalAge = users.stream().mapToInt(User::getAge).sum();

Collectors

import static java.util.stream.Collectors.*;
 
List<String> emails = stream.map(User::getEmail).collect(toList());
Set<String>  unique = stream.map(User::getEmail).collect(toSet());
 
Map<Integer, User> byId = users.stream().collect(toMap(User::getId, u -> u));
 
Map<Role, List<User>> byRole = users.stream()
  .collect(groupingBy(User::getRole));
 
Map<Role, Long> counts = users.stream()
  .collect(groupingBy(User::getRole, counting()));
 
String csv = users.stream()
  .map(User::getName)
  .collect(joining(", ", "[", "]"));      // "[Ada, Bob, Cy]"

Method references

String::toLowerCase                 // x -> x.toLowerCase()
System.out::println                 // x -> System.out.println(x)
Integer::parseInt                   // s -> Integer.parseInt(s)
User::new                           // (name, age) -> new User(name, age)

File I/O

Reading

import java.nio.file.*;
 
// Whole file as String
String content = Files.readString(Path.of("test.json"));
 
// All lines
List<String> lines = Files.readAllLines(Path.of("data.csv"));
 
// Stream over lines (lazy, closes the file when stream is closed)
try (Stream<String> stream = Files.lines(Path.of("big.log"))) {
  stream.filter(l -> l.contains("ERROR")).forEach(System.out::println);
}
 
// BufferedReader
try (BufferedReader r = Files.newBufferedReader(Path.of("test.txt"))) {
  String line;
  while ((line = r.readLine()) != null) {
    process(line);
  }
}

Writing

Files.writeString(Path.of("out.txt"), "hello");
Files.write(Path.of("out.txt"), List.of("line 1", "line 2"));
 
try (BufferedWriter w = Files.newBufferedWriter(Path.of("out.txt"))) {
  w.write("hello");
  w.newLine();
}

Properties files

config.properties:

base.url=https://staging.example.com
api.timeout=30000
admin.email=qa@example.com
Properties props = new Properties();
try (InputStream in = Files.newInputStream(Path.of("config.properties"))) {
  props.load(in);
}
String baseUrl = props.getProperty("base.url");
int timeout = Integer.parseInt(props.getProperty("api.timeout", "10000"));

JSON with Jackson

import com.fasterxml.jackson.databind.ObjectMapper;
 
ObjectMapper mapper = new ObjectMapper();
 
User u = mapper.readValue(new File("user.json"), User.class);
List<User> users = mapper.readValue(json,
    mapper.getTypeFactory().constructCollectionType(List.class, User.class));
 
String out = mapper.writeValueAsString(u);
mapper.writerWithDefaultPrettyPrinter().writeValue(new File("out.json"), u);

JSON with Gson

import com.google.gson.Gson;
 
Gson gson = new Gson();
User u = gson.fromJson(json, User.class);
String out = gson.toJson(u);

Common Patterns in Test Automation

Builder pattern for test data

public class UserBuilder {
  private String name = "Default";
  private int age = 30;
  private String email = "default@example.com";
 
  public UserBuilder name(String n)  { this.name  = n; return this; }
  public UserBuilder age(int a)      { this.age   = a; return this; }
  public UserBuilder email(String e) { this.email = e; return this; }
 
  public User build() { return new User(name, age, email); }
}
 
User u = new UserBuilder().name("Ada").age(36).build();

Page Object Model

public class LoginPage {
  private final WebDriver driver;
  private final By emailField    = By.cssSelector("[data-testid=email]");
  private final By passwordField = By.cssSelector("[data-testid=password]");
  private final By submitBtn     = By.cssSelector("[data-testid=submit]");
 
  public LoginPage(WebDriver driver) { this.driver = driver; }
 
  public DashboardPage loginAs(String email, String password) {
    driver.findElement(emailField).sendKeys(email);
    driver.findElement(passwordField).sendKeys(password);
    driver.findElement(submitBtn).click();
    return new DashboardPage(driver);
  }
}

Factory pattern for driver creation

public class DriverFactory {
  public static WebDriver create(String type) {
    return switch (type.toLowerCase()) {
      case "chrome"  -> new ChromeDriver();
      case "firefox" -> new FirefoxDriver();
      case "safari"  -> new SafariDriver();
      default        -> throw new IllegalArgumentException("Unknown: " + type);
    };
  }
}

Singleton for config

public class TestConfig {
  private static volatile TestConfig instance;
  private final Properties props = loadProps();
 
  private TestConfig() {}
 
  public static TestConfig get() {
    if (instance == null) {
      synchronized (TestConfig.class) {
        if (instance == null) instance = new TestConfig();
      }
    }
    return instance;
  }
 
  public String baseUrl() { return props.getProperty("base.url"); }
}
 
String url = TestConfig.get().baseUrl();

JUnit 5 annotations & assertions

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
 
class CheckoutTest {
 
  @BeforeAll  static void setupAll()  { /* once before all tests */ }
  @AfterAll   static void teardownAll() {}
 
  @BeforeEach void setup()    { /* before every test */ }
  @AfterEach  void teardown() {}
 
  @Test
  @DisplayName("Cart total equals sum of items")
  void cartTotalSum() {
    Cart c = new Cart();
    c.add(new Item(10), new Item(15));
    assertEquals(25, c.total());
    assertTrue(c.itemCount() > 0);
    assertNotNull(c.id());
    assertThrows(IllegalStateException.class, () -> c.checkout());
    assertAll("cart",
      () -> assertEquals(2, c.itemCount()),
      () -> assertFalse(c.isEmpty()));
  }
 
  @Disabled("flaky on CI, see #1234")
  @Test void brokenTest() {}
 
  @ParameterizedTest
  @ValueSource(strings = {"@example.com", "user@", ""})
  void rejectsInvalidEmails(String email) {
    assertThrows(ValidationException.class, () -> new User(email));
  }
}