Report this

What is the reason for this report?

How To Create an Immutable Class in Java

Updated on November 17, 2022
How To Create an Immutable Class in Java

Introduction

This article provides an overview of how to create an immutable class in Java programming.

An object is immutable when its state doesn’t change after it has been initialized. For example, String is an immutable class and, once instantiated, the value of a String object never changes. Learn more about why the String class is immutable in Java.

Because an immutable object can’t be updated, programs need to create a new object for every change of state. However, immutable objects also have the following benefits:

  • An immutable class is good for caching purposes because you don’t have to worry about the value changes.
  • An immutable class is inherently thread-safe, so you don’t have to worry about thread safety in multi-threaded environments.

Learn more about multi-threading in Java and browse the Java Multi-Threading Interview Questions.

Creating an Immutable Class in Java

To create an immutable class in Java, you need to follow these general principles:

  1. Declare the class as final so it can’t be extended.
  2. Make all of the fields private so that direct access is not allowed.
  3. Don’t provide setter methods for variables.
  4. Make all mutable fields final so that a field’s value can be assigned only once.
  5. Initialize all fields using a constructor method performing deep copy.
  6. Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

The following class is an example that illustrates the basics of immutability. The FinalClassExample class defines the fields and provides the constructor method that uses deep copy to initialize the object. The code in the main method of the FinalClassExample.java file tests the immutability of the object.

Create a new file called FinalClassExample.java and copy in the following code:

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// fields of the FinalClassExample class
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Getter function for mutable objects

	public HashMap<String, String> getTestMap() {
		return (HashMap<String, String>) testMap.clone();
	}

	// Constructor method performing deep copy
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// "this" keyword refers to the current object
		this.id=i;
		this.name=n;

		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}

	// Test the immutable class

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// print the ce values
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// change the local variable values
		i=20;
		s="modified";
		h1.put("3", "third");
		// print the values again
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

Compile and run the program:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Note: You might get the following message when you compile the file: Note: FinalClassExample.java uses unchecked or unsafe operations because the getter method is using an unchecked cast from HashMap<String,String> to Object. You can ignore the compiler warning for the purposes of this example.

You get the following output:

Output
Performing Deep Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second} ce testMap after changing variable from getter methods: {1=first, 2=second}

The output shows that the HashMap values didn’t change because the constructor uses deep copy and the getter function returns a clone of the original object.

What happens when you don’t use deep copy and cloning

You can make changes to the FinalClassExample.java file to show what happens when you use shallow copy instead of deep copy and return the object insetad of a copy. The object is no longer immutable and can be changed. Make the following changes to the example file (or copy and paste from the code example):

  • Delete the constructor method providing deep copy and add the constructor method providing shallow copy that is highlighted in the following example.
  • In the getter function, delete return (HashMap<String, String>) testMap.clone(); and add return testMap;.

The example file should now look like this:

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// fields of the FinalClassExample class
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Getter function for mutable objects

	public HashMap<String, String> getTestMap() {
		return testMap;
	}

	//Constructor method performing shallow copy

	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Shallow Copy for Object initialization");
		this.id=i;
		this.name=n;
		this.testMap=hm;
	}

	// Test the immutable class

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// print the ce values
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// change the local variable values
		i=20;
		s="modified";
		h1.put("3", "third");
		// print the values again
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

Compile and run the program:

  1. javac FinalClassExample.java
  2. java FinalClassExample

You get the following output:

Output
Performing Shallow Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second, 3=third} ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}

The output shows that the HashMap values got changed because the constructor method uses shallow copy there is a direct reference to the original object in the getter function.

Conclusion

You’ve learned some of the general principles to follow when you create immutable classes in Java, including the importance of deep copy. Continue your learning with more Java tutorials.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Pankaj Kumar
Pankaj Kumar
Author
See author profile

Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev

Andrea Anderson
Andrea Anderson
Editor
Technical Editor
See author profile
Category:
Tags:

Still looking for an answer?

Was this helpful?

… and here is a translation of the entire exercise into Scala, except without the wasteful copying and cloning, and with correct equals and hashCode methods: case class FinalClassExample(id: Int, name: String, testMap: Map[String,String])

- Ken

Shouldn’t it be “shallow copy” instead of “swallow copy” unless I am missing something?

- Shantanu Kumar

why don’t you just do this: import static java.util.Collections.unmodifiableMap; public final class FinalClassExample { ... private final Map testMap; public FinalClassExample(int i, String n, Map m){ id = i; name = n; testMap = unmodifiableMap(new HashMap (m)); } public Map getTestMap() { return testMap; } ... }

- John

In Groovy you can annotate the class as @Immutable and get /almost/ similar results to the scala example without all the boilerplate. IMHO Scala is better for it’s immutable support though. Also, don’t forget that Java Date, Dimension, and other JDK classes are not immutable as well, so you need to make defensive copies of those classes as well.

- Hamlet D’Arcy

Out of curiosity, why the requirement to have the class be marked as final so as not to be extended? What does disallowing subclasses actually provide in terms of allowing objects of this type to be immutable? Further, you don’t have to mark fields as private only just so long as you can guarantee that all constructor’s of the class properly initialize all of the fields. As a side note, you *can* have setters, but with the nuance that instead of changing an internal field, what the setter really does is specify a return type of the class the method is on, and then behind the scenes creates a new object using a constructor that accepts all internal fields, using the internally held state in for all params with the exception of the field represented by the setter called since you want the new object to have that field updated.

- whaley

It’s super webpage, I was looking for something like this

- Łomża Zuhlke

Thanks mate, great details… – Anish Sneh

- Anish Sneh

Thanks, you know it and you know how to explain it too! I will definitely read more of your articles :)

- Mirey

Thanks for the detailed tutorial, well written and the flow goes exactely to showing up almost the need of every instruction in the code :) One side question, even if I know we are talking about Objects immutability,but what about the other instance variables you introucted in the FinalClassExample (id, name)? Is there any way to make them immutable?

- Marwen

Should not be String name declared as a not final? Its not mutable anyway.

- Ramakant

Hi Pankaj, It was a great learning about creating Immutable objects.If you are performing step 5 and 6 then step 4 is not required I guess. You are not storing or returning original reference of HashMap, You are using clone concept for that, Hence as a result client application have no way to reassign new object to declared HaspMap. Please correct me If I am missing some thing Thanks& Regards Rais Alam

- Rais Alam

Can you please describe no 6. more deeply.I am not able to understand it.

- mahi

Excellent post!!

- S

thanx i disable ABP for u

- ahmed

I can modify the object using ce.testMap.put(“10”, “ten”); Output: Performing Deep Copy for Object initialization true false ce id:10 ce name:original ce testMap:{2=second, 1=first} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{2=second, 1=first, 10=ten} ce testMap after changing variable from accessor methods:{2=second, 1=first, 10=ten}

- Bijoy

private final String name; …Why this is final …String is already final …do we need to declare it again…???

- Ajaz

Replying to JournalDev

“String is already final.” - yes , it is , but that final is at class level which means you can’t extend the String class, while that "private final String " is for variable , so that we cant change the value of that object once initialized.

- Vinay

Thank you Pankaj. I’m a big fan of your writing skills. You cover every details and explain concepts in easy to understand language. Thanks again.

- Vijay Nandwana

Very easy to understand and useful post!!

- Vineet kaushik

Thanks for great article! Can we use testMap.clone() in the constructor instead of going through all of the map items with Iterator?

- Bektur Toktosunov

Hi Pankaj I didn’t Understood the System.out.println(h1 == ce.getTestMap()) answer is False. Can you Please explain why it is false.

- WAA

hi pankaj, i love to read your blog. here i found a hack at main() using a reflection how to prevent it. Class mc = ce.getClass(); Field fs = mc.getDeclaredField(“name”); fs.setAccessible(true); fs.set(ce, “hacked”); System.out.println(“ce name after reflection hack:”+ce.getName());

- tabish

When you make the field final, Why making the variable private is mandate?

- Rahul

I it necessary to have variable as final?? We can achive it without it also, there is no statement to change varable.

- Pratyush

Hi setter method for map provides shallow copy only though clone method so that we can change the value later. How come you can say its immutable ?

- Anbu

Which Java version ? or forgot the Final variables initialisation?

- Tanmai

Correct output is:: Performing Shallow Copy for Object initialization true false ce id:10 ce name:original ce testMap:{2=second, 1=first} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{3=third, 2=second, 1=first} ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=f irst}

- Rushabh

I am not clear on Point 5 initialization swallow or Deep are comparison not initialization. swallow comparison is done by == and Deep comparison by equal / equalignorecase Did you mean initialization should be done at constructor with safer way without exposing identity of the fields?

- kuldeep patil

I also wrote an article with a complete different view and you may have a look, https://www.codedjava.com/2017/09/immutable-objects-in-java\_50.html Thanks,

- Sameera

What if we remove the final access modifier from class as we are independently handling all the fields or methods of this class. And if some one extend this class then they doesn’t impact this class instance. Please suggest.

- Vaibhav Jetly

Your getter method for HashMap can be in that way public HashMap getTestMap() { //return testMap; //HashMap tempMap=new HashMap(); return new HashMap(testMap); }

- TEJENDRA PRATAP SINGH

correct output is: Performing Shallow Copy for Object initialization true false ce id:10 ce name:original ce testMap:{1=first, 2=second} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{1=first, 2=second, 3=third} ce testMap after changing variable from accessor methods:{1=first, 2=second, 3=third}

- Hagos haile

Hi Pankaj, What will happen if we do not declare class as final, since member variables are privare so can not be extendable. I am here trying to understand why we need final for class and its member variable. Is there any way state of a class can be modified if we don’t declare class and its variables final? Thanks, Prahlad.

- Prahlad Kumar

Hi Pankaj, Thanks for writing such an informative article. I would like to know what if my all member variables are immutable like all Strings or Wrappers?, Do I still need to follow above steps? Thanks,

- Jitendra Singh

Great Article… very helpful in cracking interviews … Thank you so much !!

- Omkar J

Article is very nice and easy to understand. I have gone through the comments and got deeper understanding of this concept. Requesting everyone else to go through the discussions done in comments then you will get more understanding about this topic. Thanks to Pankaj.

- Vijay Kumar

This is how we create an immutable class. But can you please describe how the immutable object is created? or is that happens in java or not??

- u.mang

Not required map iteration in constructor and return statement clone which you used, may be any other reason please provide that. class Student { private final int sid; private final String sname; private final Map map; public Student(int sid, String sname, Map map) { super(); this.sid = sid; this.sname = sname; this.map = new HashMap(map); //this.list=list; } public int getSid() { return sid; } public String getSname() { return sname; } public Map getList() { return map; } @Override public String toString() { return “Student [sid=” + sid + “, sname=” + sname + “]”; } }

- Parvise

Hi, please post your Java code of connection pooling in java without using Spring and hibernate.

- Deepak

It was worth a read about immutable class in Java! Really learned a lot form this article. I can imagine the effort you put into this & especially appreciate you sharing it. keep up the good work.

- online java training

Hi, First i want to thank you for sharing your knowledge with us. here i have one question with this if i am getting HashMap reference through getter method then i can avoid changing Immutable class value by sending deep copy. But what if user accessed it directly? i mean Map local = ce.testMap; local.put(“local” , “local”); in this case ce.testMap will have the above added object as well. How can we avoid this? thanks!

- Pandidurai

correct output is: Performing Shallow Copy for Object initialization true false ce id:10 ce name:original ce testMap:{1=first, 2=second} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{1=first, 2=second, 3=third} ce testMap after changing variable from accessor methods:{1=first, 2=second, 3=third}

- Hagos haile

Hi Pankaj, What will happen if we do not declare class as final, since member variables are privare so can not be extendable. I am here trying to understand why we need final for class and its member variable. Is there any way state of a class can be modified if we don’t declare class and its variables final? Thanks, Prahlad.

- Prahlad Kumar

Hi Pankaj, Thanks for writing such an informative article. I would like to know what if my all member variables are immutable like all Strings or Wrappers?, Do I still need to follow above steps? Thanks,

- Jitendra Singh

Great Article… very helpful in cracking interviews … Thank you so much !!

- Omkar J

Article is very nice and easy to understand. I have gone through the comments and got deeper understanding of this concept. Requesting everyone else to go through the discussions done in comments then you will get more understanding about this topic. Thanks to Pankaj.

- Vijay Kumar

This is how we create an immutable class. But can you please describe how the immutable object is created? or is that happens in java or not??

- u.mang

Not required map iteration in constructor and return statement clone which you used, may be any other reason please provide that. class Student { private final int sid; private final String sname; private final Map map; public Student(int sid, String sname, Map map) { super(); this.sid = sid; this.sname = sname; this.map = new HashMap(map); //this.list=list; } public int getSid() { return sid; } public String getSname() { return sname; } public Map getList() { return map; } @Override public String toString() { return “Student [sid=” + sid + “, sname=” + sname + “]”; } }

- Parvise

Hi, please post your Java code of connection pooling in java without using Spring and hibernate.

- Deepak

It was worth a read about immutable class in Java! Really learned a lot form this article. I can imagine the effort you put into this & especially appreciate you sharing it. keep up the good work.

- online java training

Hi, First i want to thank you for sharing your knowledge with us. here i have one question with this if i am getting HashMap reference through getter method then i can avoid changing Immutable class value by sending deep copy. But what if user accessed it directly? i mean Map local = ce.testMap; local.put(“local” , “local”); in this case ce.testMap will have the above added object as well. How can we avoid this? thanks!

- Pandidurai

Even within this constructor if you assign cloned version of input “hm” to testMap, it should still work, no need of deep copy. public FinalClassExample(int i, String n, HashMap hm){ System.out.println(“Performing Shallow Copy for Object initialization”); this.id=i; this.name=n; this.testMap=hm.clone(); }

- Ravi Beli

What’s the significance of declaring class as final here?

- Ashwani Pratap

But when we trying to change a String object a new object would be created with those changes. Is that possible here ?. Please explain

- Sreerag

One of the best example and explanation for “creating immutable class”. Loved it. Thanks !!

- Mukund Padale

4. Make all mutable fields final so that it’s value can be assigned only once. I don’t think this is necessary if are declaring the fields private. mutable fields marked as final can still be mutated, only the reference can not be changed.

- Manohar Bhat

What about Date variable we can still change the date by writing d.getTime(); How can we avoid that?

- jogi

Hi Pankaj, I have been following your site for quite some time & it has been helping me a lot… Thank you very much for that. Just one suggestion, the “scrollable” code blocks are not very user-friendly to use. It would be great if we can see the entire code at once - even though it increases the height of the page, just my personal experience…

- Shobhit Mittal

hi Pankaj, great work !!! what is the need of getter method HashMap clone as we are constructing deep clone object? thanks Aravind Sundarraj

- Aravind Sundarraj

Hi, its Explained very nicely, I have one question like i have 2 classes as below. class Separtment{ int id, String name; } Class Employee{ Department Dept, int id, String name; } Here i want to make Employee as immutable, but here Department is a child object, so here how come i make Employee class is immutable. Also One constraint like the Department object which I won’t own(I am not allowed to change anything in the Department object).

- Akhil Kumar Patro

HI Pankaj, I tried your code and analysed that by only returning the map.clone() object will work here no need to make deep copy in constructor.

- Lokesh Kumar

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.