Q12 of 40 · Core Java

What is the purpose of the `final` keyword in Java?

Core JavaJuniorfinal-keywordimmutabilityjava-fundamentalsoop

Short answer

Short answer: `final` means different things depending on where it's applied: a final variable cannot be reassigned; a final method cannot be overridden; a final class cannot be subclassed. It communicates intent ('this is fixed'), enables JVM optimisations, and is required to make effective immutable objects safe for sharing.

Detail

Final variable: the binding cannot be reassigned after initialisation. For primitives this means the value is constant. For references, it means the reference can't point to a different object — but the object itself can still be mutated. final List<String> tags = new ArrayList<>() allows tags.add("x") but not tags = new ArrayList<>().

This is important in lambda and anonymous class contexts: local variables captured by a lambda must be effectively final — either declared final or never reassigned after their point of use. The compiler enforces this.

Final method: cannot be overridden in a subclass. Use it to protect invariants — if login() must always log audit events before delegating to doLogin(), mark login() final so subclasses can only override doLogin().

Final class: cannot be extended. String, Integer, and all other wrapper classes are final. For test automation, value objects in test data builders are good candidates for final — there's rarely a reason to subclass TestUser or ApiRequest.

Immutability: combining final fields with no mutating methods and defensive copies produces a truly immutable object that is inherently thread-safe. Java Records (Java 16+) bake this in: all component fields are implicitly private final.

JVM optimisation: the JIT compiler can inline final method calls because it knows no dynamic dispatch is needed. This is a micro-optimisation in most code, but it's part of why String is fast.

// EXAMPLE

FinalKeyword.java

// final variable — binding cannot change
final int MAX_RETRIES = 3;
// MAX_RETRIES = 5; // ❌ compile error

// final reference — reference fixed, object mutable
final List<String> tags = new ArrayList<>();
tags.add("smoke");   // ✅ mutates the object
// tags = new ArrayList<>(); // ❌ reassignment not allowed

// Effectively final — required for lambda capture
String baseUrl = "https://api.example.com";
Runnable r = () -> System.out.println(baseUrl); // ✅ never reassigned
// baseUrl = "http://..."; // would break the lambda capture

// final class — immutable value object (Java 17+ record is cleaner)
final class TestCredential {
    private final String username;
    private final String password;

    TestCredential(String username, String password) {
        this.username = username;
        this.password = password;
    }
    // No setters — immutable by design
}

// Java 16+ record: implicitly final, all fields final
record TestCredential(String username, String password) {}

// WHAT INTERVIEWERS LOOK FOR

The three contexts (variable/method/class), the distinction between a final reference and an immutable object, the lambda capture requirement, and practical uses in test code. Strong answers mention Java Records as the modern way to express final-everything value types.

// COMMON PITFALL

Saying 'final makes the object immutable'. A final reference to an ArrayList doesn't prevent adding elements. True immutability requires final fields AND no mutating methods AND (for collections) unmodifiable wrappers or defensive copies.