

The Initialization-on-Demand Holder Idiom is a secure and efficient way to initialize static fields on demand in multithreading applications. It prevents subtle race conditions without impacting performance through complete synchronization of all accesses.
In this article you will find out:
- What is the motivation for the Initialization-on-Demand Holder Idiom?
- Why is complete synchronization with
synchronized
not optimal? - How is the Initialization-on-Demand Holder Idiom implemented in Java?
- What alternatives are there?
Motivation
For time-consuming initialization processes, it’s often advisable to perform them only when actually needed. For example, it might make sense to initialize a logger that establishes a connection to a database only when something is logged for the first time. This “Lazy Initialization” prevents unnecessary delays during program startup.
In a single-thread environment, the implementation is straightforward:
public class UserService {
private static Logger logger;
private static Logger getLogger() {
if (logger == null) {
logger = initializeLogger();
}
return logger;
}
public void createUser(User user) {
// . . .
getLogger().info("User created");
}
// . . .
}
Code language: Java (java)
The first call to getLogger()
initializes the logger and stores it in the static field logger
. Subsequent calls directly return the stored object.
However, this implementation is not thread-safe.
In a multithreading application, various subtle effects can occur here, which can lead to race conditions that are difficult to reproduce and thus hard to fix:
- With nearly simultaneous first calls to the
getLogger()
method from two threads, both threads could see thelogger
field asnull
, causing the logger to be initialized multiple times. - Due to thread caching effects, a thread could still see the
logger
field asnull
even if another thread has already assigned it. - Due to instruction reordering, a thread could see the
logger
field as notnull
, but it could point to aLogger
object that is not yet fully initialized.
I have described these three effects in detail in the article about the Double-Checked Locking Idiom.
In multithreading applications, we must therefore synchronize access to the logger
field through appropriate measures.
Solution 1: Complete Synchronization
The simplest approach is complete synchronization of the getLogger()
method using synchronized
(or alternatively an explicit lock):
// ↓
private synchronized static Logger getLogger() {
if (logger == null) {
logger = initializeLogger();
}
return logger;
}
Code language: Java (java)
However, this implementation leads to significant performance losses because:
- every call requires the administrative overhead of complete synchronization,
- threads must wait during parallel accesses,
- entry and exit from the
synchronized
block trigger complete cache-main memory synchronizations.
Due to this significant overhead, this solution is not optimal, especially for frequently called methods.
Solution 2: Double-Checked Locking
Another possible solution is the Double-Checked Locking mentioned above. I have explained this in detail in the linked article.
A correctly implemented Double-Checked Locking solves the performance problems mentioned above, but it is quite complicated and thus error-prone in implementation.
Solution 3: Initialization-on-Demand Holder
The third solution is the Initialization-on-Demand Holder Idiom. This also solves the performance problems mentioned above. It is easier to implement than Double-Checked Locking and thus less error-prone – but it only works with static fields, not with instance fields.
And this is how it is implemented:
public class UserService {
private static class LoggerHolder {
private static final Logger LOGGER = initializeLogger();
}
public void registerUser(User user) {
// . . .
LoggerHolder.LOGGER.info("User created");
}
// . . .
}
Code language: Java (java)
Here, the Logger
object is stored in the static LOGGER
field of the inner class LoggerHolder
.
But doesn’t this initialize the logger at program startup? Didn’t we want to avoid exactly that?
No, because the JVM only loads and initializes a class when it is needed.
How does the Initialization-on-Demand Holder Idiom ensure Lazy Initialization?
When the JVM (Java Virtual Machine) loads the UserService
class, it does not automatically load the LoggerHolder
class with it. It only loads the class when LoggerHolder.LOGGER
is accessed for the first time at runtime.
Here’s a small demo program you can try out:
public class InitializationOnDemandHolderIdiomDemo {
private static class LoggerHolder {
private static final Logger LOGGER = initializeLogger();
private static Logger initializeLogger() {
System.out.println(">>>>>>>>>> Initializing logger <<<<<<<<<<");
return Logger.getLogger(InitializationOnDemandHolderIdiomDemo.class.getName());
}
}
public static void main(String[] args) {
InitializationOnDemandHolderIdiomDemo demo =
new InitializationOnDemandHolderIdiomDemo();
demo.doSomethingWithoutLogging();
demo.doSomethingWithoutLogging();
demo.doSomethingWithoutLogging();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
}
private void doSomethingWithoutLogging() {
System.out.println("Not logging");
}
private void doSomethingWithLogging() {
System.out.println("\nI'm going to log something...");
LoggerHolder.LOGGER.info("Some log message");
System.out.println("Logged something");
}
}
Code language: Java (java)
You will see the following output:
Not logging
Not logging
Not logging
I'm going to log something...
>>>>>>>>>> Initializing logger <<<<<<<<<<
Logged something
I'm going to log something...
Logged something
I'm going to log something...
Logged something
Code language: plaintext (plaintext)
You see: The logger is only initialized – and only then – when it is needed for the first time. With this, we can check off the requirement for “Lazy Initialization”.
And what about thread safety?
How does the Initialization-on-Demand Holder Idiom guarantee thread safety?
Thread safety is automatically guaranteed by the JVM (Java Virtual Machine) when loading and initializing classes.
This means that if the first access to LoggerHolder.LOGGER
occurs simultaneously by two threads, the JVM ensures that LoggerHolder.LOGGER
is initialized only once – and also that both threads see the fully initialized Logger
object.
That almost sounds too good to be true...
Disadvantage of the Initialization-on-Demand Holder Idiom
As with almost everything, this solution also has a drawback:
If the call to the initializeLogger()
method should fail during the first access to LoggerHolder.LOGGER
, subsequent accesses will not attempt to call initializeLogger()
again. No – once the initialization of a class has failed, the JVM will not try to initialize the class again. Instead, every subsequent access to LoggerHolder.LOGGER
will immediately lead to a NoClassDefFoundError
.
Here is a small program that demonstrates this behavior:
public class InitializationOnDemandHolderIdiomErrorDemo {
private static class LoggerHolder {
private static final Logger LOGGER = initializeLogger();
private static Logger initializeLogger() {
System.out.println(">>>>>>>>>> Initializing logger <<<<<<<<<<");
throw new RuntimeException("Initialization failed");
}
}
public static void main(String[] args) {
InitializationOnDemandHolderIdiomErrorDemo demo =
new InitializationOnDemandHolderIdiomErrorDemo();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
}
private void doSomethingWithLogging() {
try {
System.out.println("\nI'm going to log something...");
LoggerHolder.LOGGER.info("I did something smart");
System.out.println("Logged something");
} catch (Throwable t) {
System.out.println(">>>>>>>>>> " + t.getClass().getName() + " <<<<<<<<<<");
}
}
}
Code language: Java (java)
The program outputs the following:
I'm going to log something...
>>>>>>>>>> Initializing logger <<<<<<<<<<
>>>>>>>>>> java.lang.ExceptionInInitializerError <<<<<<<<<<
I'm going to log something...
>>>>>>>>>> java.lang.NoClassDefFoundError <<<<<<<<<<
I'm going to log something...
>>>>>>>>>> java.lang.NoClassDefFoundError <<<<<<<<<<
Code language: plaintext (plaintext)
You see: Only on the first access to LoggerHolder.LOGGER
is initializeLogger()
called. All subsequent accesses lead directly to a NoClassDefFoundError
.
With the other solutions – complete synchronization and double-checked locking – each subsequent call to the getLogger()
method would attempt to initialize the Logger
object again.
Here’s a corresponding demo for the variant with complete synchronization:
public class LazyInitializationErrorDemo {
private static Logger logger;
private static Logger getLogger() {
if (logger == null) {
logger = initializeLogger();
}
return logger;
}
private static Logger initializeLogger() {
System.out.println(">>>>>>>>>> Initializing logger <<<<<<<<<<");
throw new RuntimeException("Initialization failed");
}
public static void main(String[] args) {
LazyInitializationErrorDemo demo = new LazyInitializationErrorDemo();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
demo.doSomethingWithLogging();
}
private void doSomethingWithLogging() {
try {
System.out.println("\nI'm going to log something...");
getLogger().info("I did something smart");
System.out.println("Logged something");
} catch (Throwable t) {
System.out.println(">>>>>>>>>> " + t.getClass().getName() + " <<<<<<<<<<");
}
}
}
Code language: Java (java)
And here’s the output of the program:
I'm going to log something...
>>>>>>>>>> Initializing logger <<<<<<<<<<
>>>>>>>>>> java.lang.RuntimeException <<<<<<<<<<
I'm going to log something...
>>>>>>>>>> Initializing logger <<<<<<<<<<
>>>>>>>>>> java.lang.RuntimeException <<<<<<<<<<
I'm going to log something...
>>>>>>>>>> Initializing logger <<<<<<<<<<
>>>>>>>>>> java.lang.RuntimeException <<<<<<<<<<
Code language: plaintext (plaintext)
Here, with each call to getLogger()
, there’s another attempt to initialize the logger.
Whether this is desired or not, of course, depends on the requirements.
Upcoming Alternative: Stable Values
The JDK developers are also aware that the existing solutions are not optimal. Therefore, a new feature is currently being worked on: Stable Values.
Stable Values will be introduced in the upcoming Java 25 as a preview version. A Stable Value is a container that encapsulates the thread-safe initialization of shared values behind a simple API.
Conclusion
To initialize fields in multithreading applications only when needed, we can use the Double-Checked Locking Idiom or – for static fields – the Initialization-on-Demand Holder Idiom described in this article.
Both variants are more performant than complete synchronization of the access method with synchronized
or an explicit lock.
However, both variants are also complicated to implement – and this can easily lead to errors – especially since a faulty implementation is only revealed by race conditions, and therefore usually not immediately, but potentially only after weeks or months.
Work is currently underway on a performant and at the same time easy-to-implement variant: Stable Values will be introduced as a preview version in Java 25.
Has this article helped you? Then I would be very grateful for a rating on ProvenExpert. Your feedback inspires me to write new informative articles and helps me to continuously improve my content.
👉 Rate now
Would you like to be informed about new articles on HappyCoders.eu? Sign up for the HappyCoders.eu newsletter for regular updates on new posts.