Being able to create an immutable object in Java is one of the most essential skills for any Java programmer. Such objects are needed when you want to rest assured that a created object cannot change for whatever reason.

The common approach is to create an object with private final fields and allow only getters for these fields. However, if you reference directly these properties when returning them you are making them mutable.

Here is an example:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

final class MutableOrNot {
	private final List<String> supposedlyImmutable;

	public MutableOrNot(List<String> supposedlyImmutable) {
		this.supposedlyImmutable = new ArrayList<String>(supposedlyImmutable);
	}

	public List<String> getSupposedlyImmutable() {
		// if you return supposedlyImmutable directly you make it mutable
		// return supposedlyImmutable;
		
		// instead you should return an unmodifiable view of the specified list
		return Collections.unmodifiableList(supposedlyImmutable);
	}
}

public class Test {
	public static void main(String[] args) {

		MutableOrNot immutable = new MutableOrNot(Arrays.asList("aa", "bb"));	
		immutable.getSupposedlyImmutable().clear(); //try to alter the contents of the immutable property by clearing it.
		System.out.println(immutable.getSupposedlyImmutable());
	}
}

Note: To run the above you have to paste it in a file called Test.java.

In the above example, the method getSupposedlyImmutable() returns a unmodifiable view of the supposedlyImmutable list:

Collections.unmodifiableList(supposedlyImmutable);

Similarly, there are also methods for making maps (Collections.unmodifiableMap()), sets (Collections.unmodifiableSet()), etc, unmodifiable.

Later in the Test class, when we try to execute the clear() method on that list, we'll get an UnsupportedOperationException exception:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableCollection.clear(Collections.java:1076)
	at testing.Test.main(Test.java:29)

However, if we make a change to the above code and return directly a reference to the list, we'll be able to clear the list:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

final class MutableOrNot {
	private final List<String> supposedlyImmutable;

	public MutableOrNot(List<String> supposedlyImmutable) {
		this.supposedlyImmutable = new ArrayList<String>(supposedlyImmutable);
	}

	public List<String> getSupposedlyImmutable() {
		// if you return supposedlyImmutable directly you make it mutable
		 return supposedlyImmutable;
	}
}

public class Test {
	public static void main(String[] args) {

		MutableOrNot immutable = new MutableOrNot(Arrays.asList("aa", "bb"));	
		immutable.getSupposedlyImmutable().clear(); //try to alter the contents of the immutable property by clearing it.
		System.out.println(immutable.getSupposedlyImmutable());
	}
}

The above will print an empty list:

[]

This shows that despite the fact that we mark the list as private and final we are still able to make a change to it and extra measures have to be taken to make it really immutable.