read-only collections
data immutability
collection security
Java programming
software development

Return collection as read-only

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Returning a collection as read-only is a common way to protect internal state from accidental modification. The goal is to let callers inspect the data without giving them the ability to add, remove, or replace elements through the returned reference.

In Java, the usual tool is an unmodifiable view or an immutable copy. The right choice depends on whether you want callers to see future changes to the underlying collection or a stable snapshot taken at return time.

Return an Unmodifiable View

The classic pattern is Collections.unmodifiableList or the corresponding wrapper for other collection types:

java
1import java.util.ArrayList;
2import java.util.Collections;
3import java.util.List;
4
5public class Catalog {
6    private final List<String> items = new ArrayList<>();
7
8    public Catalog() {
9        items.add("A");
10        items.add("B");
11    }
12
13    public List<String> getItems() {
14        return Collections.unmodifiableList(items);
15    }
16}

This prevents callers from modifying the returned list directly:

java
Catalog catalog = new Catalog();
List<String> items = catalog.getItems();
// items.add("C");  // throws UnsupportedOperationException

Understand That Unmodifiable Is Not Immutable

An unmodifiable view does not make the underlying collection immutable. If the owning class changes the internal list later, callers will see those changes through the read-only wrapper.

That can be useful or surprising depending on the API design.

If you want a stable snapshot instead, return a copy:

java
public List<String> getItemsSnapshot() {
    return List.copyOf(items);
}

This creates a separate read-only result representing the state at that moment.

Choose Between View and Snapshot

Use an unmodifiable view when:

  • the collection is part of current live state
  • you want callers to observe later changes
  • you want to avoid copying overhead

Use an immutable copy when:

  • callers should receive a stable snapshot
  • you do not want later internal mutations to be visible
  • API predictability matters more than avoiding the copy

This is a design decision, not just a syntax choice.

Defensive Encapsulation Still Matters

Returning the raw internal collection directly is the dangerous version:

java
public List<String> getItemsUnsafe() {
    return items;
}

That lets outside code mutate internal state freely, which breaks encapsulation and can make debugging much harder.

The whole point of read-only returns is to avoid that leak of ownership.

Read-Only APIs Communicate Ownership

Returning a read-only collection is not only a safety mechanism. It also communicates that the caller may inspect the data but does not own the right to mutate the provider's internal state. That makes API intent clearer.

That kind of ownership signaling is one reason read-only returns improve maintainability as well as safety.

It also makes later refactoring safer because the mutation boundary is much more obvious to callers.

That design clarity is a practical benefit, not just a theoretical one.

That is exactly the kind of boundary that reduces accidental coupling between classes.

Common Pitfalls

  • Returning the raw internal collection and assuming callers will behave.
  • Confusing an unmodifiable view with a truly immutable snapshot.
  • Forgetting that mutable elements inside a read-only collection can still be changed individually.
  • Using snapshots everywhere without considering the cost for large collections.
  • Exposing internal state in APIs and then being surprised by accidental external mutations.

Summary

  • Return read-only collections to protect internal state.
  • 'Collections.unmodifiableList provides a read-only view of an existing collection.'
  • 'List.copyOf returns an immutable snapshot copy.'
  • Choose between view and snapshot based on whether callers should see later changes.
  • Avoid returning raw internal collections directly.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.