Java 8·11·17의 발전과 Java 21·25 대비 차이
Java 8·11·17의 발전과 Java 21·25 대비 차이
Java 8, 11, 17은 오랫동안 기업 백엔드의 기준이었던 LTS 버전이다. Java 21과 Java 25는 여기에 동시성, 언어 표현력, JVM 관측성과 메모리 효율 개선을 더한 최신 LTS 계열이다.[1][2]
이 글은 버전별로 무엇이 발전했는지, 실제 사용사례는 무엇인지, Java 8·11·17이 Java 21·25와 비교해 어떤 기능을 갖지 못했는지 정리한다.
버전별 기능 설명은 공식 JDK 문서 기준이다. 성능 우열은 애플리케이션의 workload, GC, DB pool, 네트워크와 프레임워크에 따라 달라지므로 버전만으로 단정하지 않는다.
한눈에 보는 변화
Java 8 → Lambda, Stream, Optional, java.time
Java 11 → 표준 HTTP Client, String/File API 개선, JFR 운영 활용
Java 17 → record, sealed class, pattern matching, switch expression
Java 21 → virtual thread, record pattern, pattern switch, sequenced collections
Java 25 → scoped values 정식화, AOT·JFR·헤더·GC 개선 방향
1. Java 8: 함수형 스타일의 시작
핵심 발전
Lambda와 Stream
List<String> names = List.of("kim", "lee", "park");
names.stream()
.filter(name -> name.length() >= 4)
.forEach(System.out::println);
Java 8 이전에는 익명 클래스가 필요했던 컬렉션 처리 코드를 Lambda와 Stream으로 선언적으로 작성할 수 있게 됐다.
Optional
repository.findById(id)
.map(User::getEmail)
.ifPresent(emailService::send);
Optional은 값의 부재 가능성을 API에서 드러내는 데 유용하다. 다만 모든 필드와 인자를 Optional로 감싸는 것이 항상 좋은 설계는 아니다.
java.time
LocalDate today = LocalDate.now();
ZonedDateTime seoul =
ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
기존 Date와 Calendar보다 명확하고 불변적인 날짜·시간 API를 사용할 수 있게 됐다.
Interface default method
인터페이스에 기본 구현을 넣을 수 있어 라이브러리 API의 하위 호환성을 유지하기 쉬워졌다.
Java 8 사용사례
- Spring Boot 1.x·2.x 레거시 서비스
- 전자정부·금융·공공 시스템
- Stream 기반 데이터 필터링·그룹화·집계
- 배치 통계 계산
- MyBatis·JPA 기반 CRUD API
- 기존 라이브러리 호환성이 중요한 장기 운영 시스템
장점
- 넓은 생태계와 많은 운영 레퍼런스
- 많은 개발자가 경험을 보유
- 기존 시스템을 유지하기 쉬움
- Lambda와 Stream으로 기존 Java보다 생산성 향상
Java 8의 부족한 부분
- record, sealed class, pattern matching 없음
- 표준 HTTP Client 없음
- virtual thread와 structured concurrency 없음
- 최신 Spring Boot·Jakarta 기반 라이브러리와 호환 제한 가능
- 최신 JVM 관측성과 보안·암호화 기본값을 활용하기 어려움
- 대규모 blocking I/O 동시성을 thread pool 중심으로 직접 설계해야 함
2. Java 11: 표준 API와 운영성 강화
Java 11은 Java 8 이후 기업에서 널리 채택된 LTS 버전이다.
표준 HTTP Client
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
외부 API 연동에서 오래된 HttpURLConnection이나 별도 HTTP 라이브러리에 대한 의존도를 줄일 수 있다.
String API 개선
"hello ".isBlank();
"hello\nworld".lines();
" hello ".strip();
"=".repeat(20);
문자열 공백, 줄 단위 처리, 반복 생성이 간결해졌다.
파일 API 개선
Path path = Path.of("config/application.yml");
String content = Files.readString(path);
Files.writeString(path, "server:\n port: 8080");
간단한 파일 읽기·쓰기에 필요한 보일러플레이트가 줄었다.
JFR과 운영 분석
Java 11에서는 Java Flight Recorder를 운영 JVM 분석에 활용하기 쉬워졌다. CPU, allocation, lock contention, GC, thread, I/O 관련 이벤트를 이용해 성능 문제를 조사할 수 있다.
Java 11 사용사례
- Spring Boot 2.x 기반 REST API
- 외부 REST API·HTTP/2 연동
- Docker·Kubernetes 기반 서비스
- Java 8에서 Java 17로 이동하기 전의 중간 단계
- JFR 기반 GC·CPU·thread 분석
장점
- Java 8보다 표준 API가 풍부함
- 표준 HTTP Client 사용 가능
- JFR 기반 운영 관측성 강화
- Java 8 코드를 비교적 적은 수정으로 이전 가능
- 기업 장기 운영 기준으로 선택하기 쉬움
Java 11의 부족한 부분
- record와 sealed class 없음
- switch expression과 pattern matching의 정식 기능 부족
- virtual thread와 structured concurrency 없음
- 최신 Stream·컬렉션 API 부족
- 최신 Spring Boot·Jakarta 생태계와의 호환성 제한 가능
3. Java 17: 현대 Java의 기준점
Record
public record UserResponse(
Long id,
String name,
String email
) {}
API response, 이벤트 payload, 불변 DTO처럼 데이터 전달이 주목적인 타입의 코드를 크게 줄일 수 있다.
Sealed class
public sealed interface Payment
permits CardPayment, BankPayment {
}
public final class CardPayment implements Payment {}
public final class BankPayment implements Payment {}
상속 가능한 타입을 제한해 도메인 상태와 허용된 구현을 명시할 수 있다. Sealed classes는 Java 17에서 정식 기능이 됐다.[3]
Pattern matching for instanceof
if (payment instanceof CardPayment card) {
card.authorize();
}
타입 검사와 변환을 한 표현식으로 작성해 중복 cast를 줄일 수 있다.
Switch expression
String result = switch (status) {
case READY -> "대기";
case PAID -> "결제완료";
case FAILED -> "실패";
};
상태를 값으로 변환하는 코드가 명확해진다.
Text block
String sql = """
SELECT id, name
FROM users
WHERE status = 'ACTIVE'
""";
SQL, JSON, HTML, YAML 같은 여러 줄 문자열을 읽기 쉽게 표현할 수 있다.
Java 17 사용사례
- Spring Boot 3.x 기반 MSA
- Jakarta EE 기반 애플리케이션
- record DTO와 이벤트 payload
- sealed class 기반 도메인 상태 모델
- Kubernetes·Docker 기반 백엔드
- JFR·GC·heap 분석이 필요한 운영 서비스
장점
- Java 8·11보다 현대적인 언어 표현력
- DTO와 값 객체 작성량 감소
- 도메인 타입 제한과 상태 분기 명확화
- Spring Boot 3의 대표적인 최소 기준
- 컨테이너 환경과 장기 운영에 적합
Java 17의 부족한 부분
- virtual thread 없음
- structured concurrency 없음
- record pattern 없음
- pattern matching for switch 정식화 전
- scoped values 없음
- Java 25의 AOT·JFR·object header·GC 개선 없음
- blocking I/O 동시성에서 platform thread pool 튜닝 필요
4. Java 21: 동시성과 타입 표현력의 큰 변화
Java 21은 Java 17 이후의 LTS 릴리스이며 virtual threads, record patterns, pattern matching for switch, sequenced collections 등을 포함한다.[1]
Virtual Threads
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> callExternalApi());
executor.submit(() -> queryDatabase());
}
Virtual thread는 blocking I/O 중 Java runtime이 해당 작업을 중단하고 carrier platform thread를 다른 작업에 사용할 수 있도록 한다.[4]
적합한 사용사례:
- 외부 REST API 호출
- blocking JDBC 조회
- 파일 I/O
- 메시지 소비
- thread-per-request 스타일의 고동시성 서버
단, virtual thread는 CPU 계산을 빠르게 하는 기능이 아니다. Oracle 문서도 virtual thread가 속도보다 확장성, 특히 대기 시간이 많은 고처리량 작업을 위한 기능이라고 설명한다.[4]
또한 DB connection pool, 외부 API rate limit, CPU가 병목이면 virtual thread를 늘려도 해결되지 않는다.
Record Patterns
if (command instanceof CreateUser(String name, String email)) {
userService.create(name, email);
}
record의 타입 검사와 내부 값 분해를 한 번에 표현할 수 있다.
Pattern Matching for switch 정식화
String message = switch (event) {
case UserCreated(var id, var name) ->
"created: " + id;
case UserDeleted(var id) ->
"deleted: " + id;
};
Java 21에서 record patterns와 pattern matching for switch가 정식화됐다.[3]
Java 17 이하와의 차이
Java 17에서는 record와 일부 pattern matching을 사용할 수 있지만, 대량 blocking I/O를 위한 virtual thread와 record 분해·pattern switch의 완성형을 사용할 수 없다.
5. Java 25: JVM 효율·관측·초기화 개선
Java 25는 2025년 9월 16일 GA된 LTS 릴리스다.[2]
Scoped Values 정식화
Java 21에서 preview였던 Scoped Values가 Java 25에서 정식 기능이 됐다.[2]
static final ScopedValue<String> TRACE_ID =
ScopedValue.newInstance();
ScopedValue.where(TRACE_ID, "trace-123")
.run(() -> service.call());
request context, trace ID, security context처럼 실행 범위에 묶인 값을 전달하는 방향에 적합하다. Java 8·11·17에서 흔히 사용하던 ThreadLocal은 thread pool 재사용과 값 정리를 별도로 주의해야 한다.
AOT와 JFR 개선
Java 25에는 다음 기능이 포함됐다.[2]
- Ahead-of-Time Command-Line Ergonomics
- Ahead-of-Time Method Profiling
- JFR CPU-Time Profiling
- JFR Cooperative Sampling
- JFR Method Timing & Tracing
컨테이너가 자주 시작되는 환경, warm-up 시간이 중요한 서비스, 운영 프로파일링이 필요한 JVM에서 검토할 수 있다. 단, 애플리케이션별 실제 효과는 startup·warm-up·CPU trace를 측정해야 한다.
Compact Object Headers
Compact Object Headers는 객체 헤더를 줄이는 방향의 기능이다.[2] 작은 객체가 매우 많이 생성되는 애플리케이션에서 heap 효율을 검토할 수 있지만, 일반적인 성능 향상 수치로 단정해서는 안 된다.
Generational Shenandoah
Java 25에는 Generational Shenandoah가 포함됐다.[2] 단명 객체와 장수 객체를 세대별로 다루는 GC 방향으로, 큰 heap과 pause time 요구사항이 있는 서비스에서 실측 대상으로 볼 수 있다.
Java 25의 preview·incubator 구분
Java 25의 모든 기능이 정식 기능인 것은 아니다. Structured Concurrency는 fifth preview, primitive types in patterns는 third preview, Vector API는 incubator로 표시되어 있다.[2]
따라서 preview 기능은 production 표준으로 즉시 채택하기보다 컴파일 옵션, 호환성, 향후 변경 가능성을 별도로 관리해야 한다.
6. 버전별 비교
| 영역 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25 |
|---|---|---|---|---|---|
| 함수형 프로그래밍 | Lambda·Stream | API 개선 | 최신 API | 유지·확장 | 유지·확장 |
| DTO | 일반 class | 일반 class | record | record pattern | compact source 방향 |
| 타입 모델 | class/interface | class/interface | sealed class | record pattern | primitive pattern preview |
| 분기 | 전통적 switch | 전통적 switch | switch expression | pattern switch 정식 | 추가 pattern 발전 |
| HTTP | 외부 라이브러리 중심 | 표준 HttpClient | 유지·개선 | 유지·개선 | 유지·개선 |
| 동시성 | platform thread | platform thread | platform thread | virtual thread | virtual thread + scoped values |
| context 전달 | 직접 관리 | 직접 관리 | 직접 관리 | Scoped Values preview | Scoped Values 정식 |
| 관측 | 기본 JVM 도구 | JFR 활용 | JVM 운영성 개선 | virtual thread 관측 | JFR profiling·tracing 개선 |
| GC/JVM | 전통적 선택 | 개선 | G1·ZGC 활용 | Generational ZGC | Generational Shenandoah·object header 방향 |
7. 사용사례별 추천
신규 Spring Boot MSA
최소 Java 17
가능하면 Java 21 또는 Java 25 검토
record DTO, sealed domain model, pattern matching, 최신 Spring·Jakarta 생태계를 활용하기 좋다.
I/O 중심 API 서버
Java 21 또는 Java 25
외부 API, blocking JDBC, 메시지 소비가 많다면 virtual thread를 검토할 수 있다. 단, connection pool과 rate limit이 병목인지 먼저 확인한다.
기존 Spring Boot 2.x 서비스
Java 11 → Java 17 단계적 이동
라이브러리 호환성, javax에서 jakarta로의 전환, reflection·proxy, JVM 옵션, Docker base image를 함께 확인한다.
레거시 공공·금융 시스템
Java 8 또는 11 유지 가능
현재 동작 여부만이 아니라 보안 패치, 벤더 지원, 프레임워크 지원 기간을 기준으로 판단한다.
대형 heap·낮은 pause time 서비스
Java 17 이상
Java 21·25의 GC와 JFR 개선을 workload로 비교
GC pause p95/p99, allocation rate, Full GC, CPU, heap, 실제 traffic replay를 비교해야 한다.
8. 마이그레이션 체크리스트
Java 8 → 11
- 컴파일·단위 테스트·통합 테스트
- deprecated API 확인
- TLS·인증서·security provider 확인
- 표준 HTTP Client 도입 후보 확인
- GC와 heap 비교
Java 11 → 17
- Spring·Jakarta 호환성 확인
- reflection과 module 접근 확인
- record 도입 후보 DTO 선정
- sealed class·switch expression 검토
- Docker base image 변경
- 운영 부하 테스트
Java 17 → 21
- blocking I/O 경로 확인
- virtual thread 적용 후보 선정
- JDBC pool과 thread pool 재검토
- synchronized pinning 가능성 확인
- timeout·취소·예외 전파 검증
Java 21 → 25
- 프레임워크와 라이브러리 지원 확인
- AOT·JFR·GC 개선을 실제 workload로 측정
- Compact Object Headers 영향 확인
- preview 기능의 production 적용 여부 분리
- startup·warm-up·heap·pause·CPU 비교
- rollback 가능한 이미지와 배포 준비
9. 결론
버전별 핵심 변화는 다음과 같다.
Java 8:
Lambda·Stream·Optional·java.time
→ 현대 Java 문법의 시작
Java 11:
표준 HTTP Client·JFR·문자열/파일 API
→ 표준 API와 운영성 강화
Java 17:
record·sealed class·switch expression·pattern matching
→ 현대 서버 애플리케이션의 기준점
Java 21:
virtual thread·record pattern·pattern switch
→ 동시성과 타입 표현력 개선
Java 25:
scoped values·AOT·JFR·object header·GC 개선
→ JVM 효율·관측·초기화 개선 방향
실무적인 선택은 다음과 같이 정리할 수 있다.
- 레거시 유지: Java 8·11
- 신규 Spring Boot 기준: Java 17
- 높은 I/O 동시성: Java 21
- 최신 LTS와 JVM 개선 활용: Java 25
Java 21·25로 올린다고 자동으로 성능이 좋아지는 것은 아니다. 다음 지표를 버전 업그레이드 전후로 비교해야 한다.
처리량
응답시간 p95/p99
GC pause
heap 사용량
CPU 사용률
thread 수
DB connection pool 대기
외부 API 대기
startup/warm-up 시간
Java 8에서 Java 17 이상으로의 업그레이드는 JDK만 교체하는 작업이 아니다. 프레임워크, 의존성, javax/jakarta, reflection, JVM 옵션, 컨테이너 이미지와 운영 부하까지 함께 검증하는 마이그레이션으로 봐야 한다.
참고 자료
[1] OpenJDK JDK 21 — Virtual Threads, Record Patterns, Pattern Matching for switch, Sequenced Collections
[2] OpenJDK JDK 25 — Scoped Values, AOT, JFR, Compact Object Headers, Generational Shenandoah
[3] Oracle Java Language Changes — Java 17·21의 Sealed Classes, Record Patterns, Pattern Matching for switch
[4] Oracle Virtual Threads — Virtual Thread의 동작, 적용 대상과 한계
출처
- OpenJDK JDK 21: https://openjdk.org/projects/jdk/21/
- OpenJDK JDK 25: https://openjdk.org/projects/jdk/25/
- Oracle Java Language Changes: https://docs.oracle.com/en/java/javase/21/language/java-language-changes.html
- Oracle Virtual Threads: https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html
Sources
[1] https://openjdk.org/projects/jdk/21 — OpenJDK JDK 21 [2] https://openjdk.org/projects/jdk/25 — OpenJDK JDK 25 [3] https://docs.oracle.com/en/java/javase/21/language/java-language-changes.html — Oracle Java Language Changes [4] https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html — Oracle Virtual Threads