Using some setters, without setting all needed properties in the constructor(s)

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

It’s easy to show that Person class is not immutable:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

To fix it, simply delete setSurname() and refactor the constructor as follows:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

Not marking instance variables as private and final

Take a look at the following class:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

The following snippet shows that the above class is not immutable:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

To fix it, simply mark name property as private and final.


Exposing a mutable object of the class in a getter

Take a look at the following class:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names class seems immutable at the first sight, but it is not as the following code shows:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

This happened because a change to the reference List returned by getNames() can modify the actual list of Names.