들어가며
Java는 6개월 릴리스 주기를 도입한 이후 매 버전마다 꾸준히 새로운 기능을 선보이고 있습니다. 특히 Java 22부터 26까지는 수년간 Preview로 다듬어온 기능들이 정식(Stable) 릴리스되는 중요한 전환기입니다.
이 글에서는 Java 22, 23, 24, 25, 26에 도입된 주요 기능을 버전별로 정리하고, 실전 코드 예제와 함께 백엔드 개발자에게 미치는 영향을 살펴봅니다.
Java 22 (2024년 3월)
Unnamed Variables & Patterns (JEP 456) - 정식
사용하지 않는 변수를 _(언더스코어)로 표현할 수 있습니다. 코드의 의도를 명확하게 전달합니다.
// Before: 사용하지 않는 변수에 이름을 붙여야 했음
try {
int result = Integer.parseInt(input);
} catch (NumberFormatException ex) { // ex를 사용하지 않음
System.out.println("Invalid number");
}
// After: 사용하지 않는 변수를 _ 로 표현
try {
int result = Integer.parseInt(input);
} catch (NumberFormatException _) {
System.out.println("Invalid number");
}
// for문에서 인덱스가 불필요한 경우
for (var _ : collection) {
count++;
}
// 패턴 매칭에서 일부 값만 필요한 경우
if (object instanceof Point(int x, int _)) {
System.out.println("x = " + x);
}
// switch 패턴에서 활용
switch (shape) {
case Circle(var radius) -> calculateCircleArea(radius);
case Rectangle(var w, var _) -> processWidth(w); // 높이 불필요
default -> 0.0;
}
Statements Before super() (JEP 447) - Preview
생성자에서 super() 호출 전에 문장을 실행할 수 있게 되었습니다. 인자 검증에 특히 유용합니다.
// Before: super() 전에 검증 불가, 정적 메서드 우회 필요
public class PositiveAmount extends Amount {
public PositiveAmount(long value) {
super(validatePositive(value)); // 별도 정적 메서드 필요
}
private static long validatePositive(long value) {
if (value <= 0) throw new IllegalArgumentException();
return value;
}
}
// After: super() 전에 직접 검증 가능
public class PositiveAmount extends Amount {
public PositiveAmount(long value) {
if (value <= 0) {
throw new IllegalArgumentException("Amount must be positive: " + value);
}
super(value); // 검증 후 호출
}
}
Stream Gatherers (JEP 461) - Preview
Stream API에 커스텀 중간 연산을 정의할 수 있는 gather()가 추가되었습니다.
// 고정 크기 윈도우로 스트림 분할
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6, 7)
.gather(Gatherers.windowFixed(3))
.toList();
// [[1, 2, 3], [4, 5, 6], [7]]
// 슬라이딩 윈도우
List<List<Integer>> sliding = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.toList();
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
// fold: 중간 결과를 스트림으로 발행
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(() -> 0, Integer::sum))
.forEach(System.out::println);
// 15 (최종 합계만 발행)
Java 23 (2024년 9월)
Markdown Documentation Comments (JEP 467) - Preview
JavaDoc 주석에서 Markdown 문법을 사용할 수 있습니다. 기존 HTML 태그 대비 가독성이 크게 향상됩니다.
/// 사용자 정보를 조회하는 서비스입니다.
///
/// ## 사용 예시
///
/// ```java
/// UserService service = new UserService(repository);
/// User user = service.findById(1L);
/// ```
///
/// ## 주의사항
/// - 존재하지 않는 ID 조회 시 `UserNotFoundException` 발생
/// - 캐시는 5분간 유지됨
///
/// @param id 사용자 고유 식별자
/// @return 사용자 정보 객체
/// @throws UserNotFoundException 사용자가 존재하지 않을 때
public User findById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
Flexible Constructor Bodies (JEP 482) - Second Preview
Java 22의 Statements Before super()를 확장하여, super() 호출 전에 필드 초기화까지 가능해졌습니다.
public class CachedRepository extends AbstractRepository {
private final Cache cache;
public CachedRepository(DataSource dataSource, CacheConfig config) {
// super() 전에 필드 초기화 가능
this.cache = Cache.create(config);
super(dataSource);
}
}
Primitive Types in Patterns (JEP 455) - Preview
패턴 매칭에서 primitive 타입을 직접 사용할 수 있게 되었습니다.
// switch에서 primitive 패턴 매칭
int statusCode = response.getStatusCode();
String message = switch (statusCode) {
case 200 -> "OK";
case 201 -> "Created";
case 400 -> "Bad Request";
case 404 -> "Not Found";
case 500 -> "Internal Server Error";
default -> "Unknown: " + statusCode;
};
// instanceof로 범위 확인 패턴
Object value = getMetric();
if (value instanceof int i && i > 0) {
processPositiveInt(i);
} else if (value instanceof long l) {
processLong(l);
} else if (value instanceof double d) {
processDouble(d);
}
Java 24 (2025년 3월)
Java 24는 JVM 성능과 보안에 초점을 맞춘 릴리스입니다.
Compact Object Headers (JEP 450) - Experimental
객체 헤더 크기를 기존 96~128비트에서 64비트로 줄여 메모리 사용량을 절감합니다. 대량의 작은 객체를 사용하는 애플리케이션에서 10~20% 힙 메모리 절약이 가능합니다.
// JVM 옵션으로 활성화
// java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders MyApp
// 이전: Object 헤더 = mark word(64bit) + class pointer(32/64bit)
// 이후: Object 헤더 = 64bit 통합
// 메모리 절감 효과가 큰 경우
// - 수백만 개의 작은 객체 (ex: Cache Entry, DTO)
// - ArrayList/HashMap 내부 Node 객체
// - String 객체가 많은 애플리케이션
Ahead-of-Time Class Loading & Linking (JEP 483)
JVM 시작 시 클래스를 AOT 캐시에서 로딩하여 시작 시간을 대폭 단축합니다.
# 1. 학습 실행 - 클래스 로딩 패턴 기록
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -jar myapp.jar
# 2. AOT 캐시 생성
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot -jar myapp.jar
# 3. AOT 캐시를 사용하여 빠른 시작
java -XX:AOTCache=app.aot -jar myapp.jar
# Spring Boot 애플리케이션의 경우 시작 시간 30~50% 단축 가능
Scoped Values (JEP 487) - Fourth Preview
ThreadLocal의 한계를 극복하는 불변 스레드 지역 값입니다. Virtual Thread와 함께 사용하기에 적합합니다.
// ScopedValue 선언
private static final ScopedValue<UserContext> CURRENT_USER = ScopedValue.newInstance();
// 값 바인딩 및 사용
public void handleRequest(HttpRequest request) {
UserContext user = authenticate(request);
ScopedValue.runWhere(CURRENT_USER, user, () -> {
// 이 블록 내에서 CURRENT_USER 접근 가능
processOrder();
});
// 블록 밖에서는 자동으로 바인딩 해제
}
public void processOrder() {
// 호출 스택 어디서든 접근 가능
UserContext user = CURRENT_USER.get();
System.out.println("Processing order for: " + user.name());
}
// ThreadLocal과의 비교
// ThreadLocal: 가변(mutable), 명시적 remove() 필요, 메모리 누수 위험
// ScopedValue: 불변(immutable), 자동 해제, Virtual Thread 최적화
Stream Gatherers (JEP 485) - 정식
Java 22에서 Preview였던 Stream Gatherers가 Java 24에서 정식 기능이 되었습니다.
// 커스텀 Gatherer 구현 예제: 중복 제거 (연속된 동일 값)
Gatherer<String, ?, String> dedup = Gatherer.ofSequential(
() -> new Object() { String last = null; },
(state, element, downstream) -> {
if (!element.equals(state.last)) {
state.last = element;
return downstream.push(element);
}
return true;
}
);
List<String> result = Stream.of("a", "a", "b", "b", "b", "c", "a")
.gather(dedup)
.toList();
// ["a", "b", "c", "a"]
Java 25 (2025년 9월 - LTS 예정)
Java 25는 LTS(Long-Term Support) 릴리스로 예정되어 있어, 엔터프라이즈 환경에서 특히 중요합니다.
Structured Concurrency (JEP 예정) - 정식 릴리스 기대
여러 개의 동시 작업을 하나의 단위로 관리합니다. Virtual Thread와 결합하여 안정적인 동시성 프로그래밍이 가능합니다.
// Structured Concurrency로 병렬 API 호출
public record UserProfile(User user, List<Order> orders, int loyaltyPoints) {}
public UserProfile fetchUserProfile(long userId) throws Exception {
try (var scope = StructuredTaskScope.open()) {
// 세 가지 작업을 병렬 실행
Subtask<User> userTask = scope.fork(() -> userService.findById(userId));
Subtask<List<Order>> ordersTask = scope.fork(() -> orderService.findByUserId(userId));
Subtask<Integer> pointsTask = scope.fork(() -> loyaltyService.getPoints(userId));
// 모든 작업 완료 대기
scope.join();
// 결과 조합
return new UserProfile(
userTask.get(),
ordersTask.get(),
pointsTask.get()
);
}
// scope 종료 시 미완료 작업 자동 취소
}
// ShutdownOnFailure: 하나라도 실패하면 나머지 취소
public UserProfile fetchUserProfileOrFail(long userId) throws Exception {
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Joiner.awaitAllSuccessfulOrThrow())) {
Subtask<User> userTask = scope.fork(() -> userService.findById(userId));
Subtask<List<Order>> ordersTask = scope.fork(() -> orderService.findByUserId(userId));
scope.join(); // 하나라도 실패하면 ExecutionException 발생
return new UserProfile(userTask.get(), ordersTask.get(), 0);
}
}
Flexible Constructor Bodies - 정식 기대
여러 버전에 걸쳐 Preview였던 유연한 생성자 본문이 정식화될 것으로 예상됩니다.
Java 26 (2026년 3월)
Java 26에서는 그동안 인큐베이터/프리뷰로 다듬어온 기능들의 정식 릴리스가 기대됩니다.
Primitive Types in Patterns - 정식 기대
패턴 매칭에서 primitive 타입을 완벽하게 지원합니다.
// sealed interface + primitive 패턴
sealed interface ApiResponse permits Success, ClientError, ServerError {}
record Success(int code, String body) implements ApiResponse {}
record ClientError(int code, String message) implements ApiResponse {}
record ServerError(int code, String message) implements ApiResponse {}
public String handleResponse(ApiResponse response) {
return switch (response) {
case Success(int code, var body) when code == 200 ->
"OK: " + body;
case Success(int code, var body) when code == 201 ->
"Created: " + body;
case ClientError(int code, var msg) when code == 404 ->
"Not Found: " + msg;
case ClientError(int code, var msg) ->
"Client Error " + code + ": " + msg;
case ServerError(_, var msg) ->
"Server Error: " + msg;
};
}
Scoped Values - 정식 기대
// Spring MVC에서 ScopedValue 활용 패턴
public class RequestContextHolder {
private static final ScopedValue<RequestContext> CONTEXT = ScopedValue.newInstance();
public static RequestContext current() {
return CONTEXT.get();
}
public static void runInContext(RequestContext ctx, Runnable task) {
ScopedValue.runWhere(CONTEXT, ctx, task);
}
}
// 인터셉터에서 요청 컨텍스트 바인딩
public class ContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
RequestContext ctx = new RequestContext(
extractTraceId(request),
extractUserId(request)
);
RequestContextHolder.runInContext(ctx, () -> {
// 컨트롤러 실행
});
return true;
}
}
버전별 주요 기능 요약
| 버전 | 주요 기능 | 상태 |
|---|---|---|
| Java 22 | Unnamed Variables, Stream Gatherers(Preview), Statements Before super() | GA (2024.3) |
| Java 23 | Markdown Doc Comments, Flexible Constructor Bodies, Primitive Patterns | GA (2024.9) |
| Java 24 | Compact Object Headers, AOT Class Loading, Scoped Values, Stream Gatherers(정식) | GA (2025.3) |
| Java 25 | Structured Concurrency(정식 기대), LTS 릴리스 | 2025.9 예정 |
| Java 26 | Primitive Types in Patterns(정식 기대), Scoped Values(정식 기대) | 2026.3 예정 |
실무 마이그레이션 가이드
버전 업그레이드 체크리스트
// build.gradle (Gradle)
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
// pom.xml (Maven)
<properties>
<java.version>25</java.version>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
- 1단계: CI에서 새 JDK로 빌드/테스트 확인
- 2단계: 의존성 라이브러리 호환성 확인 (Spring Boot, Hibernate 등)
- 3단계: Deprecated API 사용 부분 수정
- 4단계: 새 기능을 점진적으로 도입 (코드 리뷰를 통해)
마치며
Java 22부터 26까지의 변화를 살펴보면, 크게 세 가지 방향이 보입니다:
- 개발자 경험 향상: Unnamed Variables, Markdown Doc Comments, Flexible Constructor Bodies 등으로 코드의 가독성과 표현력이 높아졌습니다
- 동시성 프로그래밍 혁신: Structured Concurrency와 Scoped Values는 Virtual Thread와 결합하여 Java의 동시성 프로그래밍 패러다임을 근본적으로 바꾸고 있습니다
- JVM 성능 최적화: Compact Object Headers와 AOT Class Loading은 별도의 코드 변경 없이 성능 향상을 제공합니다
특히 Java 25가 LTS로 예정되어 있어, 현재 Java 17/21 LTS를 사용하는 프로젝트라면 Java 25로의 마이그레이션 계획을 미리 수립하는 것을 권장합니다. 새로운 기능들은 실무에서의 생산성과 성능 모두를 크게 개선할 수 있습니다.
'Java' 카테고리의 다른 글
| Clean Code 실전 - 레거시 코드를 리팩토링하는 7가지 패턴 (0) | 2026.04.13 |
|---|---|
| Java 디자인 패턴 실전 - 실무에서 자주 쓰는 10가지 패턴 (0) | 2026.04.07 |
| JVM 메모리 구조와 GC 튜닝 - G1, ZGC, Shenandoah 완벽 비교 (0) | 2026.04.07 |
| Java 동시성 프로그래밍 완벽 가이드 - synchronized부터 Virtual Threads까지 (0) | 2026.03.30 |
| Java 17~21 새 기능 총정리 - 실무에서 바로 쓸 수 있는 핵심 기능들 (0) | 2026.03.25 |