Java

Java 22~26 새 기능 총정리 - Structured Concurrency부터 Primitive Types까지

백엔드 개발자 김승원 2026. 4. 7. 14:57

들어가며

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로의 마이그레이션 계획을 미리 수립하는 것을 권장합니다. 새로운 기능들은 실무에서의 생산성과 성능 모두를 크게 개선할 수 있습니다.