Support Online
Skip to main content

How to Create an Immutable Class in Java?

🧠 What Will You Learn in This Guide?

This guide teaches you how to create immutable classes in Java.
Immutable classes are structures whose state does not change after the object is created.
This preserves data integrity in multi-threaded (thread-safe) environments, makes code more predictable and reduces the risk of errors.

⚙️ Immutable Class Creation Rules

To create a fully immutable class in Java, follow these steps:

StepDescription
1. Define class as finalPrevents inheritance (extends), subclasses cannot change behavior.
2. Make fields private finalIt blocks outside access and only allows assignment once.
3. Adding a setter methodThere should be no methods that change values ​​(mutators).
4. Assign values ​​in constructor methodAll fields must be defined in the constructor.
5. Use deep copyFor collections or mutable objects, copying must be done in both the constructor and the getter.

🧱 Deep Copy Example

import java.util.HashMap;

public final class AyarlarNesnesi {
private final int id;
private final String isim;
private final HashMap<String, String> ayarMap;

public AyarlarNesnesi(int i, String n, HashMap<String, String> hm) {
System.out.println("Nesne başlatılırken Derin Kopya yapılıyor...");
this.id = i;
this.isim = n;
this.ayarMap = new HashMap<>(hm); // Derin kopya
}

public int getId() { return id; }
public String getIsim() { return isim; }

// Güvenli getter: orijinal map yerine kopya döndür
public HashMap<String, String> getAyarMap() {
return (HashMap<String, String>) ayarMap.clone();
}
}

🧩 This structure prevents external changes made to the Map from affecting the internal state.

🧪 Test: The Difference of Deep Copy


public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<>();
h1.put("1", "ilk");

AyarlarNesnesi nesne = new AyarlarNesnesi(10, "genixnode", h1);
System.out.println("Orijinal Map: " + nesne.getAyarMap());

h1.put("2", "ikinci"); // dış map değişti
System.out.println("Dış Map değişti. İç Map: " + nesne.getAyarMap());

HashMap<String, String> kopya = nesne.getAyarMap();
kopya.put("3", "yeni");
System.out.println("Getter kopyası değişti. İç Map: " + nesne.getAyarMap());
}

🧩 Result: The settingMap inside remains constant — immutability is preserved.

⚠️ Shallow Copy Problem


public AyarlarNesnesi(int i, String n, HashMap<String, String> hm) {
this.id = i;
this.isim = n;
this.ayarMap = hm; // ⚠️ Tehlikeli: Dış referans korunur
}

If you assign it directly like this, changes made externally will affect the internal state of the class. Result: The class is no longer immutable.


🧩 Interface Design Errors and Solutions

Error TypeRiskSolution
Returning a live reference in GetterInternal Map can be changed from outside.Use return new HashMap<>(map); or map.clone().
Not making copies in ConstructorExternal input affects the internal state.this.map = new HashMap<>(inputMap);
Using Collections.unmodifiableMapMap is fixed but internal values ​​may change.Make sure that internal elements are also immutable.

📚 Library Support: Immutable Collections

🧩 Guava ImmutableMap


import com.google.common.collect.ImmutableMap;

public final class GuavaExample {
private final ImmutableMap<String, String> config;
public GuavaExample(Map<String, String> input) {
this.config = ImmutableMap.copyOf(input); // Tek adımda immutable kopya
}
}

✅ Guava provides deep immutable collections like ImmutableMap, ImmutableList, ImmutableSet. No copying required in Getter.

⚙️ Vavr Collections (Functional Approach)


import io.vavr.collection.Map;
import io.vavr.collection.HashMap;

public final class VavrExample {
private final Map<String, String> settings;
public VavrExample(Map<String, String> input) {
this.settings = input; // Vavr koleksiyonları zaten immutable’dır
}
}

✅ Suitable for functional programming style. Each change creates a new object, it does not affect the original.


🧩 Deep Copy vs Shallow Copy Comparison

GenreDescriptionExemplary Behavior
Deep CopyData at each level is copied.new HashMap<>(orijinal)
Shallow CopyOnly the reference is copied.this.map = orijinal

🧩 Common Errors Chart

StatusError CodeSolution
Arrays.asList()Arrays.asList("A","B").add("C");new ArrayList<>(Arrays.asList("A","B"))
List.of() (Java 9+)List.of("X","Y").add("Z");new ArrayList<>(List.of("X","Y"))

💬 Frequently Asked Questions (FAQ)

  1. Does the final keyword provide immutability?

No. final only prevents the reference from being reassigned. Content may still be subject to change.

  1. Why is String immutable?

It does not leak the character array inside the string and does not contain any modifier methods.

  1. Why is immutability important for multi-threading?

Immutable objects can be shared without requiring a lock. This provides a thread-safe structure.

  1. Do immutable classes reduce performance?

There is a cost to copying, but the simplified logic and increased security offset this difference.

  1. How to change the value?

A new object is created. For example:


String s2 = s1.toUpperCase(); // s1 değişmez, s2 yenidir.

🚀 Conclusion

Immutable classes are the foundation for developing safe, fault-tolerant and thread-safe applications in Java. You can make your code more predictable by using final fields, deep copying, and immutable collections.

💡 You can immediately apply these principles in any Java-based DTO or configuration classes you create on the GenixNode platform.