Creating an object correctly is only part of concurrent correctness.
You also have to publish that object correctly to other threads.
If publication is unsafe, another thread can observe stale or incomplete state even if the constructor itself looked perfectly fine.
That is why safe publication is a separate topic from ordinary object construction.
Problem Statement
Suppose a background thread loads a new routing configuration and stores it in a shared field for request threads to read.
If that reference is exposed without a valid publication guarantee, request threads may observe:
- stale state
- partially established state
- an object that appears ready but is not safely visible across threads
This is a subtle bug because the constructor may still be perfectly correct.
What Safe Publication Means
Safe publication means there is a valid Java Memory Model guarantee that other threads will see the object in a properly established state after it is handed off.
This matters for:
- configuration snapshots
- shared services
- caches
- singleton instances
- immutable values handed across threads
Without safe publication, even “read-only” objects can be observed incorrectly.
Why This Change Was Added
Developers often assume:
- “the object is immutable, so I can share it however I want”
That is incomplete.
Immutability helps a lot, but the reference still needs a safe handoff path.
That is why Java has several publication mechanisms:
volatile- synchronization
- static initialization
- correctly designed final-field-based construction patterns
The problem is not only mutation. The problem is cross-thread visibility of established state.
Common Safe Publication Patterns
1. Publish through a volatile field
class ConfigHolder {
private volatile AppConfig current;
void publish(AppConfig config) {
current = config;
}
AppConfig current() {
return current;
}
}
2. Publish through synchronized access
class Holder {
private AppConfig current;
synchronized void publish(AppConfig config) {
current = config;
}
synchronized AppConfig current() {
return current;
}
}
3. Publish through static initialization
class ClientRegistry {
static final PaymentClient CLIENT = new PaymentClient();
}
4. Publish a properly constructed immutable object as part of another safely published object
These patterns differ mechanically, but they all provide a real visibility guarantee.
Runnable Example
public class SafePublicationDemo {
public static void main(String[] args) {
RoutingConfigManager manager = new RoutingConfigManager();
System.out.println(manager.current().primaryHost());
manager.reload(new RoutingConfig("payments-b.internal", "payments-c.internal"));
System.out.println(manager.current().primaryHost());
}
static final class RoutingConfig {
private final String primaryHost;
private final String fallbackHost;
RoutingConfig(String primaryHost, String fallbackHost) {
this.primaryHost = primaryHost;
this.fallbackHost = fallbackHost;
}
String primaryHost() {
return primaryHost;
}
String fallbackHost() {
return fallbackHost;
}
}
static final class RoutingConfigManager {
private volatile RoutingConfig current =
new RoutingConfig("payments-a.internal", "payments-b.internal");
RoutingConfig current() {
return current;
}
void reload(RoutingConfig newConfig) {
current = newConfig;
}
}
}
This is a strong pattern because:
- the reference is safely published
- the object itself is immutable
- readers always see whole snapshots, not partial mutation
Unsafe Patterns to Avoid
- assigning to a plain shared field with no further discipline
- leaking
thisduring construction - publishing a mutable object and then mutating it without coordination
- hiding publication inside race-prone lazy initialization
Safe publication is about both:
- the handoff mechanism
- the design of the object being handed off
Production-Style Guidance
A strong practical pattern is:
- build an immutable snapshot completely
- validate it
- publish it through a
volatilefield or another safe mechanism - let readers consume it without further locking
This works extremely well for:
- routing tables
- feature-flag snapshots
- permission snapshots
- pricing configuration
It is one of the most practical concurrency simplifications in backend systems.
Common Mistakes
- assuming constructor correctness alone is enough
- safely publishing a reference but exposing mutable internals behind it
- mixing safe publication with later unsafe mutation
- using lazy initialization patterns with no actual memory-visibility guarantee
Safe publication is not a label you attach after the fact. It is part of the object’s lifecycle design.
Key Takeaways
- Shared objects need a safe handoff path, not just a correct constructor.
volatile, synchronization, static initialization, and final-field-oriented design are common publication tools.- Immutable snapshots are especially easy to publish safely.
- Unsafe publication can break code that otherwise looks completely reasonable.
Comments