00:00

final Keyword in Java – Variables, Methods and Classes

Last updated: 7 min read

The final keyword means "cannot be changed". It can be applied to variables (which then cannot be reassigned), methods (which cannot be overridden) and classes (which cannot be extended). Used well, final makes code safer, clearer and easier to reason about, especially in multithreaded programs.

This tutorial covers every use of final, including blank finals, constants, effectively final variables in lambdas, and how final helps build immutable objects.

1. final at a Glance

final keyword applied to variables, methods and classes
Applied toMeaningExample
Local variable or parameterCan be assigned only oncefinal double gst = 0.18;
FieldMust be assigned exactly once, by the end of constructionprivate final String id;
static final fieldA class-level constantstatic final int MAX = 3;
MethodCannot be overridden by subclassesfinal double fee(...)
ClassCannot be extendedfinal class Currency

2. final Variables and Constants

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

public class FinalVariables {
    static final int MAX_LOGIN_ATTEMPTS = 3;          // constant: static final
    private final String id;                           // final field: assigned exactly once

    FinalVariables(String id) {
        this.id = id;                                  // blank final assigned in the constructor
    }

    public static void main(String[] args) {
        final double gst = 0.18;                       // final local variable
        // gst = 0.12;                                 // compile error

        final int discount;                            // blank final local: assigned once later
        discount = args.length > 0 ? 20 : 10;
        System.out.println("discount = " + discount + ", gst = " + gst + ", max attempts = " + MAX_LOGIN_ATTEMPTS);

        // final reference: the list object can still change
        final List<String> cart = new ArrayList<>();
        cart.add("pen");
        cart.add("book");
        // cart = new ArrayList<>();                   // compile error: cannot reassign
        System.out.println("cart = " + cart);

        System.out.println("id = " + new FinalVariables("USR-42").id);
    }
}
Output
discount = 10, gst = 0.18, max attempts = 3
cart = [pen, book]
id = USR-42
  • A blank final is declared without a value and assigned exactly once later, in every code path (for fields: in every constructor or an initialiser).
  • Constants are static final fields with values known at compile time, named in UPPER_SNAKE_CASE.
  • A final reference cannot point to another object, but the object itself may still change, as the cart list shows. final does not make objects immutable on its own.

3. final Methods and final Classes

A final method protects behaviour that subclasses must not change; a final class cannot have subclasses at all:

FinalMethodsClasses.java
public class FinalMethodsClasses {
    static class Payment {
        // final: subclasses must not change how the fee is calculated
        final double fee(double amount) { return amount * 0.02; }
        String type() { return "generic"; }
    }

    static class CardPayment extends Payment {
        @Override String type() { return "card"; }    // allowed
        // @Override double fee(double amount) { ... } // compile error: cannot override final method
    }

    static final class Currency {                     // final class: cannot be extended
        final String code;
        Currency(String code) { this.code = code; }
    }
    // static class Crypto extends Currency { }       // compile error: cannot inherit from final

    public static void main(String[] args) {
        Payment p = new CardPayment();
        System.out.println(p.type() + " payment fee on 1000 = " + p.fee(1000));
        System.out.println("currency = " + new Currency("INR").code);
        System.out.println("String is final: " + java.lang.reflect.Modifier.isFinal(String.class.getModifiers()));
    }
}
Output
card payment fee on 1000 = 20.0
currency = INR
String is final: true

Many core classes are final, including String, Integer and the other wrappers, and LocalDate. This guarantees that a String always behaves like a String, which is important for security and for using strings as map keys. Since Java 17, sealed classes offer a middle ground: extension limited to listed subclasses.

4. Effectively Final Variables

Lambdas and anonymous classes can use local variables from the enclosing method only if those variables are final or effectively final, meaning they are never reassigned after initialisation, even without the keyword:

EffectivelyFinal.java
import java.util.List;

public class EffectivelyFinal {
    public static void main(String[] args) {
        String prefix = "Item: ";            // never reassigned: "effectively final"
        List.of("pen", "cup").forEach(item -> System.out.println(prefix + item));

        int counter = 0;
        // List.of("a").forEach(x -> counter++);   // compile error: counter is not effectively final
        for (String s : List.of("a", "b", "c")) counter++;   // use a normal loop instead
        System.out.println("counter = " + counter);
    }
}
Output
Item: pen
Item: cup
counter = 3

This rule exists because lambdas may run later, possibly on another thread, so they capture a copy of the value, which must not change.

5. Using final to Build Immutable Objects

An immutable object cannot change after it is created. final is a key ingredient:

ImmutableClass.java
import java.util.List;

public class ImmutableClass {
    // final class + private final fields + no setters + defensive copy = immutable
    static final class Order {
        private final String id;
        private final List<String> items;

        Order(String id, List<String> items) {
            this.id = id;
            this.items = List.copyOf(items);           // copy: the caller's list cannot change us
        }

        String id() { return id; }
        List<String> items() { return items; }          // already unmodifiable

        Order withItem(String item) {                   // "modify" by creating a new object
            List<String> more = new java.util.ArrayList<>(items);
            more.add(item);
            return new Order(id, more);
        }
    }

    public static void main(String[] args) {
        Order o1 = new Order("A1", List.of("pen"));
        Order o2 = o1.withItem("book");
        System.out.println(o1.id() + " " + o1.items());
        System.out.println(o2.id() + " " + o2.items());
    }
}
Output
A1 [pen]
A1 [pen, book]
  • Make the class final so subclasses cannot add mutable behaviour.
  • Make all fields private final.
  • Provide no setters; return new objects for "changes".
  • Defensively copy mutable inputs and never expose mutable internals.

Records follow most of these rules automatically. Immutable objects are thread-safe and can be shared freely. Final fields also have a special guarantee in the Java Memory Model: once a constructor finishes, other threads see their correct values.

6. Rules and Notes

  • An uninitialised final field causes a compile error ("variable might not have been initialized").
  • Final static fields can be assigned in a static initializer block.
  • Constructors cannot be final; abstract and final cannot be combined on a method or class.
  • private methods are implicitly final, because they cannot be overridden.
  • Final parameters cannot be reassigned inside the method.

7. Summary

  • final variables can be assigned once; static final fields are constants.
  • final methods cannot be overridden; final classes cannot be extended.
  • A final reference fixes the reference, not the object's contents.
  • Lambdas can capture only final or effectively final local variables.
  • final fields are central to immutable, thread-safe classes.

Related Tutorials