Java
final modifier
programming best practices
software development
Java programming

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.

java
1final int maxRetries = 3;
2// maxRetries = 5;  // Compile error: cannot assign a value to final variable
3
4final List<String> items = new ArrayList<>();
5items.add("a");    // OK — the list contents can change
6// items = new ArrayList<>();  // Compile error — the reference cannot change

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:

java
1public class Connection {
2    private final String url;
3    private final int timeout;
4
5    public Connection(String url, int timeout) {
6        this.url = url;           // Assigned once
7        this.timeout = timeout;   // Assigned once
8    }
9    // No setters — object is immutable after construction
10}

This pattern creates immutable objects: once constructed, the state never changes. Immutable objects are inherently thread-safe.

Final Local Variables and Parameters

java
1public void process(final String input) {
2    final int length = input.length();
3    // input = "other";  // Compile error
4    // length = 0;       // Compile error
5
6    // Required for use in anonymous classes (pre-Java 8)
7    Runnable r = new Runnable() {
8        public void run() {
9            System.out.println(input);   // Works because input is final
10            System.out.println(length);  // Works because length is final
11        }
12    };
13}

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:

java
1public class Payment {
2    public final void validate() {
3        // Critical validation logic that subclasses must not bypass
4        if (amount <= 0) throw new IllegalArgumentException("Invalid amount");
5    }
6
7    public void process() {
8        // Subclasses can customize this
9        validate();
10        executePayment();
11    }
12}
13
14public class CreditCardPayment extends Payment {
15    // @Override public void validate() {}  // Compile error — validate is final
16
17    @Override
18    public void process() {
19        // Can override process, but validate is always called
20        super.process();
21        sendReceipt();
22    }
23}

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
1public final class ImmutablePoint {
2    private final double x;
3    private final double y;
4
5    public ImmutablePoint(double x, double y) {
6        this.x = x;
7        this.y = y;
8    }
9
10    public double getX() { return x; }
11    public double getY() { return y; }
12}
13
14// class MutablePoint extends ImmutablePoint {}  // Compile error

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 final fields with UPPER_SNAKE_CASE names
  • 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)
java
1// Good: constants
2public static final int MAX_CONNECTIONS = 100;
3public static final String DEFAULT_CHARSET = "UTF-8";
4
5// Good: immutable fields
6private final Logger logger = LoggerFactory.getLogger(getClass());
7private final ExecutorService executor;
8
9// Good: method parameter
10public String format(final String input) {
11    // Accidentally writing input = input.trim() is caught
12    return input.trim().toUpperCase();
13}

Avoid final for

  • Classes designed for extension: Framework base classes, abstract classes, and classes you expect others to subclass
  • Methods in libraries: Making methods final prevents users from customizing behavior via subclassing
  • Every local variable: Adding final to every local makes code noisy without much safety benefit in short methods
java
1// Bad: prevents legitimate subclassing
2public final class AbstractRepository { }  // Users cannot create CustomRepository
3
4// Bad: prevents mocking in tests
5public final class EmailService { }  // Cannot create MockEmailService extends EmailService

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.

java
1// Thread-safe without volatile or synchronized
2public class Config {
3    private final Map<String, String> settings;
4
5    public Config(Map<String, String> settings) {
6        this.settings = Collections.unmodifiableMap(new HashMap<>(settings));
7    }
8
9    // Any thread can safely read settings after construction
10    public String get(String key) { return settings.get(key); }
11}

Records (Java 16+) — Final by Default

Java records are implicitly final with final fields:

java
public record Point(double x, double y) { }
// Equivalent to a final class with final fields, constructor, equals, hashCode, toString

Records embody the principle: if data should not change, make it impossible to change.

Common Pitfalls

  • Confusing final reference with final object: final List prevents reassigning the variable, but the list itself can still be modified. For true immutability, use Collections.unmodifiableList() or List.of().
  • Final classes block mocking: Mockito cannot mock final classes by default. Either avoid final on classes you need to mock, use interfaces, or enable Mockito's inline mock maker (mockito-inline).
  • Final and serialization: final fields 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 final adds visual noise without preventing real bugs. Reserve it for cases where reassignment would cause confusion.
  • Missing final on fields that should be immutable: Forgetting final on a field that is only assigned in the constructor creates a latent bug — someone might add a setter later, breaking thread safety.

Summary

  • Use final on fields for immutability and thread safety (JMM guarantees safe publication)
  • Use final on methods to protect invariants that subclasses must not override
  • Use final on classes to prevent subclassing of value types and security-critical classes
  • Avoid final on classes and methods in libraries where extensibility matters
  • final reference does not mean immutable object — combine with unmodifiable collections
  • Java records provide final classes with final fields by default
  • Use final for correctness and intent, not for performance

Course illustration
Course illustration

All Rights Reserved.