Static and Final Keywords

July 1, 20265 min readUpdated 8/20/2026

Two small keywords that get confused with each other constantly. They are unrelated: static means "belongs to the class, not to an object", and final means "cannot be reassigned". Neither implies the other.

static fields: one copy, shared

class Counter {
    static int totalCreated = 0;         // ONE of these exists, ever
    int instanceNumber;                  // one per object

    Counter() {
        totalCreated++;
        instanceNumber = totalCreated;
    }
}

class Demo {
    void run() {
        Counter a = new Counter();
        Counter b = new Counter();

        System.out.println(a.instanceNumber);     // 1
        System.out.println(b.instanceNumber);     // 2
        System.out.println(Counter.totalCreated); // 2 — shared by both
    }
}

Access a static field through the class name — Counter.totalCreated. Java lets you write a.totalCreated, but it is misleading and most linters flag it, because it looks like per-object data and is not.

static methods

class MathUtils {
    static int square(int n) {           // works entirely from its arguments
        return n * n;
    }
}

class Demo {
    void run() {
        System.out.println(MathUtils.square(4));   // 16 — no object needed
    }
}

The rule for whether a method should be static is simple: does it use any instance state? If it only works from its parameters, make it static and say so. If it reads or writes a field, it must be an instance method.

A static method cannot touch instance fields or call instance methods, because there is no object for it to use — this is why main being static is the source of the beginner error "non-static variable cannot be referenced from a static context":

class Program {
    int count = 5;

    static void broken() {
        // System.out.println(count);    // does not compile: which object's count?
    }

    static void works() {
        Program p = new Program();
        System.out.println(p.count);     // 5 — an object to read it from
    }
}

Static blocks and initialisation order

class Config {
    static final Map<String, String> DEFAULTS;

    static {                             // runs ONCE, when the class is first loaded
        DEFAULTS = new HashMap<>();
        DEFAULTS.put("region", "us-west-2");
        DEFAULTS.put("retries", "3");
    }
}

A static block is for initialisation that takes more than one expression. It runs once, on first use of the class, before any object exists. Keep it small — an exception thrown from a static block surfaces as ExceptionInInitializerError, which is a genuinely unpleasant thing to debug.

final variables

final int maxRetries = 3;
// maxRetries = 5;                       // does not compile: cannot assign a value to final variable

final List<String> names = new ArrayList<>();
names.add("Folau");                      // fine! The LIST is not final, the REFERENCE is
System.out.println(names);               // [Folau]

// names = new ArrayList<>();            // this is what final forbids

This is the single most misunderstood thing about final. It freezes the variable, not the object. A final reference to a mutable object gives you no immutability at all — the name will always point at that list, and the list can change freely.

For a genuinely unchangeable collection, you need an immutable one:

final List<String> fixed = List.of("a", "b");
// fixed.add("c");                       // throws UnsupportedOperationException at runtime
System.out.println(fixed.size());        // 2

final fields

class Account {
    private final String id;              // must be set exactly once
    private double balance;               // free to change

    Account(String id, double balance) {
        this.id = id;                     // the constructor is the last chance
        this.balance = balance;
    }
}

A final field must be assigned by the end of every constructor, and never again. This is worth doing by default: a field that cannot change is one fewer thing to reason about, it is automatically thread-safe, and the compiler enforces it rather than a comment.

A good habit is to make every field final and remove the keyword only where you have a concrete reason. Most fields turn out not to need to change.

static final — constants

class HttpClientConfig {
    static final int TIMEOUT_SECONDS = 30;
    static final String USER_AGENT = "lovemesomecoding/1.0";

    // A mutable object as a "constant" is a trap — the reference is fixed, the contents are not
    static final List<String> SAFE = List.of("GET", "HEAD");
}

static final together means one shared value that never changes — a constant. By convention these are named in UPPER_SNAKE_CASE, and it is the one place in Java where that casing is used.

Note the comment on the last field. static final on a mutable collection is a classic mistake: it looks like a constant and is globally writable by anything that can see it. Use List.of, Set.of or Map.of.

final classes and methods

Here is the pattern in full, from the console bank app this site uses for examples — a final class with a private constructor and only static members:

public final class Money {

    public static final BigDecimal ZERO = of("0");

    /** Two decimal places, the way currency is written. */
    private static final int SCALE = 2;

    private Money() {
        // Utility class: nothing to construct.
    }

    public static BigDecimal of(String value) {
        return new BigDecimal(value).setScale(SCALE, RoundingMode.HALF_UP);
    }

    public static boolean isPositive(BigDecimal value) {
        return value.compareTo(BigDecimal.ZERO) > 0;
    }
}

Every keyword there is doing a job. final on the class means nobody can subclass it. The private constructor means nobody can instantiate it — together they say "this is a bag of static helpers" in a way the compiler enforces. static final SCALE is a constant used by the methods; public static final ZERO is one callers use.

class Service {
    final void audit() {                  // subclasses cannot override this
        System.out.println("audited");
    }
}

String, Integer and the other wrappers are all final — their immutability guarantees would be worthless if you could subclass them and break the rules.

Mark a method final when overriding it would break something the class depends on, such as a security or auditing step.

Effectively final

One last piece, because it explains an error message you will meet with lambdas. A local variable that is never reassigned is effectively final, and only such variables can be captured by a lambda or anonymous class:

void demo() {
    String prefix = "Order ";                     // never reassigned -> effectively final
    List<String> ids = List.of("1", "2");

    ids.forEach(id -> System.out.println(prefix + id));   // fine

    int count = 0;
    // ids.forEach(id -> count++);                // "local variables referenced from a lambda
                                                  //  expression must be final or effectively final"
}

The fix is almost never to fight the rule — it is to use the right tool, such as a stream's count() or a collector, rather than mutating a counter from inside a lambda.

Next

Packages is next — how Java organises classes into namespaces, and the four access modifiers that decide who can see what.