The final Keyword in Java

final means cannot be reassigned, cannot be overridden or cannot be extended, depending on where it is written.

Three meanings, one keyword

Applied toMeaning
VariableMay be assigned once; cannot be reassigned
MethodCannot be overridden by a subclass
ClassCannot be extended

final variables

final int maxRetries = 3;
// maxRetries = 5;              // compile error

final List<String> names = new ArrayList<>();
names.add("Meera");             // allowed - the list is mutable
// names = new ArrayList<>();   // compile error - the reference is fixed
This is the single most misunderstood point. final freezes the variable, never the object it refers to. For an immutable object use a type such as String, List.of(...) or a record.

final fields

public class Invoice {

    private final String reference;          // assigned in the constructor
    private final LocalDate issued = LocalDate.now();   // assigned inline
    private static final double TAX = 0.18;  // a constant

    public Invoice(String reference) {
        this.reference = reference;          // must be assigned exactly once
    }
}

A final instance field must be assigned exactly once, either inline, in an instance initialiser, or in every constructor. A static final field must be assigned inline or in a static block.

final parameters and effectively final

public void process(final String input) {
    // input = input.trim();     // compile error
}

public Runnable makeTask() {
    int count = 5;               // not declared final, but never reassigned
    return () -> System.out.println(count);   // allowed: effectively final
}

Since Java 8 a lambda or an anonymous class may capture a local variable that is effectively final, meaning it is never reassigned after initialisation. Adding count++ anywhere would break the capture and produce a compile error.

final methods

public class Account {

    public final void audit() {      // subclasses cannot change this
        record();
        notifyCompliance();
    }

    protected void record() { }      // subclasses may customise this part
    protected void notifyCompliance() { }
}

Marking a method final states that its behaviour is part of the guarantee of the class. It is the right tool when overriding would break an invariant, and it is commonly used for the outer method of a template method pattern.

final classes

public final class Money { }     // cannot be extended

String, Integer and the other wrappers are final. For String that is essential: if it could be subclassed, a malicious subclass could change behaviour after a security check had passed.

final and immutability

public final class Period {

    private final LocalDate start;
    private final List<String> tags;

    public Period(LocalDate start, List<String> tags) {
        this.start = start;
        this.tags = List.copyOf(tags);      // defensive copy, immutable result
    }

    public List<String> tags() {
        return tags;                        // safe to return, it cannot change
    }
}

An immutable class needs four things: the class is final, all fields are private final, no method changes state, and mutable inputs and outputs are copied. final alone gives you only the third of the way there.

final and the memory model

final fields carry a threading guarantee: once a constructor completes without leaking this, every thread that sees the object sees its fully initialised final fields. That guarantee does not extend to non final fields, which is one more reason to prefer immutable objects in concurrent code.

Common mistakes

  • Believing final makes a collection unmodifiable.
  • Forgetting to assign a final field in one of several constructors.
  • Reassigning a captured local variable and then not understanding the lambda compile error.
  • Declaring static final on a mutable object and treating it as a constant.
  • Making everything final mechanically, including parameters, which adds noise without value.

Best practices

  • Make fields final by default and relax only when mutation is genuinely required.
  • Use static final with truly immutable values such as List.of(...).
  • Make value classes final so equals cannot be undermined by a subclass.
  • Use final methods to protect invariants rather than as a general habit.
  • Skip final on parameters unless the team has agreed on it.

Practice

  1. Explain why final List<String> l = new ArrayList<>(); l.add("x"); compiles.
  2. Write a class with two constructors where one forgets a final field, and read the error.
  3. Why does a lambda refuse to capture a variable that is incremented later?
  4. Make a class genuinely immutable and list every change required.
  5. Why is String declared final, in terms of security?

Conclusion

On a variable final stops reassignment, on a method it stops overriding, on a class it stops extension. It is a building block of immutability, not immutability by itself.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.