Java good practices: Design immutable objects as long as you can

Introduction

Objects can be mutable or immutable in the Java. Mutable objects have state which can be changing. For example this class is mutable:

public class ChangeMe {
    private int state = 0;

    public void updateState(int newState) {
        state = newState;
    }
    
    public int getState() {
       return state;
    }
}

OK, we have this mutable class, but where is the problem? So let’s see example of using this class:

//...
var cm = new ChangeMe();
cm.updateStage(10);
otherFunctionDoingSth(cm);
System.out.println(cm.getState());
//...

Can we be sure what output will be? Of course not. The otherFunctionDoingSth method can change state in cm object. In more complex code it can be a huge problem and it may cause bugs which will be hard to detect. When you have mutable objects you also have to spend more time on code analysis, because you must review more part of code. To avoid this problems you should try design and use immutable objects. For example, we are changing ChangeMe class to immutable version:

public final class ChangeMe {
    private final int state;

    public ChangeMe updateState(int newState) {
        var cm = ChangeMe();
        cm.state = newState;
        return cm;
    }
    
    public int getState() {
       return state;
    }
}

In this version less things in the code can surprise us. We can safety use ChangeMe object as function argument and don’t care that state value will be changed. Make class final for immutable object is a good practice because subclass could break immutable principle.

Immutable DTO

It’s several options to make DTO immutable. The easier is remove all getters and make constructor with all parameters:

public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }
    
    public int getY() {
        return Y;
    }
}

When you have too much parameters and not all are required then you make use builder pattern. Similar to ChangeMe class, when you want to modify any value in DTO class you should always return modifying copy of this class:

public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }
    
    public int getY() {
        return Y;
    }
    
    public Point withX(int x) {
       return new Point(x, this.y);
    }

    public Point withY(int y) {
       return new Point(this.x, y);
    }
}

Of course it’s only example. Don’t forget about override methods equals and hashCode in your own classes. I also highly recommend you to use Lombok to reduce lines of codes. This example is equivalent of above code:

@Value
@Wither
public final class Point {
    private final int x;
    private final int y;
}

Immutable collections

Java has mutable collections in the standard library. The alternative is to use collection from vavr library. These collections are immutable. It means that the every operation, that modify collection structure, return the new instance of collection with the new structure. The old collection instance stay without changes:

List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = list.add(4);

System.out.println(list1);
System.out.println(list2);

The above code return values: 1, 2, 3 in the first line and values 1, 2, 3, 4 in the second. You must remember that when you use immutable collections (and other immutable objects) then all objects in this collections also should be immutable, otherwise you break the immutable principle.

Immutable date and time

Since Java 8 we have new Data/Time API in java.time package. Classes from this package are immutable. Don’t use old class: Date and Calendar. Example of new API:

LocalDate yesterday = LocalDate.now().minusDays(1);

When not using immutable objects

As I mentioned earlier the immutable objects have a lot of benefits, but there could be one disadvantage – performance. The immutable classes could be slower. So when high performance is required for you then you must thinking about using mutable alternatives. But even in this case it will be good to make benchmark tests to compare performance different between these two versions. Perhaps the different will be negligible.

Some design patterns could requires mutable objects like builder or state machine, but it’s OK. Design patterns are answer on well know common problems, so using it shouldn’t be a problem.