Why do Java singletons still feel like a 90s relic?
Tired of that volatile field, the double if-null dance, the synchronized block that screams ‘I know concurrency, but barely’? Yeah. JEP 526 in Java 26 — LazyConstant — finally gives you a native, thread-safe way to init once, lazily. No hacks. No boilerplate. Just bliss.
It’s a class, java.lang.LazyConstant. Supplies a value on first get(). Immutable ref, initialized at most once. Thread-safe by JVM magic. The old singleton pattern? Retired. Or it should be.
O problema que ela resolve é antigo: campos final garantem imutabilidade, mas exigem inicialização imediata. Campos não-final permitem inicialização tardia, mas perdem as garantias de concorrência. LazyConstant junta os dois mundos.
Spot on. Finals force eager init — bad for expensive objects like HTTP clients or metric registries. Non-finals? Race conditions galore. LazyConstant? Best of both. Supplier in, constant out.
Look, we’ve all written this garbage:
public class MetricRegistry { private static volatile MetricRegistry instance; public static MetricRegistry getInstance() { if (instance == null) { synchronized (MetricRegistry.class) { if (instance == null) { instance = new MetricRegistry(); } } } return instance; } }
Ugly. Error-prone. Newbies trip over it. The static inner class holder pattern? Better, but still a workaround. LazyConstant? LazyConstant.of(MetricRegistry::new).get(). Done.
Why Does Double-Checked Locking Suck So Much?
Short answer: verbosity. Cognitive load. One slip — forget volatile — boom, races. It’s been production poison for decades. Thousands of codebases riddled with it, not from love, but necessity.
Java 26 flips the script. Payment service with a pricey HttpClient? private final LazyConstant<HttpClient> httpClient = LazyConstant.of(() -> HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build());. First charge() call creates it. Thereafter? JVM constant-folds it like a final static. No sync. No volatiles. Pure.
And lists, maps get lazy love too. List.ofLazy(10, _ -> new OrderProcessor()). Each slot independent. Map.ofLazy(Set.of("payments", "refunds"), key -> RateLimiter.create(...)). “payments” slot lazy-inits sans touching “refunds”. Genius for modular services.
But here’s my unique gripe — or insight, if you’re polite: this is Java playing catch-up to Rust’s lazy_static crate from 2015. Rust devs laughed at our DCL hacks while sipping lazy singletons. Java 26? Better late than never. Prediction: within a year, GitHub Java repos drop DCL commits by 80%. OpenJDK’s PR spin calls it ‘first-class abstraction’ — nah, it’s just fixing a 25-year wart.
Is Java 26’s LazyConstant Production-Ready?
Preview. Second preview, actually. From Java 25’s ‘Stable Values’ (JEP 502), simplified. No more low-level setOrThrow nonsense. Just .of(supplier). Clean.
Run it? --enable-preview. Compile same. Fine for toying, but prod? Wait for final. Not Serializable — serializes your grandma’s cookies before this. Value immutable ref only; innards mutable if you screw up.
When to use: expensive, optional, shared objects. DB pools. Config caches. HTTP clients. Skip for simple finals or precise timing control — it inits pre-first-get, not on-your-schedule.
Finals demand constructor-time init. Painful for deps not ready, or startup-killers. LazyConstant defers. Post-init, JVM optimizes like final. Win.
Dry humor time: imagine your app booting a 2GB ML model for a maybe-called endpoint. Final? Startup kaboom. Lazy? Snoozes till needed. Common sense, finally baked in.
Critique the hype. OpenJDK says it ‘treats the pattern as first-class’. True, but why 2026? Concurrency primitives lag forever. Remember Project Loom? Virtual threads previewed to death. LazyConstant better not suffer same.
Wander a sec: in microservices, lazy init slashes cold starts. Serverless? Lambda wakes sans heavy init. Bold call — this quietly revolutionizes Java cloud costs. (Shh, don’t tell AWS.)
Old codebases? Refactor surgically. Singletons first. Greenfield? Mandate it.
Not perfect. No custom init triggers. No reset. One-way door. Good — prevents abuse.
Why Does This Matter for Java Devs Right Now?
DCL’s death frees brain cycles. Less bugs. Cleaner diffs. Onboarding easier. ‘What’s volatile? Why two checks?’ Gone.
Historical parallel: like enums killing switch hell in Java 5. Or lambdas axing anon classes in 8. LazyConstant? Concurrency’s enum moment.
Punchy truth: if you’re still DCL-ing in 2026, your code reviewer’s mocking you. Privately.
Use cases explode. Game servers: lazy asset loaders. Finance: on-demand rate limiters. IoT: deferred sensor calibrators.
Edge: not for primitives. Generics only. Supplier throws? Propagates on get(). Handle or bust.
FAQ time.
🧬 Related Insights
- Read more: GDPR Article 6: The Web Scraping Checklist Devs Ignore at Their Peril
- Read more: Category Theory’s Types Fix Set Theory’s Fatal Flaw
Frequently Asked Questions
What are Java 26 Lazy Constants?
LazyConstant from JEP 526: thread-safe, one-time lazy init container. Replaces DCL singletons.
Does LazyConstant replace the singleton pattern?
For lazy, shared instances? Yes. Thread-safe, no boilerplate. Eager? Stick to finals.
Is JEP 526 stable in Java 26?
Second preview. Enable with –enable-preview. Final soon — direction solid.