Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java static and final: storage, ownership, and constants

Last updated: 28 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

static associates a member with a class rather than a particular instance; final prevents reassignment of a variable or further extension of a method or class, depending on where it is applied.

Separate storage from mutability

A static field is shared by uses of that class in its defining class loader. An instance field belongs to one object. Neither choice automatically supplies a lock, a memory barrier, or protection from competing writers.

A final list reference cannot be reassigned after its permitted initialization. The list itself can still change. An immutable view blocks mutation through that view, but does not stop a separate owner from changing the backing collection.

The configuration below copies the caller’s list before wrapping it. The copy removes structural sharing with the caller. Because the elements are strings, element mutation is not a concern in this example; replacing strings in the original list also does not change the copied list.

Constants can outlive a rebuild

A static final primitive or String initialized with a compile-time constant expression can be inlined into client bytecode. Recompiling only the library after changing that value can leave an already compiled client using the previous value.

Use a method or runtime configuration when a value must change without rebuilding its consumers. A timeout loaded from configuration is not the same contract as a fixed protocol constant.

Final classes and methods constrain inheritance. They do not imply that every field is immutable, and final alone does not turn a mutable object into a value key. Review equality fields separately.

Working program

Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class NotificationConfiguration {
    static final int MAX_ATTEMPTS = 3;
    static final class Settings {
        private final List<String> channels;
        Settings(List<String> channels) {
            this.channels = Collections.unmodifiableList(new ArrayList<>(channels));
        }
        List<String> channels() { return channels; }
    }
    public static void main(String[] args) {
        List<String> requested = new ArrayList<>(Arrays.asList("email"));
        Settings settings = new Settings(requested);
        requested.add("sms");
        System.out.println(settings.channels());
        System.out.println(MAX_ATTEMPTS);
    }
}

Output

Output
[email]
3

Cost and design choices

Creating the snapshot costs O(n) time and O(n) reference storage for n channels. Wrapping the copy with an unmodifiable view adds a small wrapper rather than another full copy.

A static collection remains reachable while its class and field remain reachable. Treat a growing static map as retained application memory, not as temporary request state.

Final field initialization has specific publication guarantees, but sharing an incompletely constructed object or mutating referenced objects still needs care. For mutable cross-thread state, define a synchronisation policy.

Common Mistakes

  • Do not equate final references with immutable objects.
  • Do not wrap a caller’s mutable list and call it a detached snapshot.
  • Do not use static fields as unbounded per-request storage.

Connect the contracts

Compare the boundary explained in State boundaries with the assumptions made by this program.

java
static-final
Storage details