final Keyword
The final keyword is Java’s way of saying “this cannot change.” You can apply it to variables, methods, and classes, and each context carries a slightly different meaning — but the underlying idea is always the same: protection from modification.
final Variables
When you declare a variable as final, you can assign it a value exactly once. Any attempt to reassign it afterwards causes a compile-time error.
public class Circle {
final double PI = 3.14159;
void calculate(double radius) {
// PI = 3.14; // compile error: cannot assign a value to final variable PI
System.out.println("Area: " + PI * radius * radius);
}
}
Output:
Area: 78.53975
Constants — static final
The most common real-world use of final is declaring class-level constants. Pair it with static so the constant belongs to the class rather than each instance:
public class MathConstants {
public static final double PI = 3.14159265358979;
public static final int MAX_RETRIES = 3;
}
class App {
public static void main(String[] args) {
System.out.println(MathConstants.PI); // 3.14159265358979
System.out.println(MathConstants.MAX_RETRIES); // 3
}
}
Tip: By convention,
static finalconstants are written inUPPER_SNAKE_CASE.
Blank final Variables
You don’t have to assign a final variable at declaration. A blank final variable can be assigned later — but only once, and it must be assigned before it’s used. For instance fields, the assignment must happen in every constructor.
public class User {
final String username; // blank final
public User(String username) {
this.username = username; // assigned exactly once here
}
public static void main(String[] args) {
User u = new User("alice");
System.out.println(u.username); // alice
// u.username = "bob"; // compile error
}
}
Output:
alice
This pattern is very useful for immutable data objects — once constructed, the object’s identity fields can never change.
final Parameters
You can also mark a method parameter as final, which prevents reassigning it inside the method body:
public class Formatter {
public String greet(final String name) {
// name = name.trim(); // compile error
return "Hello, " + name + "!";
}
}
Note: Marking parameters
finalis optional style. It does not affect how arguments are passed — Java is always call by value.
Effectively Final (Java 8+)
From Java 8 onwards, a variable that is never reassigned is treated as effectively final, even without the keyword. This matters for lambda expressions and anonymous inner classes, which can only capture effectively-final local variables:
import java.util.List;
public class Demo {
public static void main(String[] args) {
int multiplier = 5; // effectively final — never reassigned
List.of(1, 2, 3).forEach(n -> System.out.println(n * multiplier));
}
}
Output:
5
10
15
final Methods
Marking a method final prevents any subclass from overriding it. The method can still be inherited and called — it just cannot be replaced.
public class Vehicle {
public final void startEngine() {
System.out.println("Engine started.");
}
}
class Car extends Vehicle {
// @Override
// public void startEngine() { } // compile error: cannot override final method
}
class App {
public static void main(String[] args) {
Car car = new Car();
car.startEngine(); // inherited, not overridden
}
}
Output:
Engine started.
Use final methods when the behavior is part of a security guarantee, a contract that subclasses should not alter, or a performance-sensitive path (see the Under the Hood section below).
final Classes
A final class cannot be subclassed at all. You cannot write class MyString extends String because String is declared final.
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
// class ExtendedPoint extends ImmutablePoint { } // compile error
Common examples of final classes in the JDK:
java.lang.Stringjava.lang.Integer(and all wrapper classes)java.lang.Math
Warning: Making a class
finalis a strong API commitment. Once published asfinal, you can never un-final it without breaking subclasses that your users might have written in the meantime. Think carefully before using it in public libraries.
Quick Reference
| Context | What final prevents |
|---|---|
| Local variable | Reassignment after first assignment |
| Instance field | Reassignment after constructor completes |
static field | Reassignment after static initializer |
| Method parameter | Reassignment inside the method body |
| Method | Overriding in subclasses |
| Class | Any form of subclassing |
Under the Hood
Constant Folding for Primitive Finals
When you declare a static final primitive (or String literal), the Java compiler performs constant folding — it inlines the value directly at every use site. Open the bytecode with the javap tool and you will not see a field reference; you will see the literal value baked into the instruction stream. This makes reads zero-cost at runtime.
public class Config {
public static final int TIMEOUT = 30;
public static void main(String[] args) {
int t = TIMEOUT; // bytecode: bipush 30 (no field lookup)
System.out.println(t);
}
}
final Methods and JIT Inlining
The JVM’s JIT compiler can inline method calls to avoid the overhead of a virtual dispatch. For non-final methods the JIT must check whether a subclass has overridden the method before inlining (a technique called speculative inlining with deoptimization guards). A final method gives the JIT a static guarantee — no subclass can override it — so inlining is unconditional and cheaper. In hot loops this can matter measurably.
Note: Modern JITs are very good at speculative inlining, so the performance gap between final and non-final methods is often negligible. Prefer
finalfor correctness and design clarity, not purely as a micro-optimisation.
final Fields and the Java Memory Model
The Java Memory Model gives final fields a special visibility guarantee: once a constructor completes, all threads are guaranteed to see the correctly initialised values of the object’s final fields — without needing any explicit synchronisation. This is why immutable objects built from final fields are inherently thread-safe.
public final class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
// Safe to share across threads without synchronization
}
Relationship to Immutability
final on a field only prevents reassignment of the reference (or primitive value). If the field holds a mutable object, the object’s internal state can still change:
import java.util.ArrayList;
import java.util.List;
public class Demo {
final List<String> names = new ArrayList<>();
public static void main(String[] args) {
Demo d = new Demo();
d.names.add("Alice"); // allowed — mutating the object, not the reference
// d.names = new ArrayList<>(); // compile error — reassigning the reference
System.out.println(d.names);
}
}
Output:
[Alice]
For true immutability you need to combine final fields with immutable (or defensively-copied) objects. See Create an Immutable Class for the full pattern.
Related Topics
- Inheritance —
finalclasses and methods directly control what subclasses can and cannot do. - Method Overriding — understand what
finalmethods block and why that matters. - Abstract Class — the opposite of
final: abstract classes are designed to be subclassed. - Immutable Class — how
finalfields combine with other techniques to build truly immutable objects. - Java Memory Model — the visibility guarantees that
finalfields carry in a multithreaded environment. - Static Keyword —
static finaltogether define class-level constants.