Skip to main content

Well, I did. In everyday work we can hear discussions about microservices, containers, beans, entities etc. but it is very hard and rare to hear some talk about immutable or mutable classes. Why is it like that?

Let’s first refresh our memories of what an Immutable class is.

Immutable class means, that once an object is initialized we cannot change its content.

To be more clear, let’s see how we can write Immutable classes in Java.

Basic rules to write some immutable classes are:
  1. Don’t provide “setter” methods — methods that modify fields or objects referred to by fields.
  2. Make all fields final and private.
  3. Don’t allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.
  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. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

How to make Immutable classes

After defining these basic rules there are several different solutions to write Immutable classes.

Basic one without using external libraries:
final public class ImmutableBasicExample {

   final private Long accountNumber;
   final private String accountCurrency;

   private void check(String accountCurrency, Long accountNumber) {
       // Some constructor rules
       // throw new IllegalArgumentException()
   }

   public ImmutableBasicExample(
           String accountCurrency, Long accountNumber) {
       check(accountCurrency, accountNumber);
       this.accountCurrency = accountCurrency;
       this.accountNumber = accountNumber;
   }

   public String getAccountCurrency() {
       return accountCurrency;
   }

   public Long getAccountNumber() {
       return accountNumber;
   }
}

Use final class, make fields final and set them in the constructor. Don’t write setter for fields, just getter.

We can use Lombok:
import lombok.Value;

@Value
public class LombokImmutable {
   Long accountNumber;
   String accountCurrency;
}

Much shorter, with just one annotation we have done our job. Value is variant of data and is immutable because: all fields are private and final, the class is made final, the getter for each field, additional some basic methods like toString(), equals() and hasCode().

Or the newest way with using a record (Java 14 preview features):
public record RecordRequstBody(
       Long accountNumber,
       String accountCurrency) {
}

By now the most sophisticated way to make an Immutable class. Small,  readable and useful code. Without using external libraries. Compiler auto generates following code: private final field, public read assessor, public constructor with signature same like state description and implementation of following methods: toString(), hashCode() and equals().

I’m sure that there are other ways to write Immutable classes, but these are enough to understand how much code and effort we need to write one.

Use of Immutable classes

We already use Immutable classes every day in our work. All primitives wrapper classes are immutable. Here is one everyday practical example:

Integer integerExample = 6;
System.out.println(integerExample);
changeInteger(integerExample);
System.out.println(integerExample);

where changeInteger is:

private void changeInteger(Integer integerExample) {
   integerExample = integerExample + 1;
}

The output will be:

6
6

It is because the line

integerExample = integerExample + 1;

creates a new reference to the new object for integerExample and the integerExample in the main code is still referencing the old Integer object, which is not changed.

This also applies to all other primitive wrappers that are immutable: Byte, Short, Integer, Long, Float, Double, Character, Boolean. Additionally BigDecimal, BigInteger and LocalDate are immutable.

Other one interesting immutable class in Java is String. If we write the following code:

String string1 = "first string";
string1 += "concatenation";

Concatenation of two Strings with “+” will produce new String. It is fine if we do this with two or a few Strings, but if we build one String with more concatenations then we will initialize more objects. (That is why here it is better to use StringBuilder)

We can create and use Immutable classes for RequestBody (DTO) in our rest controllers. It is a good idea because the state will be validated when the request is created, once, and will be valid all time after that. We will not have any need to change the state of the request. If we are changing the request then we are doing something wrong.

Another scenario where we can use them is when we need to have some business classes (where we will process some data) where the state should be unchanged. We can find a few examples for this, using them for Currency, Account information etc…

How often should we use them?

Well we see that there are some benefits using them:

  • They are thread-safe
  • Safer because their state can not be changed
  • They are simple for construct, test and use
  • It is good to understand them and use them if you work more functional and concurrent programming

But there are disadvantages too:

At first, you can’t change fields in them. To do that you should create a copy of them with changed values. It means that you will have more objects initialized in the VM and for that, you should add some code to copy objects.
To be sure that you will make some classes immutable you should be sure that the state of the object would not be changed. These days developers don’t spend time analyzing where that class will be or will not be changed.

There is one general concept from Effective Java, which describes the use of immutability:

Classes should be immutable unless there’s a very good reason to make them mutable… If a class cannot be made immutable, limit its mutability as much as possible.