Definition
final has three meanings depending on where it is applied:
- final variable: Assigned exactly once. After assignment, it cannot be reassigned. For reference types, the reference is fixed but the object's internal state can still change.
- final method: Cannot be overridden by subclasses. The implementation is locked.
- final class: Cannot be extended. No subclass can be created.
String,Integer, andMathare all final classes.
final is like a bank seal. A final variable is like a sealed envelope: once the document is sealed inside, you cannot swap it for a different document, but you can still read and annotate the document inside. A final method is like a sealed procedure: no branch can customize it. A final class is like a sealed charter: no one can create a derived version of the bank.
Understanding the Concepts
What does final mean on a variable?
A final variable can be assigned exactly once. After that, it cannot be reassigned.
final String accountId = "SAV2001";
// accountId = "CUR3001"; // COMPILE ERROR: cannot assign to final variable
final int maxRetries = 3;
// maxRetries = 5; // COMPILE ERROR
Does final make the object immutable?
No. final fixes the reference, not the object's contents.
final List<String> transactions = new ArrayList<>();
transactions.add("TX001"); // OK: modifying the list's content
transactions.add("TX002"); // OK: still the same list reference
// transactions = new ArrayList<>(); // COMPILE ERROR: cannot reassign the reference
final Account rajesh = new Account("SAV2001", new BigDecimal("1000000"));
rajesh.debit(new BigDecimal("50000")); // OK: the object's state changes
// rajesh = new Account("CUR3001", BigDecimal.ZERO); // COMPILE ERROR: cannot reassign
The reference rajesh always points to the same Account object, but that object's balance can still change.
What is a blank final?
A blank final is a final field declared without an initializer. It must be assigned in every constructor.
public class Account {
private final String accountId; // blank final: no value yet
private final BigDecimal balance;
public Account(String accountId, BigDecimal balance) {
this.accountId = accountId; // must assign here
this.balance = balance; // must assign here
}
// Every constructor must assign all blank finals
public Account(String accountId) {
this.accountId = accountId;
this.balance = BigDecimal.ZERO; // must still assign balance
}
}
If any constructor does not assign a blank final, the code does not compile.
What does final mean on a method?
A final method cannot be overridden by subclasses.
public class Account {
// This method cannot be overridden – no subclass can change it
public final String getAccountId() {
return accountId;
}
}
public class SavingsAccount extends Account {
// @Override
// public String getAccountId() { ... } // COMPILE ERROR: cannot override final method
}
Use final methods when the logic must not change in subclasses (security checks, ID generation, audit logging).
What does final mean on a class?
A final class cannot be extended. No subclass can be created.
public final class TransactionId {
private final String value;
public TransactionId(String value) {
this.value = Objects.requireNonNull(value);
}
public String getValue() { return value; }
}
// public class SpecialTransactionId extends TransactionId { } // COMPILE ERROR
JDK examples: String, Integer, Long, Math, System are all final classes.
Why? Because they are immutable or utility classes that should never be subclassed.
What does final mean on a method parameter?
public void transfer(final Account from, final Account to, final BigDecimal amount) {
// Cannot reassign the parameters
// from = anotherAccount; // COMPILE ERROR
// amount = amount.add(fee); // COMPILE ERROR
// But can still modify the object's state
from.debit(amount); // OK
to.credit(amount); // OK
}
final parameters prevent accidental reassignment inside the method. Some teams require this style for clarity.
Why is final important for thread safety?
final fields are guaranteed to be visible to all threads after the constructor finishes, without any synchronization.
public class ImmutableConfig {
private final String dbUrl;
private final int maxConnections;
public ImmutableConfig(String dbUrl, int maxConnections) {
this.dbUrl = dbUrl;
this.maxConnections = maxConnections;
}
// These fields are safely published to all threads
// No synchronized, no volatile needed
}
This is the Java Memory Model guarantee. It is why immutable objects with final fields are inherently thread-safe.
What about effectively final (Java 8+)?
A variable that is not declared final but is never reassigned is "effectively final." Lambdas require captured variables to be effectively final.
int rate = 5; // not declared final, but never reassigned → effectively final
accounts.forEach(account -> {
System.out.println(account.getBalance().multiply(BigDecimal.valueOf(rate)));
// rate is captured because it is effectively final
});
// rate = 10; // if you uncomment this, the lambda above stops compiling
What are common mistakes?
- Thinking final makes objects immutable. final fixes the reference, not the contents. A final List can still be modified.
- Forgetting to assign blank finals in every constructor. If any constructor path misses a blank final, compilation fails.
- Using final class when you meant final method. A final class prevents all subclassing. A final method prevents only that specific method from being overridden.
- Confusing final with const. Java does not have const. final applies to variables (not values), and for references it only prevents reassignment.
What should a senior engineer know?
- Records are implicitly final. All fields in a record are
private final, and the record class itself is final. - Sealed classes (Java 17+) are a more flexible alternative to final: you control which classes can extend yours.
- final enables JIT optimization. The JIT compiler can inline final methods and devirtualize calls because it knows the method cannot be overridden.
- Defensive copying. A final field holding a mutable object (like Date or List) should return a copy in the getter to prevent external mutation:
return new ArrayList<>(this.items); - final and serialization. final fields can still be set by deserialization via reflection, which is how
ObjectInputStreampopulates immutable objects.
Visual Overview
final on:
Variable: assigned once, cannot be reassigned
reference fixed, object contents can change
Method: cannot be overridden by subclasses
Class: cannot be extended (no subclasses)
Parameter: cannot be reassigned inside the method
Thread safety:
final fields are safely published after constructor
→ no volatile or synchronized needed for immutable objects
Effectively final (Java 8+):
not declared final but never reassigned → can be captured by lambdas
Key Takeaway
The final keyword means "cannot be changed after assignment." A final variable is assigned once and cannot be reassigned. A final method cannot be overridden. A final class cannot be extended.
The critical subtlety is that final on a reference variable does not make the object immutable. It fixes the reference so you cannot point it to a different object, but the object's internal state can still change. A final List can still have elements added and removed.
In banking, final is essential for immutability and thread safety. A final field is guaranteed to be visible to all threads after the constructor completes, without synchronization. This is why immutable value objects like transaction IDs and configuration records use final fields.
One sentence to remember: final means assigned once – a final variable cannot be reassigned, a final method cannot be overridden, a final class cannot be extended, but a final reference does not make the object immutable.
---