Tutorial

How To Create an Immutable Class in Java

Updated on November 17, 2022
authorauthor

Pankaj and Andrea Anderson

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 authors
Default avatar
Pankaj

author


Default avatar

Technical Editor


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
December 30, 2021

who can solve this for me? /* Edit the code by following the steps in the description */ import java.util.HashMap; import java.util.Map; public class Location { final int locationID; public String description; protected Map exits; public Location(int locationID, String description, Map exits) { this.locationID = locationID; this.description = description; this.exits = new HashMap(exits); this.exits.put(“Q”, 0); } public void addExit(String direction, int location) { exits.put(direction, location); } public void setLocationID(int locationID) { this.locationID = locationID; } public int getLocationID() { return locationID; } public void setDescription(String description) { this.description = description; } public String getDescription() { return description; } public void setExits(Map exits) { this.exits = exits; } public Map getExits() { return exits; } } Make the Location class an Immutable Class. The strategy for creating an Immutable Class is:     Steps:     1. Don’t provide setters.     2. Make all fields final and private     3. Don’t allow the class to be subclassed.     4. If the instance fields include references to mutable objects, don’t allow those objects to be changed:                -  Don’t provide methods that modify the mutable objects.             -  Don’t share references to the mutable objects. As an added Task, handle the case where exits is null when passed to the constructor. NOTE: Not all classes documented as “immutable” follow these rules. However, the steps above are the basis of an Immutable Class.

- Francis

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    June 14, 2021

    I believe deep copy will be needed for ID and Name as well. Please explain if not.

    - Dinesh Solanki

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      July 31, 2020

      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

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        December 2, 2019

        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

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          November 22, 2019

          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

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            November 7, 2019

            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

              JournalDev
              DigitalOcean Employee
              DigitalOcean Employee badge
              August 31, 2019

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

              - jogi

                JournalDev
                DigitalOcean Employee
                DigitalOcean Employee badge
                August 10, 2019

                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

                  JournalDev
                  DigitalOcean Employee
                  DigitalOcean Employee badge
                  July 21, 2019

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

                  - Mukund Padale

                    JournalDev
                    DigitalOcean Employee
                    DigitalOcean Employee badge
                    May 19, 2019

                    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

                      Try DigitalOcean for free

                      Click below to sign up and get $200 of credit to try our products over 60 days!

                      Sign up

                      Join the Tech Talk
                      Success! Thank you! Please check your email for further details.

                      Please complete your information!

                      Featured on Community

                      Get our biweekly newsletter

                      Sign up for Infrastructure as a Newsletter.

                      Hollie's Hub for Good

                      Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

                      Become a contributor

                      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

                      Welcome to the developer cloud

                      DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

                      Learn more