skip to content

How does initialization of a static final field (a constant) differ from an instance final field, and what is a compile-time constant?

level: seniorimportance: should knowfreq 45%

answer

  1. static final = one shared value, class-load time
  2. Set at declaration or in a static block, never a constructor
  3. Primitive/String + constant expression = compile-time constant → inlined
  4. Inlining = recompile consumers when the constant changes
  5. Break inlining with a non-constant initializer

basics

~20 s

A static final field belongs to the class, not an object, so it is set once at class load time — either at its declaration or in a static initializer block, never in a constructor. If its value is a constant expression known at compile time, the compiler may inline it directly into other code.

solid answer

~50 s

A static final field is a class-level field that is assigned exactly once when the class is initialized, not per object. You set it at its declaration or in a static initializer block, never in a constructor (constructors run per instance). If a static final field is of a primitive or String type and its initializer is a constant expression, it becomes a compile-time constant: the compiler can inline its value into every place that reads it. This has a real consequence — if you change the constant's value and recompile only that class, code compiled against the old value may still carry the inlined old value until it is recompiled. Static final fields that are not compile-time constants (e.g. initialized from a method call or a non-final value) are computed at class-load time and read normally. Instance final fields, by contrast, can vary per object and are assigned during construction.

go deeper

for a junior

Knows static final declares a shared constant set once for the class.

for a middle

Distinguishes static final (class, class-load time) from instance final (per object, constructor) and knows static finals aren't set in constructors.

for a senior

Explains compile-time constants, inlining, and the recompile-the-consumers versioning hazard, plus blank static finals via static blocks.

for a principal

Weighs API design implications of public constants (inlining/binary-compatibility), chooses constant vs. accessor, and reasons about class-initialization ordering and lazy init.

## Static vs instance - An **instance field** belongs to each object; each `new` creates its own copy. - A **static field** belongs to the **class itself** — there is one shared copy regardless of how many objects exist. Adding `final` to either makes it assign-once. But *when* and *where* differ: | | instance final | static final | |---|---|---| | Belongs to | each object | the class | | Assigned when | during construction | at class initialization (once) | | Assigned where | declaration, instance block, or constructor | declaration or **static initializer block** | | Per-object value? | yes | no — shared | A `static final` field is **never** assigned in a constructor, because constructors run per object and would violate exactly-once across many instances. ## Where you assign a static final Two places: ```java static final int A = 10; // at declaration static final int B; static { B = compute(); } // in a static initializer block ``` A blank `static final` (declared without a value) must be definitely assigned exactly once by the **static initializer**(s), analogous to a blank instance final being assigned by constructors. ## Class initialization timing Static fields and static blocks run **once**, when the class is first **initialized** (lazily, on first active use — e.g. first instance, first static access). This is the **class-load-time** sequence, distinct from per-object instance initialization. ## Compile-time constants (constant expressions) A special, important case: a field that is - `static final`, **and** - of a **primitive type or `String`**, **and** - initialized with a **constant expression** (literals and operations on other compile-time constants), is a **compile-time constant variable**. The JLS lets — and effectively requires — the compiler to **inline** its value wherever it is referenced. The reference disappears from the bytecode and is replaced by the literal value. ```java public static final int MAX = 100; // compile-time constant, inlined at use sites ``` ### The inlining hazard Because the value is copied into *consumers* at their compile time, changing the constant and recompiling **only** the defining class does **not** update consumers that were compiled earlier — they still hold the old literal. You must recompile the consumers too. This is a classic versioning gotcha for public constants in libraries. To avoid inlining (so the value is read at runtime), make it **not** a constant expression, e.g.: ```java public static final int MAX = Integer.valueOf(100); // not a constant expression → read at runtime ``` or compute it in a static block. ## Compile-time constants enable other features Because they are known at compile time, compile-time constants can be used as `case` labels in a `switch`, as annotation element values, and they participate in conditional-compilation-style dead-code elimination (`if (false)` with a constant). Non-constant `static final` fields cannot. ## Summary Static final = one shared, assign-once value set at class-init (declaration or static block), never in a constructor. If it is a primitive/String constant expression it is a compile-time constant and gets inlined into consumers — which is fast but creates a recompile-the-consumers versioning hazard. Instance finals differ: they are per-object and assigned during construction.

  • Why might a library bump a constant's value but callers still see the old one?
    Compile-time constants (static final primitive/String with a constant expression) are inlined into the callers' bytecode at their compile time. Recompiling only the library does not refresh callers; they must be recompiled to pick up the new literal.
  • How do you make a static final field NOT a compile-time constant?
    Initialize it with something that isn't a constant expression — e.g. a method call, Integer.valueOf(100), or assignment in a static block. Then it is read at runtime instead of being inlined.

saying these in an interview costs you the question

  • Trying to assign a static final field in a constructor
  • Assuming every static final is inlined (only primitive/String constant expressions are)
  • Thinking changing a public constant updates all callers without recompiling them
  • Confusing class-init timing (once) with per-object construction

context