Final Variables in Java

A final variable can be assigned exactly once. The variable is fixed; the object it refers to may still be mutable. Use final on fields to make immutable classes; on locals and parameters to document intent and prevent accidental reassignment.

Final locals

final int max = computeMax();
max = 200;                 // ❌ compile error

for (final String s : items) { process(s); }  // loop variable is fresh each iteration β€” legal

Final parameters

public void handle(final Event e) {
    e = new Event();      // ❌ cannot reassign
    e.process();           // βœ… using the object is fine
}

Final fields

public class Order {
    private final long id;
    private final List<Item> items;

    public Order(long id, List<Item> items) {
        this.id = id;
        this.items = List.copyOf(items);    // immutable snapshot
    }
}

Every field that doesn't need to change should be final. Classes built from final fields are safely shared across threads after construction.

Static final β€” constants

public static final int MAX_RETRY = 3;
public static final String BASE_URL = "https://api.example.com";

final β‰  immutable

final List<String> names = new ArrayList<>();
names.add("Alice");                 // βœ… the list is mutable
names = new ArrayList<>();          // ❌ but the variable is final

For true immutability: use List.copyOf, Collections.unmodifiableList, or records.

Effectively final β€” for lambdas and inner classes

int counter = 0;
list.forEach(s -> counter++);      // ❌ counter must be final or effectively final

// Workaround β€” use an accumulator
var sum = list.stream().mapToInt(String::length).sum();
// Or an AtomicInteger if you really need mutable shared state
var count = new AtomicInteger(0);
list.forEach(s -> count.incrementAndGet());

Style: mandatory final?

Some teams require final on every parameter and local that can be. It prevents accidental reassignment and makes intent explicit, but adds visual noise. Pick a convention and stick to it β€” IDE auto-insertion takes the tedium out.

Common mistakes

  • Treating final as immutable β€” wrap mutable collections with List.copyOf.
  • Skipping final on fields by default β€” most fields shouldn't mutate. Make immutability the default.
  • Trying to mutate captured locals β€” they must be effectively final. Use an accumulator or an array of length 1.

Related

Pillar: Variables in Java. See also final keyword, records.