Immutable Objects in Java

1. Overview

In this tutorial, we’ll learn what makes an object immutable, how to achieve immutability in Java, and what advantages come with doing so.

2. What’s an Immutable Object?

An immutable object is an object whose internal state remains constant after it has been entirely created.

This means that the public API of an immutable object guarantees us that it will behave in the same way during its whole lifetime.

If we take a look at the class String, we can see that even when its API seems to provide us a mutable behavior with its replace method, the original String doesn’t change:

String name = "maixuanviet";
String newName = name.replace("dung", "----");

assertEquals("maixuanviet", name);
assertEquals("bael----", newName);

The API gives us read-only methods, it should never include methods that change the internal state of the object.

3. The final Keyword in Java

Before trying to achieve immutability in Java, we should talk about the final keyword.

In Java, variables are mutable by default, meaning we can change the value they hold.

By using the final keyword when declaring a variable, the Java compiler won’t let us change the value of that variable. Instead, it will report a compile-time error:

final String name = "maixuanviet";
name = "bael...";

Note that final only forbids us from changing the reference the variable holds, it doesn’t protect us from changing the internal state of the object it refers to by using its public API:

final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("maixuanviet");
assertEquals(0, strings.size());

The second assertEquals will fail because adding an element to the list changes its size, therefore, it isn’t an immutable object.

4. Immutability in Java

Now that we know how to avoid changes to the content of a variable, we can use it to build the API of immutable objects.

Building the API of an immutable object requires us to guarantee that its internal state won’t change no matter how we use its API.

A step forward in the right direction is to use final when declaring its attributes:

class Money {
    private final double amount;
    private final Currency currency;

    // ...
}

Note that Java guarantees us that the value of amount won’t change, that’s the case with all primitive type variables.

However, in our example we are only guaranteed that the currency won’t change, so we must rely on the Currency API to protect itself from changes.

Most of the time, we need the attributes of an object to hold custom values, and the place to initialize the internal state of an immutable object is its constructor:

class Money {
    // ...
    public Money(double amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public Currency getCurrency() {
        return currency;
    }

    public double getAmount() {
        return amount;
    }
}

As we’ve said before, to meet the requirements of an immutable API, our Money class only has read-only methods.Using the reflection API, we can break immutability and change immutable objects. However, reflection violates immutable object’s public API, and usually, we should avoid doing this.

5. Benefits

Since the internal state of an immutable object remains constant in time, we can share it safely among multiple threads.

We can also use it freely, and none of the objects referencing it will notice any difference, we can say that immutable objects are side-effects free.

6. Conclusion

Immutable objects don’t change their internal state in time, they are thread-safe and side-effects free. Because of those properties, immutable objects are also especially useful when dealing with multi-thread environments.

You can find the examples used in this article over on GitHub.

Related posts:

The StackOverflowError in Java
Fixing 401s with CORS Preflights and Spring Security
Guide to WeakHashMap in Java
Java toString() Method
An Intro to Spring Cloud Contract
Java Program to Find Transitive Closure of a Graph
Java Program to Find k Numbers Closest to Median of S, Where S is a Set of n Numbers
Java Program to Find Number of Spanning Trees in a Complete Bipartite Graph
Java Program to Describe the Representation of Graph using Incidence Matrix
Java Program to Implement Binary Search Tree
Guide to java.util.concurrent.Future
Custom Cascading in Spring Data MongoDB
Spring RequestMapping
Intro to Spring Boot Starters
Java Program to Implement Warshall Algorithm
Java 8 StringJoiner
JUnit5 Programmatic Extension Registration with @RegisterExtension
Java Program to Implement Binary Heap
The Registration API becomes RESTful
Spring Boot - Quick Start
Introduction to Spring Boot CLI
Java Program to find the maximum subarray sum using Binary Search approach
Introduction to Spring Cloud Rest Client with Netflix Ribbon
Java Program to Represent Graph Using Adjacency List
The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5
@Before vs @BeforeClass vs @BeforeEach vs @BeforeAll
Java Program to Generate a Random UnDirected Graph for a Given Number of Edges
Java Program to Construct a Random Graph by the Method of Random Edge Selection
Java Program to Implement LinkedBlockingDeque API
Spring Security with Maven
Quick Guide to java.lang.System
Java Program to Implement Miller Rabin Primality Test Algorithm