
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: May 1, 2025
In this tutorial, we’ll learn what makes an object immutable, how to achieve immutability in Java, and what advantages come with doing so.
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 = "baeldung";
String newName = name.replace("dung", "----");
assertEquals("baeldung", name);
assertEquals("bael----", newName);
The API gives us read-only methods, it should never include methods that change the internal state of the object.
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 = "baeldung";
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("baeldung");
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.
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.
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.
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.