Home » Core Java » Java Immutable Objects Example

About Lefteris Karageorgiou

Lefteris is a Lead Software Engineer at ZuluTrade and has been responsible for re-architecting the backend of the main website from a monolith to event-driven microservices using Java, Spring Boot/Cloud, RabbitMQ, Redis. He has extensive work experience for over 10 years in Software Development, working mainly in the FinTech and Sports Betting industries. Prior to joining ZuluTrade, Lefteris worked as a Senior Java Developer at Inspired Gaming Group in London, building enterprise sports betting applications for William Hills and Paddy Power. He enjoys working with large-scalable, real-time and high-volume systems deployed into AWS and wants to combine his passion for technology and traveling by attending software conferences all over the world.

Java Immutable Objects Example

1. Introduction

In programming, an object is considered immutable if its state cannot change after it is created. Java not only supports immutable objects but as a best practice, they should be widely used. In this post, we will take a look at how to create immutable objects, their use cases and some examples of immutable Classes.

The technologies that we will use in the code examples are:

  • Java 8
  • Eclipse 4.10.0

2. Final keyword

In Java, immutability can be achieved in fields and objects. By default, these are mutable, which means their state can change. To make them immutable we should use the final keyword when declaring them. When we use the final keyword on a field or object, then we must initialize it otherwise we will get a compilation error.

Below we see an example of an immutable object where its fields are declared final.

Color
class Color {
    
    private final String name;
    private final String hex;
    
    public Color(String name, String hex) {
        this.name = name;
        this.hex = hex;
    }
    
    public String getName() {
        return name;
    }

    public String getHex() {
        return hex;
    }
}

The Color class has 2 fields that are immutable as they are declared final. These must be initialized through the constructor. In this class, we see that there are no setters as the state of the fields can’t be modified. Let’s create a new Color object and also make it final.

ImmutableExample
public class ImmutableExample {

    public static void main(String[] args) {
        final Color red = new Color("RED", "#ff0000");
        System.out.printf("Color %s hex is %s", red.getName(), red.getHex());
    }
}

In the main method we create a new Color object and through the constructor, we pass the values for the fields. This object is also declared final which means that it can’t be initialized again as it can’t change. The output of this would be as expected.

Ouput
Color RED hex is #ff0000

3. Concurrent Applications

In a multi-threading environment, the state of an object can change by multiple threads and as such this will lead to an inconsistent state of the object. Immutable objects are very useful in concurrent applications since they cannot change state, they cannot be corrupted by thread interference or observed in an inconsistent state.

In the following example we see how an object can change by many threads and have inconsistent state.

ConcurrentExample
public class ConcurrentExample {

    public static void main(String[] args) throws InterruptedException {
        Number number = new Number(1);

        for (int i = 0; i < 5; i++) {
            Thread t = new NumberChangerThread(number);
            t.start();
        }

        Thread.sleep(1000);
    }
}

class NumberChangerThread extends Thread {

    private Number number;

    public NumberChangerThread(Number number) {
        this.number = number;
    }

    @Override
    public void run() {
        int random = new Random().nextInt(100);
        System.out.println("changing number to " + random);
        number = new Number(random);
        System.out.println("number changed to " + number.getId());
    }
}

class Number {

    private final int id;

    public Number(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }
}

In the above code, we create a Number object which sets an initial value to its id field. Then multiple threads change the reference of this object by assigning new instances to it. Note that we can’t change the value of id as it is declared final and it’s immutable. The threads then set and get the id field which leads to an inconsistent state. Let’s see the output and verify that.

Ouput
changing id to 29
changing id to 47
changing id to 73
id changed to 73
changing id to 89
id changed to 89
changing id to 95
id changed to 95
id changed to 47
id changed to 29

From the above output, we confirm that the id field does not always have the same value when it is returned from the threads. This inconsistency is only possible for mutable objects. If we still wanted to use mutable objects, then we would have to wrap the set and get methods in a synchronized block.

4. Why String is Immutable?

String is one of the most widely used classes in Java. It was designed to be immutable for performance and security purposes. Let’s see those in more details.

Performance

The JVM stores the String literals in a special area in memory called the String Pool. Anytime a new String literal is created, then the JVM checks if it is already in the pool and it returns a reference to that object. That reduces the memory allocated by the Strings in a program.

Java Immutable Objects - Java String Pool
Java String Pool

Security

Immutability for Strings provides security for a number of use cases. For example, it is very common that we save passwords in Strings. If the String was mutable and the password could change then this would be a huge security concern. Another case is when we create an SQL where if the String was mutable the SQL could change and this would end up in wrong statement or even in SQL injection.

Finally, as we saw in the previous section Strings are great when it comes to multi-threading applications as they are immutable and they cannot change state.

5. Immutable Collections

The java.util.Collections class provides convenient methods which make a Collection immutable. An immutable Collection cannot set, add or remove any of its items. These methods are:

  • unmodifiableCollection(Collection)
  • unmodifiableList(List)
  • unmodifiableMap(Map)
  • unmodifiableNavigableMap(NavigableMap)
  • unmodifiableNavigableSet(NavigableSet)
  • unmodifiableSet(Set)
  • unmodifiableSortedMap(SortedMap)
  • unmodifiableSortedSet(SortedSet)

Let’s create an immutable ArrayList and try to add a new item in it.

ImmutableCollectionsExample
public class ImmutableCollectionsExample {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list = Collections.unmodifiableList(list);
        list.add(3);
    }
}

In the above example, we first create a new ArrayList, then we make it immutable and finally we add a new item. The final operation would throw an exception as the list is immutable.

Ouput
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
	at com.javacodegeeks.ImmutableCollectionsExample.main(ImmutableCollectionsExample.java:14)

The exception that was thrown was the UnsupportedOperationException as the add operation is not supported for immutable Collections.

6. Builder Pattern

The Builder pattern is a very common design pattern which provides a flexible solution to various object creation problems in object-oriented programming. The Builder pattern is very useful when it comes to creating immutable objects. Let’s see an example below.

Employee
class Employee {
    
    private final String name;
    private final String email;
    
    private Employee(EmployeeBuilder builder) {
        this.name = builder.name;
        this.email = builder.email;
    }
    
    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
    
    static class EmployeeBuilder {
    
        private String name;
        private String email;
        
        public Employee build() {
            return new Employee(this);
        }
        
        public EmployeeBuilder setName(String name) {
            this.name = name;
            return this;
        }
        
        public EmployeeBuilder setEmail(String email) {
            this.email = email;
            return this;
        }
    }
}

Above, we create the immutable class Employee which has a private constructor and as such objects can’t be instantiated. For that we embed the EmployeeBuilder class which has setters for the fields of the Employee and a build method which returns a new Employee object. Therefore, the Employee object cannot change as soon as it is created. Below we instantiate a new Employee object through the builder.

ImmutableBuilderExample
public class ImmutableBuilderExample {

    public static void main(String[] args) {
        Employee emp = new EmployeeBuilder().setName("John Smith").setEmail("johnsmith@example.com").build();
        System.out.printf("%s's email is %s", emp.getName(), emp.getEmail());
    }
}

The EmployeeBuilder class helps us set the fields of the Employee object and then return a new instance of it. The output of this would be:

Ouput
John Smith's email is johnsmith@example.com

7. Immutability in Sets & Maps

Immutable objects should be used in the java.util.Set and java.util.Map class. The Set class should contain immutable elements and the Map class should contain immutable keys. If those are mutable, then the hashCode & equals methods will not work as expected. The following example demonstrates bad usage of a Set with mutable objects.

SetMutableExample
public class SetMutableExample {

    public static void main(String[] args) {
        HashSet numbers = new HashSet();
        Numbers n1 = new Numbers(1);
        Numbers n2 = new Numbers(2);
        Numbers n3 = new Numbers(3);
        numbers.add(n1);
        numbers.add(n2);
        numbers.add(n3);

        System.out.println("Numbers: " + numbers);
        System.out.println("Numbers contain 4: " + numbers.contains(new Numbers(4)));

        // change n1 id
        n1.setId(4);

        System.out.println("\nNumbers: " + numbers);
        System.out.println("Numbers contain 4: " + numbers.contains(new Numbers(4))); // wrong !!!
    }
}

class Numbers {

    private int id;

    public Numbers(int id) {
        this.id = id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Numbers other = (Numbers) obj;
        if (id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return id + "";
    }
}

In the code above, we create a HashSet (implementation of Set) in which we add 3 mutable Numbers objects. Then in line 16, we change the reference of one of the objects added, which also replaces the element in the set. This impacts the hashCode method which makes the contains method (line 19) return an incorrect result. Let’s see the output and confirm that.

Ouput
Numbers: [1, 2, 3]
Numbers contain 4: false

Numbers: [4, 2, 3]
Numbers contain 4: false

From the output above we see that the line 5 has incorrect result as the set does have this number in it.

8. Best Practices

Below we summarize the best practices for the immutable objects:

  • Initialize the immutable fields that are declared final in the constructors
  • Do not provide setters as the immutable fields cannot change
  • Use them in concurrent applications to achieve thread-safety
  • The convenient Collections methods create immutable Collections
  • The Builder pattern eases the creation of immutable objects
  • Create immutable Set elements and immutable Map keys

9. Java Immutable Objects – Conclusion

In this post, we took a look at the immutable objects in Java and how to create them using the final keyword. We saw the importance of immutability in concurrent applications. why the String class is immutable and how to make use of the Builder pattern. Finally we took a look at the convenient Collections methods and why we should use immutability in Set elements and Map keys.

10. Download the Eclipse project

Download
You can download the full source code of the above examples here: Java Immutable Objects Example
(0 rating, 2 votes)
1 Comment Views Tweet it!

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

 

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

 

and many more ....

 

Receive Java & Developer job alerts in your Area

 

1
Leave a Reply

avatar
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Maxim Markov Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Maxim Markov
Guest
Maxim Markov

unmodifiable collections are not the same as immutable! Unmodifiable collection is a view, so indirectly it could still be ‘modified’ from some other reference that is modifiable. More info: https://stackoverflow.com/questions/8892350/immutable-vs-unmodifiable-collection