How do final instance fields interact with the Java Memory Model to provide safe publication, and what design rule does this imply for immutable objects?
answer
- final fields → JMM initialization safety / freeze at constructor end
- Other threads see fully initialized finals without synchronization
- Only if 'this' does not escape during construction
- Non-final fields need volatile/locks to publish safely
- Foundation of the immutable-object pattern
basics
~20 sThe Java Memory Model gives a special guarantee: if you set a final field during construction and don't let the object escape before the constructor finishes, any other thread that later sees the object is guaranteed to see the correctly initialized final field, even without locks or volatile.
solid answer
~50 sJava's Memory Model gives final fields a special initialization-safety guarantee. When a constructor finishes, there is a 'freeze' of its final fields: any thread that obtains a reference to the fully constructed object through a normal (even unsynchronized) read is guaranteed to see the correctly initialized values of those final fields, and of anything reachable from them at construction time. This is why a properly constructed immutable object — all fields final, no mutable state, and crucially no 'this' reference escaping during construction — can be shared safely across threads with no synchronization. The catch is the escape rule: if the constructor leaks 'this' (e.g. registers a listener, starts a thread, or stores itself in a shared field) before completing, another thread might see the object before the freeze, voiding the guarantee. Non-final fields get no such guarantee and require volatile, locking, or other safe-publication mechanisms to be visible. This is the foundation of using immutability for lock-free concurrency.
go deeper
May know that immutable objects are 'thread-safe' without the memory-model detail.
Knows final relates to immutability and that immutable objects are easier to share between threads.
Explains the final-field initialization-safety guarantee and that it requires no this-escape during construction.
Designs concurrency strategies around final-field safe publication, reasons about transitive reachability, this-escape hazards, defensive copying, and when volatile/locks are still required.
## Background: why visibility is even a problem In a multithreaded program, one thread writing a field does **not** automatically mean another thread will *see* that write. The **Java Memory Model (JMM)** is the part of the spec that defines when writes by one thread become visible to reads by another. Without proper synchronization, a thread can observe a **partially constructed** object — for example, a reference that is non-null but whose fields still hold default values (0/null) — because the compiler/CPU may reorder the field writes relative to the publication of the reference. **Safe publication** means making an object visible to other threads in a way that guarantees they see it fully and correctly initialized. ## The final-field guarantee The JMM grants `final` fields a special rule, sometimes called **initialization safety** or the **final-field freeze**: > When a constructor completes normally, the JMM inserts a conceptual *freeze* of the object's final fields. Any thread that reads a reference to that object — even through an ordinary, unsynchronized field — is guaranteed to see at least the values the final fields had at the freeze, **and** the values (at construction time) of everything those final fields transitively reference. In plain terms: **correctly constructed objects with final fields can be safely read by other threads without any synchronization**, with respect to those final fields. This is unusual — normally you need `volatile`, `synchronized`, or another happens-before edge to publish data safely. final fields are the one mechanism that bakes safe publication into construction itself. ## The critical precondition: do not let `this` escape The guarantee only holds if the object is **not made visible to other threads before its constructor finishes**. If the constructor lets the `this` reference *escape* early, another thread may grab the object **before** the freeze and see uninitialized finals. Ways `this` escapes during construction: - Storing `this` in a static or shared field. - Passing `this` to a method that publishes it (e.g. registering an event listener, adding to a shared collection). - Starting a thread that captures `this` from inside the constructor. - Calling an overridable method that does any of the above. So the design rule is: **finish constructing before publishing; never leak `this` from a constructor.** ## Why non-final fields are different Non-final fields receive **no** initialization-safety guarantee. If you publish an object with non-final fields via a data race (e.g. assigning to an ordinary shared field), readers may see stale defaults. To publish such state safely you must use `volatile`, `final`, locks, or other happens-before machinery. ## The immutable-object design pattern This directly motivates the canonical **immutable object** recipe for lock-free sharing: 1. Make **all fields `final`**. 2. Make the object's state **not mutable** after construction (no setters; defensively copy mutable inputs/outputs). 3. Ensure **`this` does not escape** during construction. 4. The class is then effectively immutable and can be **freely shared across threads without synchronization**. ```java public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } // no this escape public int x() { return x; } public int y() { return y; } } ``` A `Point` constructed on one thread and handed to others via any means is guaranteed to be seen fully initialized. ## Subtleties worth knowing - The guarantee covers what the final fields **transitively reference at construction time**, so a `final` field pointing at an array gives visibility of the array's *contents written during construction* — but later mutations of that array are not covered (which is why you also need true immutability). - This is precisely why `String`, the boxed primitives, and other JDK immutables are safe to share without locks. - It is the reason the double-checked-locking idiom for immutable holders can work when the held field is final, whereas with non-final fields you must use `volatile`. ## Summary final fields give the JMM's strongest construction guarantee: a fully constructed object's final fields (and what they reference at construction) are safely visible to any thread that later obtains the reference, with no synchronization — provided `this` never escaped during construction. This underwrites the immutable-object pattern as a first-class concurrency tool.
- What breaks the final-field safe-publication guarantee?Letting the this reference escape before the constructor completes — e.g. registering a listener, starting a thread that captures this, or storing this in a shared field. Then another thread can see the object before the final-field freeze and observe uninitialized values.
- Do non-final fields get any visibility guarantee from construction?No. Non-final fields require explicit safe publication — volatile, synchronization, or another happens-before edge. Only final fields get the JMM's initialization-safety freeze.
saying these in an interview costs you the question
- Claiming all fields (not just final) get visibility guarantees for free
- Letting 'this' escape in the constructor and still expecting the guarantee
- Thinking final makes a referenced mutable object thread-safe for later mutations
- Confusing this guarantee with ordering of non-final writes