JDK 25(LTS) — 정식이 된 언어 기능과 런타임 변화
JDK 25는 2025-09-16 GA한 LTS(장기 지원) 버전입니다. JDK 21 다음 LTS라, 운영에서 21에 머무르던 팀이 다음으로 올릴 목적지입니다. 이번엔 preview를 거쳐 정식(final)이 된 언어 기능이 여럿이라, 실무 코드에 바로 쓸 수 있는 것 위주로 정리합니다(아직 preview인 것은 뒤에서 따로 표시).
LTS에서 눈여겨볼 것은 미리보기 기능이 아니라 정식으로 확정된 기능입니다 — JDK 25에서 final이 된 것은--enable-preview 없이 바로 쓸 수 있습니다.
1. 모듈 임포트 — import module 한 줄 (JEP 511, 정식)
파일 맨 위 import 더미를 줄여 줍니다. 한 모듈이 export하는 모든 패키지를 한 줄로 가져옵니다.
// 기존 — 스트림 몇 줄 쓰려고 import가 코드만큼 길다
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// JDK 25 — java.base가 export하는 패키지를 한 번에
import module java.base;
import module java.base;는 on-demand 패키지 임포트 54개와 같은 효과입니다. 프로토타이핑·학습·여러 패키지에 걸친 라이브러리를 쓸 때 특히 편합니다.
2. 간결한 소스 파일 + 인스턴스 main (JEP 512, 정식)
"Hello World"를 쓰려면 클래스 선언·public static void main(String[] args)·System.out.println이 필요했습니다. JDK 25는 이 의식을 걷어냅니다.
// 클래스 선언도, static도, String[] args도 없이
void main() {
IO.println("Hello, JDK 25");
}
- 클래스 없이 파일에 바로 인스턴스
main()을 둘 수 있고(컴파일러가 감춰진 클래스를 만듦),String[] args도 필요 없을 때 생략합니다. - 새
java.lang.IO의IO.println으로 출력합니다(System.out을 몰라도 됨). java Hello.java로 컴파일 없이 실행하던 흐름과 맞물려, 입문·스크립트·예제의 진입 장벽을 낮춥니다.
3. 유연한 생성자 본문 (JEP 513, 정식)
그동안 생성자에서 super(...)/this(...)는 반드시 첫 문장이어야 했습니다. 그래서 인자 검증이나 필드 준비를 생성자 앞에서 못 하고, 편법(정적 헬퍼 메서드 등)을 썼습니다. JDK 25는 super() 앞에 문장을 허용합니다.
class Employee extends Person {
Employee(int age) {
// 이제 super() 앞에서 검증·가공 가능
if (age < 18) {
throw new IllegalArgumentException("미성년 불가");
}
int normalized = Math.max(age, 18);
super(normalized); // 검증을 통과한 값으로 부모 생성자 호출
}
}
부모 객체를 만들기 전에 잘못된 인자를 걸러 낼 수 있어, 불필요한 상위 초기화·부작용을 막습니다.
"검증은 super() 뒤에서"라는 오래된 제약이 사라져, 잘못된 입력을 부모 초기화 전에 차단할 수 있습니다.4. Scoped Values — ThreadLocal의 대안 (JEP 506, 정식)
요청 컨텍스트(현재 사용자·트랜잭션 ID 등)를 메서드 인자로 안 넘기고 공유할 때 ThreadLocal을 썼습니다. 그런데 ThreadLocal은 가변이고 정리(remove)를 깜빡하면 누수되며, 가상 스레드가 수백만 개면 부담이 됩니다. ScopedValue 는 불변 + 정해진 범위 안에서만 유효한 대안입니다.
final static ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
// where(...)로 값을 바인딩하고, run(...) 블록 안에서만 유효
ScopedValue.where(CURRENT_USER, user).run(() -> {
handle(); // 이 안 어디서든
});
void handle() {
User u = CURRENT_USER.get(); // 바인딩된 값 조회 (범위 밖이면 예외)
}
- 블록이 끝나면 자동으로 풀려 누수가 없고, 값이 불변이라 예측 가능합니다.
- 가상 스레드(가상 스레드는 JDK 21에서 정식에서 스레드 풀 맥락으로 언급)·구조적 동시성과 궁합이 좋습니다 — 하위 작업으로 값이 안전하게 전달됩니다.
5. 런타임 — 메모리·GC·시작 속도
언어 기능 외에 JVM 쪽도 정식화된 게 많습니다(모두 JDK 25 final).
- Compact Object Headers(JEP 519) — 객체 헤더를 줄여 힙 사용량을 절감합니다. 객체가 많은 애플리케이션에서 메모리·GC 부담이 내려갑니다.
- Generational Shenandoah(JEP 521) — Shenandoah GC가 세대 구분을 지원해, 짧게 사는 객체가 많은 워크로드에서 효율이 오릅니다.
- AOT 관련(JEP 514·515) — Ahead-of-Time 커맨드라인 정리·메서드 프로파일링으로 시작 속도를 개선합니다(Project Leyden 흐름).
- JFR 개선(협조적 샘플링·CPU-Time 프로파일링 등)으로 프로파일링이 더 정확해집니다.
아직 preview — 실험만, 운영 금지
아래는 JDK 25에서 preview/incubator라 --enable-preview가 필요하고 버전 간 문법이 바뀔 수 있습니다. 운영 코드엔 넣지 말고 검토만 합니다.
- 구조적 동시성(JEP 505, 5차 preview) — 여러 하위 작업을 하나의 단위로 묶어 실패·취소를 함께 다룸. 가상 스레드와 짝. 아직 API가 다듬어지는 중.
- Stable Values(JEP 502, preview) — 한 번만 초기화되는 불변 값(지연 상수).
final보다 유연한 지연 초기화. - 패턴에서 원시 타입(JEP 507, 3차 preview) —
switch·instanceof패턴에서int같은 원시 타입 매칭.
정리 — 21에서 25로
- 바로 쓰는 정식 기능:
import module(JEP 511), 간결한 소스 파일 + 인스턴스main(512), 유연한 생성자 본문(513), Scoped Values(506). - 코드 없이 얻는 런타임 이득: Compact Object Headers·Generational Shenandoah·AOT 시작 속도.
- 아직 preview: 구조적 동시성·Stable Values·패턴의 원시 타입 — 실험만.
- LTS라 지원이 길고, 정식 기능은 대부분 소스 호환이라 21에서 넘어갈 명분이 큽니다(런타임에서 정식 지원되는 최신은 Spring Boot 4.0이 Java 25를 1급으로 받습니다).
