Java에서 Stream API를 사용할 때 Collectors.toList()는 오랫동안 익숙하게 사용해온 방식이었습니다.
그런데 최근 IntelliJ에서 Stream.toList() 사용을 권장하는 안내 메시지를 보고 궁금증이 생겼습니다. 🤔

이번 글에서는 두 방식의 차이점과 언제 어떤 걸 선택해야 하는지 명확히 정리해보겠습니다.
Collectors.toList() → 변경 가능한 리스트 (mutable)
List<String> list = Stream.of("A", "B", "C").collect(Collectors.toList());
list.add("D"); // 가능! 리스트에 값 추가됨
Collectors.toList()는 내부적으로 new ArrayList<>()를 사용해 리스트를 생성합니다.

그래서 리스트에 값을 추가하거나 삭제할 수 있습니다.
하지만 이로 인해 실수로 외부에서 리스트를 수정해버리는 경우가 생길 수 있습니다.
(특히 불변(immutable) 데이터가 필요한 곳에서는 치명적인 버그가 될 수 있음!)
Stream.toList() → 변경 불가능한 리스트 (immutable)
List<String> list = Stream.of("A", "B", "C").toList();
list.add("D"); // 오류 발생! UnsupportedOperationException
Java 16부터 추가된 메서드로 이 방식은 리스트를 바꿀 수 없게 만듭니다.
반환되는 리스트는 불변 리스트 (UnmodifiableList)입니다.

즉, 한 번 만든 후에는 추가/삭제가 불가능한 불변리스트를 반환합니다.
핵심 차이: 가변 리스트 vs 불변 리스트
| 메서드 반환 | 타입 변경 가능 | 변경 가능 | 버전 |
| Collectors.toList() | ArrayList | ✅ 가능 | Java 8+ |
| Collectors.toUnmodifiableList() | UnmodifiableList | ❌ 불가능 | Java 10+ |
| Stream.toList() | UnmodifiableList | ❌ 불가능 | Java 16+ |
📝 Stream.toList()는 사실상 Collectors.toUnmodifiableList()를 짧게 쓴 문법이자 더 나은 대체제입니다.
SonarQube 경고 (Rule S6204)
Collectors.toList()를 불변 리스트가 필요한 곳에서 사용하면 SonarQube가 경고를 띄우기도 합니다.
❌ 잘못된 예시
List<String> emails = users.stream()
.map(User::getEmail)
.collect(Collectors.toList()); // Code Smell (S6204)
이 코드에서는 emails 리스트가 외부에서 바뀔 위험이 있지만, 누가 봐도 "읽기 전용"일 것처럼 보여 문제가 됩니다.
✅ 올바른 개선 방법 (Java 16 이상)
List<String> emails = users.stream()
.map(User::getEmail)
.toList(); // 불변 리스트 반환
Stream.toList()를 사용하면 의도가 명확하고, SonarQube도 만족합니다!
언제 어떤 걸 써야 할까?
| 상황 | 추천 방식 |
| 리스트를 외부에서 바꾸면 안 되는 경우 (기본값) | Stream.toList() |
| Java 8~15 환경에서 불변 리스트가 필요할 때 | Collectors.toUnmodifiableList() |
| 일부러 변경 가능한 리스트가 필요할 때 | Collectors.toList() |
기본 원칙:
→ 변경이 필요하면 명시적으로 Collectors.toList()
→ 기본은 Stream.toList()로 시작하고, 정말 필요할 때만 mutable로
Stream API는 Java 개발자에게 너무 익숙한 기능이지만, Stream.toList()의 도입은 작지만 중요한 변화입니다.
의도가 더 분명해지고, 코드가 더 안전해지며, 더 적은 코드로 같은 결과를 낼 수 있습니다.
Java 16 이상을 쓰고 있다면 이제부터는 Collectors.toList() 대신 Stream.toList()를 기본값으로 써보세요. 👩🏻💻
'Backend > Java' 카테고리의 다른 글
| MapStruct와 ObjectMapper, 언제 어떤 걸 써야 할까? (2) | 2025.07.23 |
|---|---|
| OutOfMemory: IntelliJ IDEA로 Heap Dump(.hprof) 분석하기 (0) | 2025.05.28 |
| [Java] Map.getOrDefault()가 예외를 던진다고? (0) | 2025.04.16 |
| JDK 23: 자바 핵심 변경점 정리 (0) | 2025.01.15 |
| Jackson 커스텀 어노테이션: @JacksonAnnotationsInside (1) | 2024.12.21 |