Final Keyword in Java: Variables, Methods, and Classes
backend
9 min read
The final keyword in Java does more than make constants. It locks down variables, prevents method overriding, and blocks inheritance — here's exactly how and when to use it.

Published By: Nelson Djalo | Date: April 17, 2026
The final keyword in Java is one of those features that sounds straightforward until you actually try to use it consistently. Most developers know it makes variables constant, but that's only a third of the story. final also prevents method overriding and stops classes from being subclassed. Understanding all three uses — and especially the gotchas around references vs. values — is what separates someone who knows the keyword from someone who knows the language.
This post covers everything you need to know about the final keyword in Java with practical code examples, common mistakes, and the reasoning behind when to use it.
When you declare a variable as final, you're saying it can only be assigned once. After the initial assignment, any attempt to reassign it will cause a compile-time error.
public class PaymentProcessor {
public static final double TAX_RATE = 0.08;
public static final int MAX_RETRIES = 3;
public static final String API_VERSION = "v2";
}
The public static final combination is Java's way of defining constants. By convention, constant names use UPPER_SNAKE_CASE. You'll see this pattern everywhere in the standard library — Integer.MAX_VALUE, Math.PI, TimeUnit.SECONDS.
final works on local variables too, not just fields. This is useful when you want to signal that a value shouldn't change after computation:
public double calculateTotal(List<Item> items) {
final double subtotal = items.stream()
.mapToDouble(Item::getPrice)
.sum();
final double tax = subtotal * TAX_RATE;
return subtotal + tax;
}
Since Java 8, local variables used inside lambdas and anonymous classes must be effectively final anyway. Marking them explicitly final just makes the intent visible.
You can declare method parameters as final to prevent reassignment within the method body:
public void processOrder(final String orderId, final int quantity) {
// orderId = "changed"; // Compile error
System.out.println("Processing " + quantity + " items for order " + orderId);
}
Some teams enforce this as a code style rule. Others find it noisy. Either way, it catches accidental parameter reassignment, which is a real source of bugs in longer methods.
A blank final is a final variable declared without an initializer. You must assign it exactly once before it's used — typically in the constructor:
public class DatabaseConnection {
private final String url;
private final int port;
public DatabaseConnection(String url, int port) {
this.url = url;
this.port = port;
}
// No setter for url or port — they're locked after construction
}
This is the backbone of immutable object design in Java. The compiler enforces that every constructor path assigns the blank final fields exactly once. If you forget, it won't compile.
Marking a method as final prevents subclasses from overriding it. The method's behavior is locked in.
public class Account {
private double balance;
public final double getBalance() {
return balance;
}
public void withdraw(double amount) {
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
balance -= amount;
}
}
Here, getBalance() is final because the account class guarantees it returns the actual balance. A subclass can override withdraw to add custom validation, but it can't redefine what "get the balance" means.
Use final methods when:
public abstract class AbstractValidator {
// Template method - final so subclasses can't break the flow
public final ValidationResult validate(Object input) {
if (input == null) {
return ValidationResult.error("Input cannot be null");
}
return doValidate(input);
}
// Subclasses implement this
protected abstract ValidationResult doValidate(Object input);
}
The null check always runs. No subclass can skip it. That's the value of a final method.
A final class cannot be extended at all. No subclass, no overriding, nothing.
public final class Config {
private final String environment;
private final int maxConnections;
public Config(String environment, int maxConnections) {
this.environment = environment;
this.maxConnections = maxConnections;
}
public String getEnvironment() {
return environment;
}
public int getMaxConnections() {
return maxConnections;
}
}
The most famous final class in Java is String. You can't extend String, and for good reason — Strings are used as keys in HashMaps, passed around security managers, and cached in the string pool. Allowing subclasses could break all of that.
Other final classes in the JDK: Integer, Double, Boolean, and all the wrapper types. Also Math, System, and LocalDate.
Make a class final when:
If you're writing application code (not a library), making classes final by default is a solid habit. You can always remove final later if you need inheritance. Adding it after the fact is harder because existing subclasses will break.
This is where developers trip up the most. When you declare a reference as final, the reference itself can't change — but the object it points to is still mutable.
final List<String> names = new ArrayList<>();
names.add("Alice"); // Fine - modifying the list contents
names.add("Bob"); // Fine
// names = new ArrayList<>(); // Compile error - can't reassign the reference
final locks the pointer, not the data. If you want an actually immutable list, you need to go further:
final List<String> names = List.of("Alice", "Bob", "Charlie");
// names.add("Dave"); // UnsupportedOperationException at runtime
The same applies to maps, sets, arrays, and any other mutable object:
final int[] scores = {90, 85, 78};
scores[0] = 100; // Fine - array contents are mutable
// scores = new int[]{1, 2, 3}; // Compile error - can't reassign
final Map<String, Integer> cache = new HashMap<>();
cache.put("key", 42); // Fine - map contents are mutable
// cache = new HashMap<>(); // Compile error
If you need a truly immutable collection, use Collections.unmodifiableList(), Map.of(), List.of(), or a library like Guava's ImmutableList.
As shown above, final only prevents reassignment. It does not make the referenced object immutable. This is the single most common misconception about the final keyword in Java.
Every blank final must be assigned in every constructor. Miss one path and the compiler rejects it:
public class Service {
private final String name;
public Service(String name) {
this.name = name; // OK
}
public Service() {
// Compile error: name might not have been initialized
}
}
Some developers go overboard and mark every single local variable as final. While technically correct, it clutters the code without adding much value for short methods where the scope is obvious. Use it where it communicates intent or prevents real bugs.
final and static are independent concepts. static means class-level. final means no reassignment. You can have any combination:
static int counter = 0; // Class-level, reassignable
final int id; // Instance-level, assigned once
static final String VERSION = "1.0"; // Class-level constant
If you want to understand static in depth, check out Static in Java.
The JVM can sometimes optimize final variables and methods more aggressively. A final method can be inlined by the JIT compiler since it knows no subclass will override it. A final field's value can be cached because it won't change.
That said, modern JVMs are extremely good at detecting these patterns on their own through speculative optimization. Don't use final for performance — use it for correctness and readability. The performance benefit is real but marginal, and it should never be your primary motivation.
Since Java 8, the concept of effectively final exists. A variable is effectively final if it's never reassigned after initialization, even if you don't write the final keyword:
String name = "Alice"; // effectively final
Runnable r = () -> System.out.println(name); // works
The compiler treats effectively final variables the same as explicitly final ones for lambda and anonymous class purposes. The difference is purely syntactic — explicitly writing final documents your intent for human readers.
If an interviewer asks about the final keyword in Java, cover three things: final variables (can't be reassigned), final methods (can't be overridden), and final classes (can't be subclassed). Then mention the reference trap — that final doesn't make objects immutable. That last point is what separates a good answer from a great one.
The final keyword serves three roles in Java: it prevents variable reassignment, blocks method overriding, and stops class inheritance. The most important nuance is that final on a reference variable locks the reference, not the object — a final List can still have items added to it.
Use final for constants (static final), for immutable object design (blank finals assigned in constructors), for locking down critical methods in class hierarchies, and for sealing classes that should never be extended. Avoid it as decoration on every single variable — use it where it communicates something meaningful.
Want to build your Java fundamentals on solid ground? Start with Java for Beginners, where we cover these core language features through hands-on projects. You might also want to read The 'this' Keyword in Java and Static in Java to complete your understanding of Java's essential keywords.

Skip the generic recommendations. These 9 books changed how I write code, lead teams, and think about systems - from Clean Code to books most devs haven't heard of.

The exact skills, tools, and learning order to go from zero to hired as a Java full stack developer. Covers Spring Boot, React, databases, Docker, and what employers actually look for.

Abstract classes in Java are one of the most misunderstood OOP concepts. Here's a practical guide with real-world examples, code you can actually use, and the mistakes most devs make.
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.