Implement lazy initialization in Java carefully so deferred creation does not quietly turn into race conditions or confusing runtime failures.
Lazy initialization: Deferring object construction until the object is first needed rather than creating it eagerly at startup.
The appeal is simple: do not pay for what you might never use. The risk is just as real: the moment of first use becomes a construction boundary with all the concurrency and failure semantics that come with it.
1public final class ReportRenderer {
2 private ExpensiveTemplateEngine templateEngine;
3
4 public ExpensiveTemplateEngine templateEngine() {
5 if (templateEngine == null) {
6 templateEngine = new ExpensiveTemplateEngine();
7 }
8 return templateEngine;
9 }
10}
This works only in single-threaded or tightly controlled contexts. In concurrent code, it is not safe enough.
In real Java systems, safer lazy initialization often uses:
volatileThe right choice depends on scope and lifecycle. For many cases, the holder idiom or a DI-container provider is clearer than hand-written lock choreography.
Laziness should be reviewable. Readers should be able to see:
If those answers are vague, laziness is likely just disguised uncertainty about ownership.
When reviewing lazy initialization, ask:
Lazy initialization is strongest when it protects a real cost boundary. Without that pressure, it often just delays complexity instead of reducing it.