Question 9 · Section 20

Are Record fields final

This makes Record immutable by design.

Language versions: English Russian Ukrainian

🟢 Junior Level

Yes, all fields in Record are automatically final. This means they cannot be changed after the object is created.

public record User(String name, int age) {}

User user = new User("John", 25);

// ❌ Cannot change fields
user.name = "Jane";  // compilation error — field is final

// ✅ Can create a new Record
user = new User("Jane", 25);  // OK

This makes Record immutable by design.


🟡 Middle Level

How it works

The compiler automatically makes all fields private final:

public record Point(int x, int y) {}

// Compiler generates:
public final class Point extends java.lang.Record {
    private final int x;  // implicit final
    private final int y;  // implicit final

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

    public int x() { return x; }
    public int y() { return y; }
}

Even if you don’t write final:

// Both options are identical
public record Point(int x, int y) {}
public record Point(final int x, final int y) {}  // final is redundant

Important nuance: mutable components

Fields are final, but if they reference mutable objects, the contents can be changed:

public record User(String name, List<String> tags) {}

User user = new User("John", new ArrayList<>(List.of("admin", "user")));

// ❌ Cannot replace the list
// user.tags = new ArrayList<>();  // compilation error

// ✅ But can change the list contents
user.tags().add("moderator");  // OK! The list is mutable
user.tags().clear();           // OK!

Solution — defensive copy:

public record User(String name, List<String> tags) {
    public User {
        tags = List.copyOf(tags);  // immutable list
    }
}

User user = new User("John", List.of("admin"));
// user.tags().add("moderator");  // UnsupportedOperationException!

Common Mistakes

  1. Expecting full immutability: ```java public record Data(int[] values) {}

Data d = new Data(new int[]{1, 2, 3}); d.values()[0] = 99; // ✅ Can — array is mutable!

// ✅ Solution public record SafeData(List values) { public SafeData { values = List.copyOf(values); } }


2. **Trying to change field:**
```java
public record Point(int x, int y) {
    public void setX(int x) {
        // this.x = x;  // compilation error — final field
    }
}

🔴 Senior Level

Internal Implementation

JVM level:

// All Record components have ACC_FINAL flag
Field {
    access_flags: ACC_PRIVATE | ACC_FINAL
    name: "x"
    descriptor: "I"
}

Reflection confirms:

Field[] fields = Point.class.getDeclaredFields();
for (Field f : fields) {
    System.out.println(f.getName() + " final=" + Modifier.isFinal(f.getModifiers()));
}
// Output: x final=true, y final=true

Architectural Trade-offs

Final fields:

Pros Cons
Thread-safe (safe publication) Cannot change state
JIT optimizations Need to create new objects
Predictability Mutable components require protection
HashMap keys Harder to work with graphs

Edge Cases

1. Mutable components:

public record MutableRecord(Date createdAt) {}

MutableRecord r = new MutableRecord(new Date());
r.createdAt().setTime(0);  // can change Date!

// ✅ Solution
public record ImmutableRecord(Date createdAt) {
    public ImmutableRecord {
        createdAt = new Date(createdAt.getTime());  // defensive copy
    }
}

2. Final != immutable:

public record Config(Map<String, String> props) {}

// final field, but Map can be changed
Config c = new Config(new HashMap<>());
c.props().put("key", "value");  // OK

3. Memory visibility:

// final fields guarantee safe publication
// after constructor completes, all threads see correct values
public record SharedConfig(String url, int timeout) {}

// Publication safe
SharedConfig config = new SharedConfig("http://api.com", 5000);
// Another thread will see correct values

Performance

Final fields:
- JIT can inline values
- No need for volatile
- Safe publication without synchronization
- Memory barrier at construction

Benchmark:

// Approximate values. Depend on JVM/hardware.
Operation          | Final fields | Mutable fields
-------------------|------------|-------------
Read                | 1 ns       | 1 ns
Publish (thread)    | 0 ns       | sync needed
JIT optimization    | Maximum | Limited

Production Experience

Immutable DTO:

public record OrderDto(
    String id,
    Instant createdAt,
    BigDecimal amount,
    List<OrderItem> items
) {
    public OrderDto {
        items = List.copyOf(items);  // protection against mutable collection
    }
}

Value objects:

public record Money(BigDecimal amount, Currency currency) {
    // Both fields are final — fully immutable
    public Money add(Money other) {
        // Return new object, don't modify current
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

Best Practices

// ✅ Record for immutable data
public record User(String name, Email email) {}

// ✅ Defensive copy for mutable components
public record Tags(List<String> values) {
    public Tags { values = List.copyOf(values); }
}

// ✅ Immutable collections
public record Config(Map<String, String> props) {
    public Config { props = Map.copyOf(props); }
}

// ❌ Mutable components without protection
public record BadRecord(Date date, int[] array) {}
// ❌ Expecting that final = fully immutable

🎯 Interview Cheat Sheet

Must know:

  • All Record fields are automatically private final — compiler adds final
  • Final means you cannot replace the reference, but mutable objects inside can be changed
  • Arrays, Date, collections — mutable components, require defensive copy
  • List.copyOf(), Set.copyOf(), Map.copyOf() to protect mutable collections
  • Final fields guarantee safe publication in multi-threaded environment (without volatile)
  • JIT can better optimize final fields (inlining)

Common follow-up questions:

  • Can you change an array inside Record? — Yes, arrays are mutable: record.values()[0] = 99
  • How to protect mutable component? — Defensive copy in compact constructor: tags = List.copyOf(tags)
  • Final = fully immutable? — No, final only protects the reference, not the object contents
  • Why is final important for HashMap keys? — Modified key = broken hashCode, data is lost

Red flags (DO NOT say):

  • ❌ “Final means full immutability” — Mutable components can be changed
  • ❌ “Array in Record is safe” — Arrays are always mutable, need defensive copy
  • ❌ “Can change field via setter” — Fields are final, setter is impossible
  • ❌ “Final gives no advantages” — Final gives safe publication and JIT optimizations

Related topics:

  • [[1. What is Record in Java and since which version are they available]]
  • [[5. What methods are automatically generated for a Record]]
  • [[7. What is compact constructor in Record]]
  • [[10. Can you use Record as a key in HashMap]]