The singleton design pattern restricts a class to having only one instance and provides a global point of access to that single instance. To learn more about the singleton pattern, please refer to Singleton design pattern and Singleton class in Java. In this tutorial, we are going to learn how we can break Singleton pattern in Java and methods to prevent their breaking.
We are going to use the following singleton class example, where we are creating a class in Java as Book, making its constructor private, and providing a method to return the singleton instance of the class.
package com.paulsofts.data;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In getBook() method, first null check is first lock
* after that inside synchronized block, second null
* check represents the second lock
*
* 4. In getBook() method which is the global point of
* access to singleton object we are checking if
* reference variable contains null then initialize
* otherwise returns the previously created object
*
* @author paulsofts
*/
public class Book {
private static Book book;
private Book() {
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
}
Now, we write a Main class where we are going to call the instance of the Book class and verify the Singleton pattern for the book class.
package com.paulsofts.main;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) {
Book book_obj_1 = Book.getBook();
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Book book_obj_2 = Book.getBook();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
As we can see, we are getting the same hash code for both the objects, and hence only one single object is getting created.
Different ways to break singleton pattern in Java
We can break singleton patterns in various ways, and we are going to learn the most common methods to break singleton patterns in Java.
How to break Singleton pattern in Java using the Reflection API in Java
The Java Reflection API is used for examining or modifying the behaviour of classes at runtime. The reflection API in Java is present in the java.lang.reflect package. To learn more, please refer to the Reflection API.
In order to break the singleton pattern, first we load the Book class inside our Main class by calling Book.class. Now, we call the getDeclaredConstructor() method on this, which returns the declared constructor of the Book class. As it returns the constructor of the Book class, we are going to store it inside the Constructor<T> of the Book type member, which is present in the java.lang.reflect package.
The constructor declared in the Book class is a private constructor; we need to set its accessibility to true. So, we are calling setAccessible(boolean b) method on it. After this, we will call the newInstance() method on the member variable of type Constructor<Book>, which creates a new object of the Book class.
package com.paulsofts.main;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. we load Book class and call getDeclaredConstructors() method
* which will return the constructor declared in Book class,
* we store it inside a Constructors<T> type and call newInstance()
* method which will create an object of Book class
*
* 3. make sure to set the accessibility to be true, other wise
* it will give an error about private constructor
*
* 4. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Book book_obj_1 = Book.getBook();
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Constructor<Book> constructor = Book.class.getDeclaredConstructor();
constructor.setAccessible(true);
Book book_obj_2 = constructor.newInstance();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
Once we run our application, we can see we are getting different hash codes, and hence we have broken the singleton pattern using the Reflection API.
Prevent Singleton pattern from Reflection API
We can prevent the Singleton pattern from Reflection API in following ways:
1. Using the Runtime Exception
In this implementation, we check if the object of the singleton class already exists, and if someone tries to create another object, we throw a Runtime exception saying the object already exists.
package com.paulsofts.data;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In our private constructor, we are checking
* the global reference variable if it is not null
* and still someone tries to call the constructor
* we throw a Runtime exception
*
* @author paulsofts
*/
public class Book {
private static Book book;
private Book() {
if(book != null) {
throw new RuntimeException("Object already exists, you are tryting to break the singlton pattern");
}
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
}
Now, in our Main class, we try to create the two objects of our Book class with the help of the Reflection API.
package com.paulsofts.main;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. we load Book class and call getDeclaredConstructors() method
* which will return the constructor declared in Book class,
* we store it inside a Constructors<T> type and call newInstance()
* method which will create an object of Book class
*
* 3. make sure to set the accessibility to be true, other wise
* it will give an error about private constructor
*
* 4. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Book book_obj_1 = Book.getBook();
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Constructor<Book> constructor = Book.class.getDeclaredConstructor();
constructor.setAccessible(true);
Book book_obj_2 = constructor.newInstance();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
As we run our Main class, we see that the first object gets created, and as soon as we tries to create the second object, an exception occurs.
2. Using the Enum in Java
The Java enums are internally initialized only once, and hence we can also use enum to prevent singleton classes from using the Reflection API. In this approach, we do not require any private constructor or any method to return the sole instance of a singleton class.
Instead, the class that we want to make singleton, we declare it as an enum type and declare a member variable inside it, that represents its instance.
package com.paulsofts.data;
/**
* 1. We want to make Book class as singleton
* class, we declare it as of enum type
*
* 2. After that, declare a member variable book,
* which represents it's instance
*
* @author paulsofts
*/
public enum Book {
book;
}
Once we have declared our class as an enum type, We try to create two objects in our Book class.
package com.paulsofts.main;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import com.paulsofts.data.Book;
/**
* 1. We are calling the reference variable
* "book" of Book class
*
* 2. 2. we load Book class and call getDeclaredConstructors() method
* which will return the constructor declared in Book class,
* we store it inside a Constructors<T> type and call newInstance()
* method which will create an object of Book class
*
* 3. make sure to set the accessibility to be true, other wise
* it will give an error about private constructor
*
* 4. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Book book_obj_1 = Book.book;
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Constructor<Book> constructor = Book.class.getDeclaredConstructor();
constructor.setAccessible(true);
Book book_obj_2 = constructor.newInstance();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
As we can see, the first object gets created and accessed. After that, as we try to create a new object using the Reflection API, we get a NoSuchMethodException because the Reflection API uses constructor to create objects, and we do not have any constructor declared in our Book class.
How to break Singleton pattern in Java using Serialization and Deserialization in Java
Serialization in Java is the process of converting the state of an object into a byte stream, which is then used to send the stream data over a network or save it inside a file. The reverse of serialization is de-serialization, where the byte stream is used to get the actual Java object.
We can use serialization and de-serialization to break singleton pattern in Java, as if we serialize an object and then de-serialize it, this will create a new object, and hence the singleton pattern is broken. To serialize any class we need to make sure that class implements Serializable interface. In our example, we make our Book class to implement Serializable interface.
package com.paulsofts.data;
import java.io.Serializable;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In getBook() method, first null check is first lock
* after that inside synchronized block, second null
* check represents the second lock
*
* 4. In getBook() method which is the global point of
* access to singleton object we are checking if
* reference variable contains null then initialize
* otherwise returns the previously created object
*
* 5. We need to make our Book class to implement Serializable
* interface in order to serialize it
*
* @author paulsofts
*/
public class Book implements Serializable {
private static Book book;
private Book() {
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
}
After we implement the Serializable interface in our Book class, we serialize and de-serialize its object. First, we call the singleton object and store it in a required type reference variable. After that, we use ObjectOutputStream to write the singleton object to a file, and our serialization is complete. Moving forward, we de-serialize our object. For this, we use ObjectInputStream and read the object, typecast it to the required class type, and store it inside another required type reference variable. Now, we check the hash code for both serialize and de-serialize objects.
package com.paulsofts.main;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* 3. First we serialize the object and then we de-serialize it
* and checks their hash code
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Book book_obj_1 = Book.getBook();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream("paulsofts.txt"));
objectOutputStream.writeObject(book_obj_1);
System.out.println("Serialization completed...");
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream("paulsofts.txt"));
Book book_obj_2 = (Book) objectInputStream.readObject();
System.out.println("De-serialization completed...");
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
Below, we can see we get different hash codes after serialization and de-serialization, and hence the singleton pattern is broken.
Prevent Singleton pattern from Serialization
readResolve method in Java
The readResolve() method is called when ObjectInputStream reads an object from the stream and is preparing to return it to the caller. To learn more, please refer to the readResolve method in Java.
In order to prevent the singleton pattern from being serialized, we need to implement the readResolve() method, which ensures that when an object is read from the ObjectInputStream, it replaces that object with the singleton instance and no new instance is created during serialization and de-serialization.
package com.paulsofts.data;
import java.io.Serializable;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In getBook() method, first null check is first lock
* after that inside synchronized block, second null
* check represents the second lock
*
* 4. In getBook() method which is the global point of
* access to singleton object we are checking if
* reference variable contains null then initialize
* otherwise returns the previously created object
*
* 5. We need to make our Book class to implement Serializable
* interface in order to serialize it.
*
* 6. We have implemented readResolve method which return
* the singleton instance our Book class
*
* @author paulsofts
*/
public class Book implements Serializable {
private static Book book;
private Book() {
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
public Object readResolve() {
return book;
}
}
Above, we have implemented the readResolve method, which returns the singleton instance of our Book class.
package com.paulsofts.main;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* 3. First we serialize the object and then we de-serialize it
* and checks their hash code
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Book book_obj_1 = Book.getBook();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream("paulsofts.txt"));
objectOutputStream.writeObject(book_obj_1);
System.out.println("Serialization completed...");
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream("paulsofts.txt"));
Book book_obj_2 = (Book) objectInputStream.readObject();
System.out.println("De-serialization completed...");
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
As we can see, we have the same hash code for both the objects after serialization and de-serialization.
How to break Singleton pattern in Java using object cloning
In Java, object cloning is the process of creating a copy of an object. Object cloning can be used to break the singleton pattern. If we clone a singleton object, we create its copy, and two instances of the singleton class get created, so the singleton pattern is broken.
package com.paulsofts.data;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In getBook() method, first null check is first lock
* after that inside synchronized block, second null
* check represents the second lock
*
* 4. In getBook() method which is the global point of
* access to singleton object we are checking if
* reference variable contains null then initialize
* otherwise returns the previously created object
*
* 5. In order to clone, we need to implement Cloneable
* interface and override clone() method.
*
* @author paulsofts
*/
public class Book implements Cloneable {
private static Book book;
private Book() {
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
In our Main class, first we create a singleton instance, and after that, we clone that instance by calling the clone method on it.
package com.paulsofts.main;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* 3. First, we call the singleton instance and after this
* we are cloning the singleton instance and creating
* its copy
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Book book_obj_1 = Book.getBook();
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Book book_obj_2 = (Book) book_obj_1.clone();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
Prevent Singleton pattern from object cloning
To prevent the singleton pattern from object cloning, while overriding the clone() method, we need to either throw an exception or return the singleton instance. So, if anyone tries to clone the singleton object, they will either get an error or receive the sole singleton instance.
package com.paulsofts.data;
/**
* 1. Declare a static reference variable of Book class
* the default value of book is null
*
* 2. Make the constructor private, this will hide
* Book's constructor from other classes
*
* 3. In getBook() method, first null check is first lock
* after that inside synchronized block, second null
* check represents the second lock
*
* 4. In getBook() method which is the global point of
* access to singleton object we are checking if
* reference variable contains null then initialize
* otherwise returns the previously created object
*
* 5. In order to clone, we need to implement Cloneable
* interface and override clone() method and return
* and return the singleton instance
*
* @author paulsofts
*/
public class Book implements Cloneable {
private static Book book;
private Book() {
}
public static Book getBook() {
if(book == null) {
synchronized(Book.class) {
if(book == null) {
book = new Book();
}
}
}
if(book == null) {
book = new Book();
}
return book;
}
@Override
public Object clone() throws CloneNotSupportedException {
return book;
}
}
Now, if we run our Main class while cloning the singleton object, we will receive the same singleton instance, and this way we prevent the singleton pattern from object cloning.
package com.paulsofts.main;
import com.paulsofts.data.Book;
/**
* 1. we will call the static method getBook()
* using class-name
*
* 2. HashCode is a unique number assigned to every object
* we are calling hashCode() method on objects to check
* the uniqueness of object
*
* 3. First, we call the singleton instance and after this
* we are cloning the singleton instance and creating
* its copy
*
* @author paulsofts
*
*/
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Book book_obj_1 = Book.getBook();
System.out.println("Hashcode for 1st obj: " + book_obj_1.hashCode());
Book book_obj_2 = (Book) book_obj_1.clone();
System.out.println("Hashcode for 2nd obj: " + book_obj_2.hashCode());
}
}
We can see that we are getting the same hash code for both objects.