How to brake singleton pattern and prevent ways

Serdar A.
4 min readMar 11, 2024

--

Hello everyone;

(source code : https://github.com/serdaralkancode/cracksingletonpattern)

How to brake singleton pattern and how to prevent ways. In this article, I want to show you these ways.

There are 3 ways.

  1. Brake via Reflection API
public class MyObject {

private MyObject() {};

public static final MyObject instance = new MyObject();


}

public class ReflectionTest {

public static void main(String[] args) {

MyObject ins1 = MyObject.instance;
MyObject ins2 = null;

Constructor[] constructors = MyObject.class.getDeclaredConstructors();

for(Constructor constructor : constructors)
{
constructor.setAccessible(true);
try
{
ins2 = (MyObject) constructor.newInstance();
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

//different hashcode
System.out.println("ins1 hashcode :" + ins1.hashCode());
System.out.println("ins2 hashcode :" + ins2.hashCode());
}
}

different hashcode on output

ins1 hashcode :1705736037
ins2 hashcode :558638686
  • Prevent Singleton from Reflection

Solution : throw an exception from private constructor

public class MyObject2 {

private MyObject2() {

if(MyObject2.instance != null)
{
//add this exception
try {
throw new InstantiationException("creating is not allowed");
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}

};

public static final MyObject2 instance = new MyObject2();
}
public class ReflectionTest2 {

public static void main(String[] args) {

MyObject2 ins1 = MyObject2.instance;
MyObject2 ins2 = null;

Constructor[] constructors = MyObject2.class.getDeclaredConstructors();

for(Constructor constructor : constructors)
{
constructor.setAccessible(true);
try
{
ins2 = (MyObject2) constructor.newInstance();
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

System.out.println("ins1 hashcode :" + ins1.hashCode());
System.out.println("ins2 hashcode :" + ins2.hashCode());
}
}

output is :

Exception in thread "main" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at tech1.prevent.ReflectionTest2.main(ReflectionTest2.java:25)
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at tech1.prevent.ReflectionTest2.main(ReflectionTest2.java:22)
Caused by: java.lang.RuntimeException: java.lang.InstantiationException: creating is not allowed
at tech1.prevent.MyObject2.<init>(MyObject2.java:13)
... 6 more
Caused by: java.lang.InstantiationException: creating is not allowed
at tech1.prevent.MyObject2.<init>(MyObject2.java:11)
... 6 more

2. Brake via Serialization and DeSerialization

public class MySerializableObject implements Serializable {

private static final long serialVersionUID = -1110l;

private MySerializableObject() {};

public static final MySerializableObject instance = new MySerializableObject();
}
public class SerializeDeserializeTest {

public static void main(String[] args) {

MySerializableObject ins1 = MySerializableObject.instance;
MySerializableObject ins2 = null;

String file = "C://temp/myObject.ser";

try
{
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
outputStream.writeObject(ins1);

fos.close();
outputStream.close();

FileInputStream fis = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);

ins2 = (MySerializableObject) objectInputStream.readObject();

fis.close();
objectInputStream.close();

} catch (IOException | ClassNotFoundException e) {
System.out.println("error" + e.getMessage());
}

//diffrent hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}

different hashcode on output:

ins1 hashcode 883049899
ins2 hashcode 396180261
  • Prevent Singleton from Serialization and Deserialization

Solution : override readResolve method

public class MySerializableObject2 implements Serializable {

private static final long serialVersionUID = -11102;

private MySerializableObject2() {};

public static final MySerializableObject2 instance = new MySerializableObject2();

protected Object readResolve()
{
System.out.println("readResolve method is called");
return instance;
}
}
public class SerializeDeserializeTest2 {

public static void main(String[] args) {

MySerializableObject2 ins1 = MySerializableObject2.instance;
MySerializableObject2 ins2 = null;

String file = "C://temp/myObject2.ser";

try
{
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
outputStream.writeObject(ins1);

fos.close();
outputStream.close();

FileInputStream fis = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);

ins2 = (MySerializableObject2) objectInputStream.readObject();

fis.close();
objectInputStream.close();

} catch (IOException | ClassNotFoundException e) {
System.out.println("error" + e.getMessage());
}

//same hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}

output is :

readResolve method is called
ins1 hashcode 883049899
ins2 hashcode 883049899

3. Brake via Clonable

public class MyClonableParentObject implements Cloneable {

int val = 10;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class MySingletonObject extends MyClonableParentObject{

public static final MySingletonObject instance = new MySingletonObject();

private MySingletonObject() {};

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class ClonableTest {

public static void main(String[] args) throws CloneNotSupportedException {

MySingletonObject ins1 = MySingletonObject.instance;
MySingletonObject ins2 = (MySingletonObject) ins1.clone();

//different hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}

different hashcode on output:

ins1 hashcode 455659002
ins2 hashcode 1149319664
  • Prevent Singleton from Clonable

Solution : throw excepiton or return same instance on overriding clone method

public class MySingletonObject2 extends MyClonableParentObject {

public static final MySingletonObject2 instance = new MySingletonObject2();

private MySingletonObject2() {};

@Override
protected Object clone() throws CloneNotSupportedException {
return instance; //or throw exception
}
}
public class ClonableTest2 {

public static void main(String[] args) throws CloneNotSupportedException {

MySingletonObject2 ins1 = MySingletonObject2.instance;
MySingletonObject2 ins2 = (MySingletonObject2) ins1.clone();

//same hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}

output is :

ins1 hashcode 455659002
ins2 hashcode 455659002

I hope this was a useful article for you.

--

--

Serdar A.

Senior Software Developer & Architect at Havelsan Github: https://github.com/serdaralkancode #Java & #Spring & #BigData & #React & #Microservice