Using the final modifier whenever applicable in Java
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Java's final keyword locks down a variable, method, or class so it cannot be reassigned, overridden, or subclassed. Using final communicates intent — it tells other developers (and the compiler) that this value should not change, this method's behavior is fixed, or this class is complete. The question is not whether final is useful, but where the tradeoff between safety and flexibility tips in your favor.
Final Variables
A final variable can be assigned exactly once. Any further assignment is a compile error.
final on a reference variable means the reference is fixed, not the object it points to. A final List can still be modified — only reassignment is blocked.
Blank Finals
A final field does not need to be initialized at declaration. It can be assigned once in the constructor:
This pattern creates immutable objects: once constructed, the state never changes. Immutable objects are inherently thread-safe.
Final Local Variables and Parameters
Since Java 8, local variables used in lambdas only need to be effectively final (not reassigned), so the explicit final keyword is optional in that context.
Final Methods
A final method cannot be overridden by subclasses:
Use final on methods that enforce invariants — validation, security checks, template method skeletons — where subclass overrides would break correctness.
Final Classes
A final class cannot be subclassed:
Java's String, Integer, LocalDate, and all wrapper classes are final. This guarantees their immutability cannot be broken by a subclass that overrides methods.
When to Use Final — Practical Guidelines
Use final for
- Constants:
static finalfields withUPPER_SNAKE_CASEnames - Immutable fields: Constructor-assigned fields that should never change
- Method parameters you do not reassign: Catches accidental reassignment
- Security-critical methods: Validation, authentication, authorization logic
- Value classes: Classes that represent data (DTOs, records, value objects)
Avoid final for
- Classes designed for extension: Framework base classes, abstract classes, and classes you expect others to subclass
- Methods in libraries: Making methods
finalprevents users from customizing behavior via subclassing - Every local variable: Adding
finalto every local makes code noisy without much safety benefit in short methods
Final and Performance
The JVM's JIT compiler inlines methods aggressively regardless of final. In modern JVMs, final does not produce meaningful performance improvements for methods or classes. Use it for correctness and clarity, not speed.
For fields, final does matter: the JVM guarantees that a final field is fully initialized before any thread can access the object (Java Memory Model, JSR-133). This makes final fields safe to publish without synchronization.
Records (Java 16+) — Final by Default
Java records are implicitly final with final fields:
Records embody the principle: if data should not change, make it impossible to change.
Common Pitfalls
- Confusing final reference with final object:
final Listprevents reassigning the variable, but the list itself can still be modified. For true immutability, useCollections.unmodifiableList()orList.of(). - Final classes block mocking: Mockito cannot mock
finalclasses by default. Either avoidfinalon classes you need to mock, use interfaces, or enable Mockito's inline mock maker (mockito-inline). - Final and serialization:
finalfields complicate deserialization since they cannot be set after construction. Libraries like Jackson handle this via constructor-based deserialization, but reflection-based frameworks may struggle. - Overusing final on local variables: In a 3-line method, marking every local
finaladds visual noise without preventing real bugs. Reserve it for cases where reassignment would cause confusion. - Missing final on fields that should be immutable: Forgetting
finalon a field that is only assigned in the constructor creates a latent bug — someone might add a setter later, breaking thread safety.
Summary
- Use
finalon fields for immutability and thread safety (JMM guarantees safe publication) - Use
finalon methods to protect invariants that subclasses must not override - Use
finalon classes to prevent subclassing of value types and security-critical classes - Avoid
finalon classes and methods in libraries where extensibility matters finalreference does not mean immutable object — combine with unmodifiable collections- Java records provide
finalclasses withfinalfields by default - Use
finalfor correctness and intent, not for performance

