Skip to content
Java polymorphism 6 min read

final Keyword

The final keyword is Java’s way of saying “this cannot change.” You can apply it to variables, methods, and classes, and each context carries a slightly different meaning — but the underlying idea is always the same: protection from modification.

final Variables

When you declare a variable as final, you can assign it a value exactly once. Any attempt to reassign it afterwards causes a compile-time error.

public class Circle {
    final double PI = 3.14159;

    void calculate(double radius) {
        // PI = 3.14; // compile error: cannot assign a value to final variable PI
        System.out.println("Area: " + PI * radius * radius);
    }
}

Output:

Area: 78.53975

Constants — static final

The most common real-world use of final is declaring class-level constants. Pair it with static so the constant belongs to the class rather than each instance:

public class MathConstants {
    public static final double PI = 3.14159265358979;
    public static final int MAX_RETRIES = 3;
}

class App {
    public static void main(String[] args) {
        System.out.println(MathConstants.PI);      // 3.14159265358979
        System.out.println(MathConstants.MAX_RETRIES); // 3
    }
}

Tip: By convention, static final constants are written in UPPER_SNAKE_CASE.

Blank final Variables

You don’t have to assign a final variable at declaration. A blank final variable can be assigned later — but only once, and it must be assigned before it’s used. For instance fields, the assignment must happen in every constructor.

public class User {
    final String username; // blank final

    public User(String username) {
        this.username = username; // assigned exactly once here
    }

    public static void main(String[] args) {
        User u = new User("alice");
        System.out.println(u.username); // alice
        // u.username = "bob"; // compile error
    }
}

Output:

alice

This pattern is very useful for immutable data objects — once constructed, the object’s identity fields can never change.

final Parameters

You can also mark a method parameter as final, which prevents reassigning it inside the method body:

public class Formatter {
    public String greet(final String name) {
        // name = name.trim(); // compile error
        return "Hello, " + name + "!";
    }
}

Note: Marking parameters final is optional style. It does not affect how arguments are passed — Java is always call by value.

Effectively Final (Java 8+)

From Java 8 onwards, a variable that is never reassigned is treated as effectively final, even without the keyword. This matters for lambda expressions and anonymous inner classes, which can only capture effectively-final local variables:

import java.util.List;

public class Demo {
    public static void main(String[] args) {
        int multiplier = 5; // effectively final — never reassigned
        List.of(1, 2, 3).forEach(n -> System.out.println(n * multiplier));
    }
}

Output:

5
10
15

final Methods

Marking a method final prevents any subclass from overriding it. The method can still be inherited and called — it just cannot be replaced.

public class Vehicle {
    public final void startEngine() {
        System.out.println("Engine started.");
    }
}

class Car extends Vehicle {
    // @Override
    // public void startEngine() { } // compile error: cannot override final method
}

class App {
    public static void main(String[] args) {
        Car car = new Car();
        car.startEngine(); // inherited, not overridden
    }
}

Output:

Engine started.

Use final methods when the behavior is part of a security guarantee, a contract that subclasses should not alter, or a performance-sensitive path (see the Under the Hood section below).

final Classes

A final class cannot be subclassed at all. You cannot write class MyString extends String because String is declared final.

public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }
}

// class ExtendedPoint extends ImmutablePoint { } // compile error

Common examples of final classes in the JDK:

  • java.lang.String
  • java.lang.Integer (and all wrapper classes)
  • java.lang.Math

Warning: Making a class final is a strong API commitment. Once published as final, you can never un-final it without breaking subclasses that your users might have written in the meantime. Think carefully before using it in public libraries.

Quick Reference

ContextWhat final prevents
Local variableReassignment after first assignment
Instance fieldReassignment after constructor completes
static fieldReassignment after static initializer
Method parameterReassignment inside the method body
MethodOverriding in subclasses
ClassAny form of subclassing

Under the Hood

Constant Folding for Primitive Finals

When you declare a static final primitive (or String literal), the Java compiler performs constant folding — it inlines the value directly at every use site. Open the bytecode with the javap tool and you will not see a field reference; you will see the literal value baked into the instruction stream. This makes reads zero-cost at runtime.

public class Config {
    public static final int TIMEOUT = 30;

    public static void main(String[] args) {
        int t = TIMEOUT; // bytecode: bipush 30 (no field lookup)
        System.out.println(t);
    }
}

final Methods and JIT Inlining

The JVM’s JIT compiler can inline method calls to avoid the overhead of a virtual dispatch. For non-final methods the JIT must check whether a subclass has overridden the method before inlining (a technique called speculative inlining with deoptimization guards). A final method gives the JIT a static guarantee — no subclass can override it — so inlining is unconditional and cheaper. In hot loops this can matter measurably.

Note: Modern JITs are very good at speculative inlining, so the performance gap between final and non-final methods is often negligible. Prefer final for correctness and design clarity, not purely as a micro-optimisation.

final Fields and the Java Memory Model

The Java Memory Model gives final fields a special visibility guarantee: once a constructor completes, all threads are guaranteed to see the correctly initialised values of the object’s final fields — without needing any explicit synchronisation. This is why immutable objects built from final fields are inherently thread-safe.

public final class Point {
    final int x;
    final int y;

    Point(int x, int y) { this.x = x; this.y = y; }
    // Safe to share across threads without synchronization
}

Relationship to Immutability

final on a field only prevents reassignment of the reference (or primitive value). If the field holds a mutable object, the object’s internal state can still change:

import java.util.ArrayList;
import java.util.List;

public class Demo {
    final List<String> names = new ArrayList<>();

    public static void main(String[] args) {
        Demo d = new Demo();
        d.names.add("Alice"); // allowed — mutating the object, not the reference
        // d.names = new ArrayList<>(); // compile error — reassigning the reference
        System.out.println(d.names);
    }
}

Output:

[Alice]

For true immutability you need to combine final fields with immutable (or defensively-copied) objects. See Create an Immutable Class for the full pattern.

  • Inheritancefinal classes and methods directly control what subclasses can and cannot do.
  • Method Overriding — understand what final methods block and why that matters.
  • Abstract Class — the opposite of final: abstract classes are designed to be subclassed.
  • Immutable Class — how final fields combine with other techniques to build truly immutable objects.
  • Java Memory Model — the visibility guarantees that final fields carry in a multithreaded environment.
  • Static Keywordstatic final together define class-level constants.
Last updated June 13, 2026
Was this helpful?