final Keyword
BeginnerApply final to variables (assign-once), methods (no overriding), and classes (no subclassing) to communicate intent and enable JVM optimisations.
Overview
The final keyword has three distinct uses in Java: on a variable it means assign-once (the reference cannot be changed, though the referenced object itself may still be mutable); on a method it means no subclass can override it; on a class it means no subclass can extend it. Correctly using final communicates design intent clearly and enables JVM optimisations like inlining. Java 8+ also introduced "effectively final" — a variable that is never reassigned can be used in lambdas even without the explicit final keyword.
final Variables & Blank Finals
A final local variable or field must be assigned exactly once. For instance fields, you can assign them in the declaration or in the constructor (blank final). Static final fields must be assigned in the declaration or in a static initialiser block.
final does NOT make an object immutable — it only prevents reassigning the reference. A final List can still have items added to it. For true immutability you must also make the object's content unmodifiable (see the Immutability topic).
import java.util.ArrayList;
import java.util.List;
public class FinalDemo {
// Static constant — public static final, UPPER_SNAKE_CASE
public static final double TAX_RATE = 0.18;
// Blank final — assigned in constructor, not at declaration
private final String id;
private final List<String> items = new ArrayList<>(); // final ref, mutable object!
public FinalDemo(String id) {
this.id = id; // assigned exactly once
// this.id = "other"; // compile error — already assigned
}
public void addItem(String item) {
items.add(item); // OK — final only prevents reassigning the reference
// items = new ArrayList<>(); // compile error — cannot reassign final field
}
public static void main(String[] args) {
FinalDemo demo = new FinalDemo("order-42");
demo.addItem("book");
demo.addItem("pen");
System.out.println(demo.items); // [book, pen]
// final local variable — effectively like a constant in scope
final int MAX = 10;
// MAX = 20; // compile error
// Effectively final (Java 8+) — not declared final but never reassigned
String prefix = "Hello"; // effectively final
Runnable r = () -> System.out.println(prefix + " World"); // OK in lambda
r.run();
// String prefix2 = "Hello";
// prefix2 = "Hi"; // reassigned — NOT effectively final
// Runnable r2 = () -> System.out.println(prefix2); // compile error
}
}final Methods & final Classes
A final method cannot be overridden in a subclass — the compiler enforces this. Use it when the method's behaviour must remain stable regardless of subclassing (e.g., template method skeletons, security-sensitive operations).
A final class cannot be subclassed at all. String, Integer, and all other wrapper classes are final — ensuring their immutability cannot be broken by a subclass that overrides equals() or hashCode() in a way that violates the contract. Declare your own classes final when subclassing would be unsafe or meaningless.
// final class — cannot be extended
public final class SSN {
private final String value;
public SSN(String value) {
if (!value.matches("\d{3}-\d{2}-\d{4}"))
throw new IllegalArgumentException("Invalid SSN format");
this.value = value;
}
public String getMasked() { return "***-**-" + value.substring(7); }
@Override public String toString() { return getMasked(); }
}
// class ExtendedSSN extends SSN { } // compile error — final class
// final method in a non-final class
public class BaseProcessor {
// final method — subclasses can add behaviour but not override this method
public final void process(String data) {
validate(data); // may be overridden
doProcess(data); // must be overridden (abstract or hook)
audit(data); // fixed — always runs
}
protected void validate(String data) {
if (data == null) throw new NullPointerException();
}
protected void doProcess(String data) {
System.out.println("Processing: " + data);
}
private void audit(String data) {
System.out.println("Audited: " + data.length() + " chars");
}
}
public class LoggingProcessor extends BaseProcessor {
@Override
protected void doProcess(String data) {
System.out.println("[LOG] " + data);
super.doProcess(data);
}
// @Override public void process(...) { } // compile error — process() is final
}final, Effectively Final & Performance
Effectively final variables (Java 8+): a local variable or parameter that is never reassigned after initialisation. Lambdas and anonymous classes can only capture variables that are final or effectively final — this prevents the confusion of a captured variable changing after capture.
final and JVM optimisations: the JVM can inline calls to final methods at compile/JIT time, eliminating virtual method dispatch overhead. For static final primitives, the compiler inlines the value directly at call sites. These are micro-optimisations — prefer final for design clarity first, performance second.
import java.util.List;
import java.util.function.Predicate;
public class EffectivelyFinalDemo {
public static void main(String[] args) {
// Effectively final — never reassigned, usable in lambda
int threshold = 5;
Predicate<Integer> isAbove = n -> n > threshold; // OK
System.out.println(isAbove.test(7)); // true
// NOT effectively final — reassigned, can't capture
// int limit = 10;
// limit = 20; // reassignment
// Predicate<Integer> p = n -> n > limit; // compile error
// Common pattern: copy to effectively-final local before lambda
String mutableValue = System.getProperty("java.version");
// mutableValue = "override"; // if this line exists, lambda below breaks
List<String> lines = List.of("Java 17", "Java 21");
lines.stream()
.filter(l -> l.contains(mutableValue.substring(0, 4)))
.forEach(System.out::println);
// Static final inlined by compiler
System.out.println(Math.PI); // compiler may inline 3.141592653589793 directly
}
}Key Points to Remember
- final variable: assign once — for fields, in declaration or constructor (blank final)
- final does not make an object immutable — it only prevents reassigning the reference
- final method: cannot be overridden — use for security-sensitive or template skeleton methods
- final class: cannot be subclassed — String and all wrappers are final
- Effectively final (Java 8+): never reassigned after init; can be captured in lambdas
- static final primitive constants are inlined by the compiler at call sites
Practice final Keyword in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between final, finally, and finalize in Java?
Can you change the contents of a final List in Java?
What is an effectively final variable in Java 8+?
Why is String declared as final in Java?
What is a blank final field and where must it be assigned?
Ask Aria about final Keyword
Your personal AI tutor — ask anything about this concept