LabHub

블로그

모던 Java 2026 — Java 25 LTS / Spring Boot 3.5 / Quarkus / 가상 스레드 / GraalVM 25 / Loom / Panama 심층 가이드

한국어English日本語

모던 Java 2026 — Java 25 LTS / Spring Boot 3.5 / Quarkus 심층 가이드

2026년의 Java는 더 이상 "보수적인 엔터프라이즈 언어"가 아니다. Java 21 LTS(2023.9)에서 가상 스레드가 GA로 들어왔고, Java 25 LTS(2025.9)에서는 패턴 매칭이 완성되었으며 가상 스레드는 사실상 기본 모델이 되었다. GraalVM 25 네이티브 이미지는 Spring Boot 3.5에서 일등 시민이 되었고, Quarkus 3.20은 람다와 K8s에서 supersonic / subatomic 부팅 시간을 제공한다. 동시에 토스·카카오 페이·Mercari·LINE·NTT Data·楽天 같은 대형 트래픽 서비스가 여전히 Java/Kotlin/Spring으로 코어를 굴리고 있다. 이 글은 2026년 5월 기준으로 모던 Java 생태계 전체를 한 번에 정리한다.


1. 2026년 모던 Java — Java 21 LTS가 바꾼 것, Java 25 LTS가 마무리한 것

먼저 시계열로 정리하면 다음과 같다.

핵심 메시지는 두 가지다. (1) 2026년에 새로 짓는 Java 서비스는 Java 21 또는 Java 25 LTS가 표준이고, Java 17은 "유지 보수 모드". (2) 동시성은 더 이상 "스레드 풀 튜닝" 문제가 아니라 블로킹 코드를 자연스럽게 쓰면 된다는 모델이 보편화됐다. 리액티브 코드의 "콜백 지옥" 시대가 끝났다.


2. Java 25 LTS (2025.9) — 가상 스레드 default, Pattern matching, Module imports

Java 25 LTS의 가장 큰 변화 세 가지를 보자.

2.1 가상 스레드(Virtual Threads)가 사실상 기본값

Java 21에서 GA로 들어온 가상 스레드는 Java 25 LTS에서 "Synchronize Virtual Threads without Pinning"(JEP 491) 등이 안정화되면서 사실상 모든 새 코드의 디폴트가 됐다. 핵심 API는 다음과 같다.

// Java 25 — 가상 스레드를 그대로 쓰는 가장 간단한 예
Thread.startVirtualThread(() -> {
    var response = httpClient.send(request, BodyHandlers.ofString());
    log.info("got {}", response.statusCode());
});

// Executor 형태
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i ->
        executor.submit(() -> fetchUser(i))
    );
} // 자동 join

10,000개의 동시 HTTP 호출을 만들어도 OS 스레드는 수십 개 수준이다. 가상 스레드는 JVM이 carrier 스레드(=ForkJoinPool)에서 협력적으로 마운트/언마운트한다.

2.2 Pattern Matching for switch (확정)

switch가 데이터 형태에 따라 분기하는 진짜 패턴 매칭이 된다.

sealed interface Shape permits Circle, Rect, Triangle {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
record Triangle(double b, double h) implements Shape {}

double area(Shape s) {
    return switch (s) {
        case Circle(double r)         -> Math.PI * r * r;
        case Rect(double w, double h) -> w * h;
        case Triangle(double b, double h) -> 0.5 * b * h;
    };
}

sealed interface + record deconstruction + exhaustiveness 체크가 한 묶음으로 들어와서, Scala/Kotlin/Rust에 익숙한 사람이 보던 match와 거의 동일하다. default가 필요 없다는 점이 큰 차이.

2.3 Module Import Declarations (preview)

매 파일 상단의 import java.util.*; import java.util.concurrent.*; 더미를 한 줄로 줄인다.

import module java.base;
import module java.net.http;

void main() {
    var client = HttpClient.newHttpClient();
    var req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
}

JEP 477(Implicit Classes / Instance Main)과 합쳐지면 작은 스크립트는 거의 Python 수준으로 짧아진다. 학습용·jbang 친화적.


3. Project Loom — 가상 스레드 + Structured Concurrency 실전

Loom의 진짜 가치는 두 가지가 한 묶음으로 들어왔다는 것이다.

3.1 가상 스레드의 동작 모델

가상 스레드는 OS 스레드가 아니라 JVM 힙에 사는 객체다. JVM이 carrier(=플랫폼 스레드) 위에 mount 했다가, I/O 같은 블로킹 지점에서 unmount 하고 다른 가상 스레드를 mount 한다. 핵심 함의는 다음과 같다.

3.2 Structured Concurrency(JEP 480 / 505)

여러 동시 작업을 부모-자식 구조로 묶는다. 자식이 실패하면 형제도 같이 취소된다.

import jdk.incubator.concurrent.StructuredTaskScope;

record UserDashboard(User user, List<Order> orders, List<Notice> notices) {}

UserDashboard build(long userId) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        var userF    = scope.fork(() -> userService.findById(userId));
        var ordersF  = scope.fork(() -> orderService.findByUser(userId));
        var noticesF = scope.fork(() -> noticeService.findByUser(userId));

        scope.join();          // 모두 끝날 때까지 대기
        scope.throwIfFailed(); // 하나라도 실패하면 전파

        return new UserDashboard(userF.get(), ordersF.get(), noticesF.get());
    }
}

세 작업이 진정한 의미에서 부모와 lifecycle 을 공유한다. 부모가 인터럽트되면 자식도 같이 죽고, 스택 트레이스도 부모-자식 관계가 보존된다. 실무에서는 "한 요청 = 한 scope"가 표준이 된다.

3.3 Scoped Values(JEP 506)

ThreadLocal을 대체하는, 가상 스레드 환경에서 안전하고 효율적인 컨텍스트 전파 메커니즘.

private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

void handle(HttpRequest req) {
    ScopedValue.where(REQUEST_ID, req.header("x-request-id"))
        .run(() -> business());
}

void logInfo() {
    log.info("request-id={}", REQUEST_ID.get());
}

ThreadLocal과 달리 (1) 불변이고 (2) scope 가 끝나면 자동 해제되며 (3) 가상 스레드 간 복사 비용이 거의 없다. 가상 스레드를 본격적으로 쓴다면 ThreadLocal 은 신중하게 줄여 가는 게 좋다.


4. Spring Boot 3.5 — Native + GraalVM 통합

Spring Boot 3.5(2025.5 GA)는 Java 17 minimum, Java 21/25 권장, Jakarta EE 11 기반이다. 핵심 포인트.

4.1 가상 스레드를 한 줄로 켠다

# application.properties
spring.threads.virtual.enabled=true

이 한 줄이면 Tomcat / Jetty / Undertow 가 요청 핸들러를 가상 스레드로 실행한다. @Async 의 기본 풀도 가상 스레드 기반으로 전환할 수 있다.

@Configuration
class AsyncConfig implements AsyncConfigurer {
    @Override public Executor getAsyncExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
}

DB 커넥션 풀(HikariCP), Reactor Netty 클라이언트, 외부 HTTP 호출은 그대로 사용 가능. 단 synchronized 가 많은 라이브러리는 Java 25 권장.

4.2 GraalVM Native Image 정식 지원

# Spring Boot + GraalVM native image
./mvnw -Pnative native:compile

# 또는 cloud-native buildpack
./mvnw spring-boot:build-image -Pnative

Spring Boot 3.5는 @RegisterReflectionForBinding 같은 hint 어노테이션이 정착해 있어서, 대부분의 Spring Data / Jackson / JPA 코드는 hint 없이도 native 컴파일된다.

4.3 Observability 1급 시민

Micrometer + Tracing(Brave/OpenTelemetry) 이 기본 의존성에 들어 있다. OTel Java agent 없이도 @Observed 어노테이션 하나로 span 이 생긴다.

@Service
class OrderService {
    @Observed(name = "order.create")
    public Order create(OrderRequest req) { /* ... */ }
}

OTel collector 로 보내려면 management.otlp.tracing.endpoint 한 줄 추가하면 된다.


5. Quarkus 3.20 — Supersonic Subatomic Java

Quarkus(Red Hat)는 Kubernetes-native Java를 표방한다. 핵심 차별점.

5.1 부팅 시간 / 메모리 우위

이 숫자가 Knative / Lambda / Cloud Run 같은 serverless 환경과 잘 맞는다. scale-to-zero 가 의미 있게 작동한다.

5.2 개발 경험 — Dev Mode + Continuous Testing

./mvnw quarkus:dev

5.3 코드 예시 — REST + Panache + Reactive

@Path("/users")
public class UserResource {

    @GET @Path("/{id}")
    public Uni<User> get(@PathParam("id") Long id) {
        return User.<User>findById(id);
    }

    @POST
    public Uni<Response> create(User user) {
        return Panache.withTransaction(user::persist)
            .replaceWith(Response.created(URI.create("/users/" + user.id)).build());
    }
}

Uni/Multi(SmallRye Mutiny)가 기본 reactive 타입이다. 단, Quarkus 3.x부터는 RESTEasy Reactive + Hibernate Reactive를 쓰지 않고 일반 blocking 모드를 가상 스레드와 함께 쓰는 패턴이 일반화됐다.

5.4 확장 생태계

quarkus- 접두어 확장이 600개 이상. Kafka, gRPC, Redis, Vault, Keycloak, Kubernetes-config, OpenAPI, MicroProfile 전체 — 거의 모든 게 1급으로 들어와 있다.


6. Micronaut 4 — Reactor 친화

Micronaut 4 의 차별점은 컴파일 타임 DI다. 리플렉션 대신 어노테이션 프로세서로 빈 메타데이터를 생성하기 때문에, native image 가 잘 맞는다.

@Controller("/hello")
public class HelloController {
    @Get(uri = "/{name}", produces = MediaType.TEXT_PLAIN)
    public String hello(String name) {
        return "Hello, " + name;
    }
}

토스 / 카카오 같은 회사들은 Spring 이 절대 다수지만, 일부 신규 서비스에 Micronaut 를 도입해 콜드 스타트를 낮춘 사례가 보고되고 있다.


7. Helidon 4 / Vert.x 5 — 그 외 옵션

7.1 Helidon 4 (Oracle)

Oracle 이 주도하는 microservices 프레임워크. Helidon Nima 라는 새 웹 엔진은 처음부터 가상 스레드 위에서 동작하도록 설계됐다(Netty 사용 안 함).

public class Main {
    public static void main(String[] args) {
        WebServer.builder()
            .routing(r -> r.get("/hello", (req, res) -> res.send("Hello")))
            .build()
            .start();
    }
}

Netty 없이 가상 스레드를 직접 carrier 로 쓰기 때문에, 동시성 모델이 가장 단순하다. JVM 메모리 사용량도 매우 낮다. Oracle Cloud 환경과 잘 맞는다.

7.2 Vert.x 5

이벤트 루프 기반의 reactive toolkit. 한 노드에서 매우 높은 동시 연결을 처리해야 할 때 유리하다.

Vertx vertx = Vertx.vertx();
vertx.createHttpServer()
    .requestHandler(req -> req.response().end("Hello"))
    .listen(8080);

Vert.x 5 는 reactive 와 함께 가상 스레드 모드도 지원해서, 콜백 지옥 없이도 쓸 수 있다. 메시지 게이트웨이, IoT 게이트웨이, WebSocket 서버처럼 연결 수가 폭발하는 워크로드의 단골.


8. ZGC — sub-ms pause GC

Java 25 LTS 기준으로 Generational ZGC가 기본 GC가 됐다.

8.1 왜 중요한가

기존 G1 GC 가 수십~수백 ms의 STW(stop-the-world) pause 를 낼 수 있는 반면, ZGC 는 GC pause 가 1ms 이하다. 힙 크기가 수십~수백 GB여도 동일한 pause 특성을 보인다.

8.2 켜는 법

java -XX:+UseZGC -Xmx16g -jar app.jar

Java 21 부터는 -XX:+ZGenerational 로 generational 모드를 켰는데, Java 25 LTS 에서는 그게 디폴트가 됐다.

8.3 트레이드오프

토스의 결제 게이트웨이, Mercari의 주문 처리 같이 p99 가 SLA 인 경우 ZGC가 사실상 표준이다.


9. Project Valhalla — value types

Valhalla 는 여전히 "곧 나온다"의 영역이지만, 2026년 시점에서는 value types(JEP 401 / 402) 가 preview 단계까지 와 있다.

9.1 무엇이 바뀌나

value class Point {
    int x;
    int y;
}

9.2 왜 중요한가

지금까지 Java 의 객체는 무조건 힙에 포인터로 살았다. 수치 컴퓨팅, ML 텐서, 게임 엔진처럼 "수십 만개의 작은 객체"를 다루는 워크로드에서는 박싱과 캐시 미스 때문에 C++/Rust 대비 큰 손해를 봤다. Valhalla 가 GA 되면 그 격차가 상당 부분 사라진다. 2026년 기준으로 production 적용은 시기상조지만, ML 라이브러리(DJL 등)는 이미 준비 중이다.


10. Project Panama (FFI, Java 22 GA) — jextract와 함께

Panama 의 Foreign Function & Memory API(FFM)는 Java 22에서 GA 됐다. JNI 의 후계자이자 훨씬 안전한 대안.

10.1 기본 사용 — C 함수 호출

import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

try (Arena arena = Arena.ofConfined()) {
    Linker linker = Linker.nativeLinker();
    SymbolLookup stdlib = linker.defaultLookup();

    MethodHandle strlen = linker.downcallHandle(
        stdlib.find("strlen").orElseThrow(),
        FunctionDescriptor.of(JAVA_LONG, ADDRESS)
    );

    MemorySegment cString = arena.allocateUtf8String("Hello, Panama!");
    long len = (long) strlen.invoke(cString);
    System.out.println(len); // 14
}

JNI 와 비교한 장점.

10.2 jextract — C 헤더에서 Java 바인딩 자동 생성

# 예: SQLite 의 C 헤더에서 Java 바인딩 생성
jextract --source --output gen \
  -t org.sqlite.ffi \
  /usr/include/sqlite3.h

org.sqlite.ffi.sqlite3_h 같은 클래스가 생성되고, sqlite3_open/sqlite3_exec 같은 함수가 Java MethodHandle 로 노출된다. 결과적으로 JNI 없이도 native 라이브러리를 사용할 수 있다.

10.3 적용 사례

JNI 시절에는 "성능 때문에 어쩔 수 없이" 썼지만, Panama 시대에는 "기본 옵션"이 됐다.


11. Project Babylon — JVM의 GPU/AI 컴퓨트

Babylon(2024~)은 OpenJDK 의 새 프로젝트. Code Reflection 이라는 기능을 통해 Java 코드를 다른 프로그래밍 모델(GPU, SQL, 미분 가능 함수 등)로 변환할 수 있게 한다.

11.1 동기

지금까지 Java 에서 GPU 를 쓰려면 (1) JNI 로 CUDA 콜, (2) ONNX Runtime / TornadoVM 같은 외부 도구를 거쳤다. Babylon 의 목표는 순수 Java 코드를 그대로 GPU 커널로 컴파일할 수 있는 표준 API 를 제공하는 것.

11.2 그림으로

Java source           Reflected method body (IR)        Target backend
@CodeReflection  ────►  Op tree (SSA)             ────►  SPIR-V / PTX / SQL / ...
void matmul(...) { }

Babylon 은 라이브러리 작성자들이 자신만의 어노테이션 + IR consumer 를 만들도록 하는 인프라다. 예를 들어 TornadoVM 이나 HAT(Heterogeneous Accelerator Toolkit) 가 Babylon 위에서 동작한다.

11.3 2026년의 의미

Java 는 AI/ML 의 "서비스 계층" 언어로 자리를 굳혔지만, "학습/추론 계층"에서는 항상 Python 에 밀렸다. Babylon 이 안정화되면, Java 에서 직접 GPU 커널을 쓸 수 있게 되어 추론 서비스의 일부를 native 화하는 길이 열린다. 아직 incubating 단계지만, 방향이 옳다.


12. GraalVM 25 — native image

GraalVM 25 (2025.9)는 OpenJDK 25 와 같이 릴리스됐다. 핵심 가치는 두 가지.

12.1 Native Image

# Spring Boot / Quarkus / Micronaut 어느 쪽이든
native-image --no-fallback -jar app.jar

Spring Boot 3.5 + GraalVM 25 조합은 reachability metadata 가 자동 수집되어, 대부분의 Spring 표준 코드는 hint 없이 native 화된다.

12.2 Polyglot

GraalVM 위에서 JavaScript / Python / Ruby / Wasm / R / LLVM bitcode 가 같이 돈다. Java 함수에서 JS 함수를 그대로 호출 가능. 예시.

try (Context ctx = Context.create("js")) {
    Value array = ctx.eval("js", "[1,2,3]");
    System.out.println(array.getArraySize()); // 3
}

실무에서는 polyglot 보다 native image 쪽이 훨씬 자주 쓰인다.

12.3 Oracle GraalVM vs GraalVM CE

Java 21 부터 Oracle GraalVM 이 GFTC 라이선스(서비스 자유 사용 + GraalVM 의 fast-path 컴파일러 포함)로 풀렸다. CE 와 EE 의 구분이 사라지고, 사실상 무료로 production 에 쓸 수 있다.


13. Maven 4 + Gradle 9 + jbang

13.1 Maven 4

10년 만의 메이저 업그레이드. 핵심 변화.

기존 pom.xml 은 거의 그대로 동작하므로, 큰 마이그레이션 부담은 없다.

13.2 Gradle 9

plugins {
    id("org.springframework.boot") version "3.5.0"
    id("io.spring.dependency-management") version "1.1.6"
    kotlin("jvm") version "2.1.0"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
}

13.3 jbang — Java 스크립트화

# 단일 파일에서 Spring Boot 앱 실행
jbang hello.java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.7.6

import picocli.CommandLine;
import picocli.CommandLine.Command;

@Command(name = "hello")
public class hello implements Runnable {
    public void run() { System.out.println("Hello from jbang!"); }
    public static void main(String[] args) { new CommandLine(new hello()).execute(args); }
}

//DEPS 주석으로 의존성을 선언하고, Maven 의 dependency resolver 가 자동으로 가져온다. CLI 도구, CI 자동화 스크립트, ad-hoc 데이터 처리에 매우 유용. Java 25 의 Implicit Classes(JEP 477) 와 함께 쓰면 거의 Python 수준으로 짧다.


14. 한국 / 일본 — 토스, 카카오 페이, Mercari, LINE, NTT Data, 楽天

14.1 한국

14.2 일본

공통점은 "Java/Kotlin/Spring 모노리스 + Kafka 이벤트 드리븐 + 일부 신규 서비스에 가상 스레드 / Native Image"라는 패턴이다.


15. 누가 Java/Spring을 골라야 하나 — 엔터프라이즈 / 금융 / 모노리스 / 멀티-언어 팀

15.1 강력 추천

15.2 조건부 추천

15.3 비추천

15.4 Java vs Kotlin

같은 JVM 위지만 선택 기준이 다르다. Java 는 "조직 표준이 Java" 또는 "라이브러리가 Java 위주"일 때, Kotlin 은 "DSL 이 많은 도메인" 또는 "Android 와 코드를 공유"할 때. Spring 은 Kotlin 도 1급 지원하므로 신규 서비스에서 Kotlin 을 채택하는 한국·일본 회사가 빠르게 늘고 있다.


참고 / References

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다