The final Keyword in Java
This code compiles and runs, even though the list is declared final:
final List<String> names = new ArrayList<>();
names.add("Anna");
names.add("Boris");
System.out.println(names); // [Anna, Boris] That is where most people trip: final forbids pointing the variable at another object, but it never freezes the object itself. Let's walk through what the final modifier does to variables, methods and classes — and what it deliberately does not do.
1. What the final keyword is in Java
final is a Java modifier that blocks any further change to whatever it is applied to: a variable cannot be reassigned, a method cannot be overridden, and a class cannot be extended. The word says it plainly — the declaration is finished, and nobody downstream is allowed to revise it.
final is not an access modifier. It combines freely with public, private and static, and it goes before the type.
| Where final is applied | What it forbids | Example |
|---|---|---|
| Local variable | Reassignment | final int x = 5; |
| Method parameter | Changing the parameter inside the method | void print(final double d) |
| Instance field | Any change after initialization (declaration, initializer block or constructor) | private final String name; |
| Static field (constant) | Any change after initialization (declaration or static initializer block) | public static final int MAX_WEIGHT = 100; |
| Method | Overriding in a subclass | public final void print() |
| Class | Inheritance, that is, creating subclasses | public final class Money |
2. final variables: initialization rules
A variable declared final receives its value exactly once. A second assignment is a compile-time error, not a runtime failure — the code never makes it into a build.
The rules depend on the kind of variable:
- Instance field (
finalwithoutstatic) — initialized at the declaration, in an instance initializer block, or in a constructor. A field left without a value at the declaration is called a blank final and must be assigned exactly once in every constructor of the class. - Static field (
static final) — initialized at the declaration or in a static initializer block. - Local variable — may be declared without a value and assigned later, but only once.
- Method parameter — the value arrives with the call and cannot be changed inside the method body.
Break one of those rules and the compiler answers with variable might not have been initialized or cannot assign a value to final variable.
The example below shows the different flavours of final declarations; the commented-out lines are the ones that would not compile:
public class FinalVariablesExample {
public static final int FILE_NEW = 1; // static final: class constant
private final String someString = "something"; // final String: instance field
public static void print(final double d) { // final double: method parameter
// FILE_NEW = 2; // error: cannot assign a value to final variable FILE_NEW
final String str; // local final variable, no value yet
str = "someString"; // the single assignment - allowed
// str = ""; // error: variable str might already have been assigned
// d = 4; // error: final parameter d may not be assigned
System.out.println("FILE_NEW = " + FILE_NEW);
System.out.println("str = " + str);
System.out.println("d = " + d);
}
public static void main(String[] args) {
print(3);
}
} The everyday use of a blank final is an object whose state is fixed by the constructor and never touched again:
public class User {
private final String name; // blank final
private final int id;
public User(String name, int id) {
this.name = name; // assigned exactly once
this.id = id;
}
public String getName() {
return name;
}
// there can be no setter for name: a second assignment would not compile
} 3. final and object references
For reference types final freezes the reference, not the state of the object. The variable keeps pointing at the same object forever, yet that object is free to change if it is mutable:
final List<String> names = new ArrayList<>();
names.add("Anna"); // OK: we mutate the object, not the reference
names.clear(); // OK
// names = new ArrayList<>(); // error: cannot assign a value to final variable names
final int[] numbers = {1, 2, 3};
numbers[0] = 42; // OK: array elements are not protected
// numbers = new int[5]; // compile-time error String rarely causes confusion here, because String is immutable already: every method returns a brand-new string and leaves the original untouched.
final String greeting = "Hello";
// greeting = "Hi"; // compile-time error
String upper = greeting.toUpperCase(); // OK: a new string is created
System.out.println(greeting); // Hello - the original is unchanged Important
final on its own does not make an object immutable. A genuinely immutable class needs final fields, no setters, defensive copies of mutable state, and no subclasses (a final class or a record). One real bonus you do get: values written to final fields in a constructor are guaranteed by the Java Memory Model to be visible to other threads once the object is published, with no extra synchronization.
4. Constants in Java: public static final
A constant in Java is a class field declared public static final: one value shared by the whole class, reachable without creating an object, and protected from reassignment. Constant names are written in upper case with underscores between words: MAX_WEIGHT, DEFAULT_TIMEOUT.
Constants are the standard cure for magic numbers — literals whose meaning is impossible to guess without a comment. Here the number 9.81 shows up three times:
public class PhysicsMagicNumber {
public static double potentialEnergy(double mass, double height) {
return mass * height * 9.81;
}
public static double getVelocity(double time) {
return time * 9.81;
}
public static double getDistance(double time) {
return 9.81 * time * time / 2;
}
} Introducing a final double constant called ACCELERATION fixes two problems at once: the name explains what the number means, and the value now lives in a single place, so changing it is a one-line edit.
public class Physics {
public static final double ACCELERATION = 9.81;
public static double potentialEnergy(double mass, double height) {
return mass * height * ACCELERATION;
}
public static double getVelocity(double time) {
return time * ACCELERATION;
}
public static double getDistance(double time) {
return ACCELERATION * time * time / 2;
}
} Worth knowing
Constants of primitive types and String initialized with a literal are compile-time constants: the compiler copies their value straight into the bytecode of every calling class. Change such a constant in a library, rebuild only the library, and the already-compiled callers happily keep using the old value. Whenever a published constant changes, recompile everything that depends on it.
5. final methods: no overriding
To prevent a method from being overridden in subclasses, put final in its declaration. Subclasses still inherit and call the method; what they cannot do is swap in their own implementation. This is how a base class protects behaviour that the rest of its logic depends on — validation, security checks, the skeleton of a template method.
The class Report below declares a final method, and the subclass SalesReport cannot replace it — uncommenting the override produces the compile error overridden method is final:
public class Report {
public final void print() {
System.out.println("This method is final");
}
} public class SalesReport extends Report {
// This method cannot be overridden
/* @Override
public void print() {
System.out.println("Not allowed");
}*/
} A few rules that come up in interviews:
private finalon a method is redundant: aprivatemethod is invisible to subclasses and cannot be overridden anyway.staticmethods are hidden, not overridden, sofinalon a static method simply forbids hiding it in a subclass.- A constructor can never be
final— constructors are not inherited in the first place. abstract finalis illegal for a method:abstractdemands an implementation from a subclass,finalforbids one.finalblocks overriding, not overloading — you may still declare another method with the same name and a different parameter list.
6. final classes: no inheritance
To prevent a class from being extended, mark the class itself final. Every method of such a class is implicitly final as well, simply because there is no subclass left to override anything in. A class cannot be abstract and final at the same time: an abstract class exists purely to be extended.
final class Money {
// ...
}
class Cash extends Money { // ERROR: cannot inherit from final Money
// ...
} The standard library is full of final classes: String, the primitive wrappers such as Integer and Double, LocalDate, Math. They are final so that no subclass can break their immutability or their equals()/hashCode() contract. A record is implicitly final too, and its components are final fields.
Tip
Final classes used to be painful in tests, because plain Mockito could not mock them. Since Mockito 5 the inline mock maker is the default, so final classes and final methods are mockable out of the box. And if you only want to restrict who may extend a class rather than forbid it outright, use sealed classes with a permits clause (Java 17 and later).
7. Parameters, local variables and effectively final
final on a method parameter or a local variable changes nothing for the caller. It is a guard against accidental reassignment inside the method and a signal to the reader that this value stays put.
Java 8 introduced the notion of an effectively final variable: a local variable or parameter that is never reassigned after initialization, even though the word final is not written. Lambdas and anonymous classes can capture only final or effectively final variables:
public void printTotal(List<String> items) {
int total = items.size(); // effectively final: never reassigned
Runnable task = () -> System.out.println("Total: " + total); // OK
task.run();
// total++; // uncomment this and the lambda stops compiling:
// // local variables referenced from a lambda expression
// // must be final or effectively final
} This is one of the first walls developers hit when they move to lambdas: a counter cannot be incremented from inside the lambda. The usual workarounds are a one-element array, an AtomicInteger, or — almost always the better answer — a Stream API operation such as count() or reduce() that returns the result instead of mutating a variable.
8. final vs finally vs finalize
Three lookalike words that belong to three unrelated parts of the language. Telling them apart is a classic interview opener:
| Word | What it is | Purpose |
|---|---|---|
final | Modifier | Blocks reassignment of a variable, overriding of a method, inheritance of a class |
finally | Block of a try-catch-finally statement | Runs either way, whether an exception was thrown or not. Used for releasing resources |
finalize() | Legacy method of Object | An obsolete hook called before garbage collection. Deprecated in Java 9, deprecated for removal in Java 18, and already switchable off with --finalization=disabled — never rely on it. Use try-with-resources or java.lang.ref.Cleaner instead |
9. Key takeaways
finalis a modifier meaning "finished": it applies to variables, methods and classes.- A final variable is assigned exactly once; a second assignment fails at compile time.
- For reference types the reference is frozen, not the object: elements of a final array or a final collection can still be changed.
- A constant is a
public static finalfield named likeMAX_WEIGHT; constants replace magic numbers. - A final method cannot be overridden, a final class cannot be extended;
abstract finaland a final constructor are both illegal. - Lambdas and anonymous classes capture only final or effectively final variables.
Frequently asked questions
Does final make Java code faster?
Not measurably in most cases. The JIT compiler already works out at runtime that a method is never overridden anywhere in the loaded classes and inlines it. The only real optimization tied to the keyword is the inlining of compile-time constants (static final primitives and strings). Use final for readability and design safety, not for speed.
If final does not protect the contents, how do I get a truly unmodifiable collection?
Use the factory methods List.of(), Set.of(), Map.of() (Java 9 and later) or the wrappers such as Collections.unmodifiableList(). They return a collection that throws UnsupportedOperationException on any attempt to modify it. The combination private final List<String> items = List.of(...) locks both the reference and the contents.
Can a method be both abstract and final?
No, that is a compile-time error. abstract says a subclass must supply the implementation, final says no subclass may supply one — the two requirements contradict each other. For the same reason a class cannot be declared abstract final.
Should I write final on every parameter and local variable?
That is a team style decision. In favour: the reader instantly sees that the value never changes, and an accidental reassignment fails to compile. Against: the code becomes noisier. The common compromise is to require final on fields and leave it optional on parameters, since lambdas are satisfied by effectively final variables anyway.
Why does Java have no usable const keyword?
const is a reserved word in Java but has no meaning: you cannot name a variable const, and you cannot declare a constant with it. The job is done by a static final field at class level and by final for values inside a method.
Comments