From 8319ad44203531c2b419fa1502490aed26ca8fe9 Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 19:02:26 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs(java):=20LTS=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EA=B0=80=EC=9D=B4=EB=93=9C=20=EC=84=A4=EA=B3=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...09-16-java-lts-version-selection-design.md | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md diff --git a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md new file mode 100644 index 00000000..d35a6669 --- /dev/null +++ b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md @@ -0,0 +1,169 @@ +# Java LTS 버전 선택과 현대화 가이드 설계 + +## 목적 + +운영 중인 Java 8 또는 17 서비스를 Java 21 또는 25로 전환하려는 백엔드 +팀이 다음 질문에 답할 수 있는 공개 기술 가이드를 작성한다. + +- Java 8, 17, 21, 25에서 실무에 영향을 주는 주요 변화는 무엇인가? +- 신규 서비스와 기존 서비스는 어떤 LTS 버전을 선택해야 하는가? +- 최신 JDK가 성능에 유리할 수 있는 이유와 보장할 수 없는 부분은 무엇인가? +- 버전을 올린 뒤 어떤 순서와 코드 패턴으로 현대화해야 하는가? + +## 산출물 + +- canonical topic: + `docs/services/application-development/java-lts-version-selection/index.md` +- 문서 유형: `guide` +- 대표 서비스: `application-development` +- 기술: `java` +- 태그: `evaluate`, `migrate`, `optimize` +- sample과 이미지는 추가하지 않는다. +- 기존 taxonomy 값으로 충분하므로 새 서비스, 기술, 태그는 추가하지 않는다. + +## 대상 독자와 전제 + +주 독자는 Java 8 또는 17 기반의 운영 서비스를 보유하고 있으며, 지원 기간, +호환성, 성능과 개발 생산성을 함께 고려해 다음 LTS를 선택해야 하는 백엔드 +개발·플랫폼 팀이다. 특정 클라우드, 프레임워크 또는 JDK 배포판에 종속되지 +않는 결정을 돕는다. + +문서에서 “LTS”는 Java SE 또는 OpenJDK 프로젝트가 모든 배포판에 일괄 +부여하는 속성이 아니라 JDK 공급자의 지원 정책이라는 점을 먼저 밝힌다. +지원 기간은 실제로 선택한 배포판의 정책을 별도로 확인하도록 안내한다. + +## 콘텐츠 구조 + +### 1. 결론과 빠른 선택표 + +문서 첫 부분에서 조건별 기본 선택을 제시한다. + +- Java 8: 애플리케이션이나 상용 제품 제약으로 즉시 이전할 수 없는 기존 + 시스템에만 유지한다. +- Java 17: 의존성 또는 플랫폼이 21 이상을 아직 인증하지 않은 경우의 + 호환성 전환점으로 사용한다. +- Java 21: 생태계 성숙도와 현대 기능의 균형을 중시하는 기존 서비스의 + 보수적인 기본 선택으로 사용한다. +- Java 25: 런타임·프레임워크·관측 도구가 지원하고 검증을 통과한 신규 + 서비스 또는 장기 운영 서비스의 우선 후보로 사용한다. + +이는 절대 규칙이 아니다. 배포판 지원 기간, 프레임워크 최소·최대 버전, +라이브러리와 에이전트 호환성, 롤백 가능성, 워크로드 벤치마크를 최종 +결정 조건으로 둔다. + +### 2. 버전별 주요 기능 + +모든 JEP를 나열하지 않고, 코딩 방식이나 운영 특성을 바꾸는 기능만 네 +범주로 요약한다. + +| 버전 | 언어·모델링 | 표준 API | 동시성 | JVM·GC | +|---|---|---|---|---| +| 8 | lambda, method reference, default method | Stream, Optional, `java.time` | `CompletableFuture` | Metaspace, 당시 기본 GC 변화 | +| 17 | record, sealed class, pattern matching for `instanceof`, switch expression, text block | 표준 HTTP Client 등 9~17의 누적 변화 | 기존 executor 중심 | G1 성숙, 저지연 GC 선택지와 컨테이너 인식 개선 | +| 21 | record pattern, pattern matching for `switch` | sequenced collections | virtual thread | generational ZGC | +| 25 | 22~25에서 확정된 API·언어 기능과 25의 주요 기능 | 최신 표준 API 변화 | scoped value 등 확정 상태를 확인한 기능 | compact object headers 등 25의 주요 런타임 변화 | + +각 항목은 최종 확정 기능과 preview/incubator 기능을 명시적으로 구분한다. +Java 17과 21, 25는 직전 LTS 이후 누적된 기능이라는 점도 표에 표시한다. +정확한 JEP 번호와 최종 상태는 집필 시 공식 원문으로 다시 확인한다. + +### 3. 최신 버전이 성능에 유리할 수 있는 이유 + +성능을 “버전 번호만 올리면 자동 향상”으로 설명하지 않는다. 다음 경로를 +분리해 설명한다. + +- JIT와 런타임 최적화가 장기 실행 처리량과 코드 생성 품질에 미치는 영향 +- G1, ZGC 등 GC 개선이 pause time, 처리량, 힙 크기와 메모리 사용량에 + 미치는 영향 +- compact object headers 같은 메모리 레이아웃 변화가 객체가 많은 + 워크로드에 미칠 수 있는 영향 +- virtual thread가 blocking I/O 서비스의 동시성 비용을 줄일 수 있지만 + CPU-bound 연산 자체를 빠르게 만들지는 않는다는 경계 +- 컨테이너 자원 인식과 진단 기능 개선이 운영 안정성에 주는 간접 효과 + +워크로드, 힙 크기, GC, JVM 옵션, 워밍업, 프레임워크 버전이 다르면 결과가 +달라지므로 숫자로 된 보편적 향상률은 제시하지 않는다. + +### 4. 버전 업그레이드와 현대화 패턴 + +업그레이드를 두 단계로 분리한다. + +1. **호환성 단계**: 목표 JDK에서 기존 동작을 유지한다. 빌드 도구, + 프레임워크, 라이브러리, Java agent, 제거된 모듈·API, 강한 캡슐화, + JVM 옵션을 먼저 점검하고 회귀·부하 테스트를 통과시킨다. +2. **현대화 단계**: 안정 기능을 작은 변경 단위로 도입한다. 기능 전환과 + 런타임 전환을 한 번에 섞지 않아 회귀 원인과 롤백 경계를 명확히 한다. + +본문에는 다음 전후 패턴을 짧은 코드로 제시한다. + +- 불변 DTO와 값 객체: 보일러플레이트 class에서 `record`로 전환 +- 닫힌 도메인 모델: 상속 가능한 class 계층에서 `sealed` 계층으로 전환 +- 타입 분기: cast 중심 `instanceof` 연쇄에서 pattern matching으로 전환 +- 값 계산 분기: statement형 `switch`에서 exhaustive switch expression으로 + 전환 +- blocking I/O fan-out: 고정 thread pool을 무조건 크게 만드는 방식에서 + virtual-thread-per-task 실행기로 전환 + +virtual thread 예제에는 요청별 무제한 외부 호출을 허용하지 않고 semaphore, +connection pool 또는 대상 시스템의 용량에 맞춘 별도 동시성 제한이 +필요하다고 설명한다. preview 기능은 운영 기본 패턴에서 제외하고 별도 +참고로만 다룬다. + +### 5. 측정과 도입 체크리스트 + +동일한 애플리케이션과 트래픽으로 다음을 비교하도록 한다. + +- 처리량과 오류율 +- p50, p95, p99 latency +- 시작 시간과 warm-up 구간 +- CPU 사용량, RSS, heap 사용량 +- GC pause, allocation rate, collection 빈도 + +기준 JDK와 목표 JDK에서 프레임워크·의존성, 컨테이너 제한, JVM 옵션, +GC와 테스트 데이터를 기록한다. 운영 반영은 canary 또는 점진적 배포로 +진행하고 JDK 이미지와 JVM 옵션을 함께 되돌릴 수 있는 롤백 조건을 둔다. + +## 주장과 출처 원칙 + +- 언어와 JVM 기능은 OpenJDK JEP 원문과 JDK 릴리스 자료를 우선한다. +- LTS 지원 기간은 특정 공급자의 공식 지원 정책으로 한정해 표현한다. +- 저장소의 현재 출처 검증 계약을 만족하도록 Microsoft Learn의 Java + 지원 자료를 포함하고, OpenJDK의 공식 GitHub JEP 원문을 함께 사용한다. +- 실제로 전문을 확인한 문서만 `official_sources`에 기록한다. +- 중요한 기능 상태와 적용 범위를 모두 확인한 경우에만 + `verification_status: verified`로 표시한다. 확인이 불완전하면 + `needs-review`로 남긴다. +- 출처 확인 기준일은 `2026-09-16`으로 기록한다. + +## 비목표 + +- Java 8 이후 모든 JEP의 완전한 목록 +- 특정 JDK 배포판의 가격 또는 전체 지원 기간 비교 +- Spring Boot, Jakarta EE 등 프레임워크별 상세 호환성 행렬 +- 근거 데이터가 없는 보편적 성능 향상률 +- preview 기능을 사용하는 운영 코드 권장 +- 실행 가능한 벤치마크 sample 제공 + +## 검증 + +내용 작성 뒤 다음을 확인한다. + +1. 각 기능의 도입 버전과 final/preview/incubator 상태를 공식 원문과 + 대조한다. +2. 버전 선택 문구가 조건부 권장이고 배포판 지원 정책을 일반화하지 + 않았는지 검토한다. +3. 코드 예제가 해당 기능이 final인 최소 버전과 일치하는지 확인한다. +4. 저장소 루트에서 필수 문서 검증을 모두 실행한다. + +```text +python -m pytest tests/docs -q +python scripts/docs/validate_metadata.py +python scripts/docs/validate_sources.py +python scripts/docs/validate_links.py +python scripts/docs/validate_public_safety.py +mkdocs build --strict +python scripts/docs/validate_search_index.py +python scripts/docs/audit_pre_pages.py +``` + +생성된 `site/`와 검색 인덱스 산출물은 커밋하지 않는다. From f2b1da06e7cc0a65eab81494d2d6129a516ac0c5 Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 19:06:20 +0900 Subject: [PATCH 2/6] =?UTF-8?q?docs(java):=20LTS=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EA=B3=84=ED=9A=8D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../2026-09-16-java-lts-version-selection.md | 440 ++++++++++++++++++ ...09-16-java-lts-version-selection-design.md | 4 +- 2 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-09-16-java-lts-version-selection.md diff --git a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md new file mode 100644 index 00000000..e8817296 --- /dev/null +++ b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md @@ -0,0 +1,440 @@ +# Java LTS Version Selection Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Publish a concise, source-backed Korean guide that compares Java 8, 17, 21, and 25, recommends a version by operating constraint, explains conditional performance benefits, and shows safe modernization patterns. + +**Architecture:** Add one canonical `guide` topic under the existing `application-development` service. Keep feature claims vendor-neutral with OpenJDK release and JEP sources, use Microsoft Build of OpenJDK only as one concrete example of vendor-specific LTS support, and integrate the page through the repository's generated navigation and search pipeline. + +**Tech Stack:** Markdown, YAML front matter, OpenJDK JEP documentation, Microsoft Learn, MkDocs Material, Python documentation validators + +## Global Constraints + +- Create the canonical topic at `docs/services/application-development/java-lts-version-selection/index.md`. +- Use `document_type: guide`, service `application-development`, technology `java`, and tags `evaluate`, `migrate`, `optimize`. +- Cover Java SE 8, 17, 21, and 25; Java 11 is outside this guide's requested comparison scope. +- Treat LTS as a JDK-vendor support policy, not an intrinsic OpenJDK or Java SE designation. +- Separate final features from preview, incubator, experimental, and opt-in features. +- Do not claim that a JDK upgrade alone guarantees a performance improvement. +- Recommend only final features in production modernization examples. +- Add no sample project, rendered image, manual navigation entry, README entry, or hard-coded catalog entry. +- Do not add a new service, technology, or tag. +- Use `2026-09-16` for `sources_checked_at` and `last_verified` only after all material claims have been checked against the listed official sources. +- Use `python3` for local validation because this worktree has `/usr/bin/python3` but no `python` executable. +- Do not commit generated `site/` output. + +--- + +## File Structure + +- Modify `docs-taxonomy.yml` + - Allow `openjdk.org` as an official source host so Java feature claims can cite their canonical upstream documentation. +- Create `docs/services/application-development/java-lts-version-selection/index.md` + - Own all version comparisons, decision guidance, performance caveats, migration stages, code patterns, and verification guidance. +- Do not modify tests. + - Existing metadata, source, topic, link, search-index, public-safety, and pre-Pages tests automatically discover canonical documents. + +### Task 1: Publish the source-backed Java LTS guide + +**Files:** +- Modify: `docs-taxonomy.yml` +- Create: `docs/services/application-development/java-lts-version-selection/index.md` +- Test: `tests/docs/test_content.py` +- Test: `tests/docs/test_topics.py` + +**Interfaces:** +- Consumes: `services.application-development`, `technologies.java`, tags `evaluate`, `migrate`, and `optimize` from `docs-taxonomy.yml`. +- Consumes: the common and `guide` metadata contract in `scripts/docs/content.py`. +- Produces: canonical topic key `application-development/java-lts-version-selection`. +- Produces: an official-source set containing at least one `learn.microsoft.com` URL and canonical `openjdk.org` URLs. + +- [ ] **Step 1: Verify the source set and record the claim boundaries** + +Read the complete contents of these official sources before drafting: + +| Source | Claims it supports | +|---|---| +| `https://learn.microsoft.com/en-us/java/openjdk/support` | Vendors assign different support timelines; Microsoft lists 17, 21, and 25 as LTS releases and handles Java 8 separately through Temurin-based images in its stated scenarios. | +| `https://openjdk.org/projects/jdk8/` | JDK 8 GA date and its role as the Java SE 8 reference implementation. | +| `https://openjdk.org/projects/jdk8/features` | Java 8 lambda, collection bulk operations/Stream foundation, date/time API, and removal of PermGen. | +| `https://openjdk.org/projects/jdk/17/` | JDK 17 GA date and vendor-qualified LTS wording. | +| `https://openjdk.org/projects/jdk/17/jeps-since-jdk-11` | Final and preview additions from 12 through 17, including records, sealed classes, switch expressions, text blocks, strong encapsulation, ZGC, and G1 improvements. | +| `https://openjdk.org/jeps/248` | G1 becomes the default garbage collector in JDK 9. | +| `https://openjdk.org/jeps/261` | The Java Platform Module System is delivered in JDK 9. | +| `https://openjdk.org/jeps/286` | Local-variable type inference is delivered in JDK 10. | +| `https://openjdk.org/jeps/321` | The standard HTTP Client API is delivered in JDK 11. | +| `https://openjdk.org/projects/jdk/21/` | JDK 21 GA date and vendor-qualified LTS wording. | +| `https://openjdk.org/projects/jdk/21/jeps-since-jdk-17` | Final and preview additions from 18 through 21. | +| `https://openjdk.org/projects/jdk/25/` | JDK 25 GA date and vendor-qualified LTS wording. | +| `https://openjdk.org/projects/jdk/25/jeps-since-jdk-21` | Final, preview, experimental, deprecated, and removed changes from 22 through 25. | +| `https://openjdk.org/jeps/444` | Virtual threads improve the thread-per-request style and throughput for high-concurrency blocking workloads, not CPU execution speed or latency by themselves. | +| `https://openjdk.org/jeps/439` | Generational ZGC goals and trade-offs in JDK 21. | +| `https://openjdk.org/jeps/491` | JDK 24 removes nearly all `synchronized`-related virtual-thread pinning cases. | +| `https://openjdk.org/jeps/483` | Opt-in AOT class loading and linking in JDK 24 targets startup and warm-up time. | +| `https://openjdk.org/jeps/506` | Scoped values are final in JDK 25 and are intended for immutable context sharing. | +| `https://openjdk.org/jeps/519` | Compact object headers become a product feature in JDK 25 but are not enabled by default. | + +Do not generalize the Microsoft support dates to other vendors. Do not repeat +the workload-specific percentages shown in JEP 483 or JEP 519 as universal +performance expectations. + +- [ ] **Step 2: Create the canonical document with upstream sources** + +Create `docs/services/application-development/java-lts-version-selection/index.md` +with this front matter: + +```yaml +--- +services: +- application-development +official_sources: +- title: Support roadmap for the Microsoft Build of OpenJDK + url: https://learn.microsoft.com/en-us/java/openjdk/support +- title: JDK 8 Project + url: https://openjdk.org/projects/jdk8/ +- title: JDK 8 Features + url: https://openjdk.org/projects/jdk8/features +- title: JDK 17 Project + url: https://openjdk.org/projects/jdk/17/ +- title: JEPs in JDK 17 integrated since JDK 11 + url: https://openjdk.org/projects/jdk/17/jeps-since-jdk-11 +- title: JEP 248 - Make G1 the Default Garbage Collector + url: https://openjdk.org/jeps/248 +- title: JEP 261 - Module System + url: https://openjdk.org/jeps/261 +- title: JEP 286 - Local-Variable Type Inference + url: https://openjdk.org/jeps/286 +- title: JEP 321 - HTTP Client API + url: https://openjdk.org/jeps/321 +- title: JDK 21 Project + url: https://openjdk.org/projects/jdk/21/ +- title: JEPs in JDK 21 integrated since JDK 17 + url: https://openjdk.org/projects/jdk/21/jeps-since-jdk-17 +- title: JDK 25 Project + url: https://openjdk.org/projects/jdk/25/ +- title: JEPs in JDK 25 integrated since JDK 21 + url: https://openjdk.org/projects/jdk/25/jeps-since-jdk-21 +- title: JEP 444 - Virtual Threads + url: https://openjdk.org/jeps/444 +- title: JEP 439 - Generational ZGC + url: https://openjdk.org/jeps/439 +- title: JEP 491 - Synchronize Virtual Threads without Pinning + url: https://openjdk.org/jeps/491 +- title: JEP 483 - Ahead-of-Time Class Loading and Linking + url: https://openjdk.org/jeps/483 +- title: JEP 506 - Scoped Values + url: https://openjdk.org/jeps/506 +- title: JEP 519 - Compact Object Headers + url: https://openjdk.org/jeps/519 +document_type: guide +status: current +verification_status: verified +sources_checked_at: 2026-09-16 +last_verified: 2026-09-16 +review_cycle_days: 180 +applies_to: +- Java SE 8, 17, 21, 25 +title: Java 8·17·21·25 LTS 선택과 현대화 가이드 +description: Java 주요 LTS의 기능과 성능 특성을 비교하고 신규 도입 및 업그레이드 기준과 구현 패턴을 정리합니다. +technologies: +- java +tags: +- evaluate +- migrate +- optimize +--- +``` + +Use exactly one H1 and organize the body under these H2 sections: + +1. `먼저 내릴 결론` +2. `LTS를 해석할 때 주의할 점` +3. `버전별 누적 주요 변화` +4. `어떤 버전을 선택할까` +5. `최신 JDK가 성능에 유리할 수 있는 이유` +6. `업그레이드는 호환성과 현대화를 분리한다` +7. `버전 업그레이드 후 권장 구현 패턴` +8. `성능 검증 체크리스트` +9. `제약 사항` +10. `공식 참고 자료` + +The opening decision table must give these conditional recommendations: + +| Situation | Default choice | Reason | +|---|---|---| +| New service with a fully certified framework, libraries, agents, base image, and platform | Java 25 | Longest forward-looking support candidate and access to improvements delivered through 25 | +| Existing service where 25 support is not yet complete | Java 21 | Mature virtual-thread and pattern-matching baseline with broad ecosystem adoption | +| Dependency or product certification blocks 21 | Java 17 | Transitional compatibility target, not the preferred endpoint for unconstrained new work | +| Only legacy application/vendor constraints allow it | Java 8 | Maintain temporarily with a funded migration plan | + +Immediately qualify the table: the selected JDK distribution's support policy, +not the version number alone, determines the actual support period. + +The cumulative feature table must distinguish stable features from features +that require deliberate enablement: + +| Version | Stable highlights to explain | Operational or migration highlights | +|---|---|---| +| 8 | lambda/method references, Stream, default methods, `Optional`, `java.time`, `CompletableFuture` | Metaspace replaces PermGen | +| 17 | module system, `var`, standard HTTP Client, switch expressions, text blocks, records, pattern matching for `instanceof`, sealed classes | G1 default since 9, production ZGC since 15, helpful NPEs, strong encapsulation of JDK internals | +| 21 | virtual threads, record patterns, pattern matching for `switch`, sequenced collections, UTF-8 by default | Generational ZGC; dynamic agent loading warning | +| 25 | unnamed variables/patterns, FFM API, Class-File API, Stream Gatherers, Scoped Values, module imports, flexible constructor bodies | improved virtual-thread `synchronized` behavior, AOT cache/tooling, compact object headers, Generational Shenandoah, richer JFR; Security Manager disabled and 32-bit x86 removed | + +Add a separate note that Structured Concurrency, Stable Values, primitive +patterns, PEM encodings, and the Vector API are not final in JDK 25. Do not +recommend them as the default production migration pattern. + +Explain performance through mechanisms rather than promises: + +- Newer HotSpot builds include cumulative JIT, GC, runtime, platform, and + diagnostic work, but application results depend on workload and configuration. +- Virtual threads can increase throughput for high-concurrency blocking I/O by + reducing the cost of one-thread-per-task; they do not make CPU-bound work + faster and do not remove downstream capacity limits. +- Generational ZGC can reduce allocation stalls and memory overhead relative to + non-generational ZGC for suitable workloads, but G1 can still be the better + throughput or simplicity choice. +- JDK 24/25 AOT cache facilities target startup and warm-up, require a + representative training workflow, and are not equivalent to native-image + compilation. +- Compact object headers can improve object density when explicitly enabled, + but JEP 519 says the layout is not the default. +- Language features mainly improve clarity, modeling, and maintainability; + never present records or pattern matching as automatic runtime speedups. + +Use a two-stage migration: + +1. Compatibility: inventory the distribution/support policy, build tool, + framework, dependencies, annotation processors, agents, native/JNI access, + removed modules/APIs, internal JDK access, JVM flags, container image, and + rollback artifact. Run the existing application on the target runtime before + changing its programming model. +2. Modernization: adopt final language/API features in small reviewable changes, + re-run correctness and load tests after each change, and keep preview + features outside the default production path. + +Include these complete code patterns. + +Record for an immutable value: + +```java +public record CustomerId(String value) { + public CustomerId { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("value must not be blank"); + } + } +} +``` + +Sealed domain model with exhaustive Java 21 matching: + +```java +sealed interface PaymentResult permits Approved, Declined {} + +record Approved(String approvalId) implements PaymentResult {} + +record Declined(String reason) implements PaymentResult {} + +static String message(PaymentResult result) { + return switch (result) { + case Approved(var approvalId) -> "approved: " + approvalId; + case Declined(var reason) -> "declined: " + reason; + }; +} +``` + +Virtual-thread fan-out with an explicit downstream limit: + +```java +private static final Semaphore DOWNSTREAM_LIMIT = new Semaphore(100); + +static String fetch(Callable request) throws Exception { + DOWNSTREAM_LIMIT.acquire(); + try { + return request.call(); + } finally { + DOWNSTREAM_LIMIT.release(); + } +} + +static List fetchAll(List> requests) + throws Exception { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var futures = requests.stream() + .map(request -> executor.submit(() -> fetch(request))) + .toList(); + + var results = new ArrayList(futures.size()); + for (var future : futures) { + results.add(future.get()); + } + return List.copyOf(results); + } +} +``` + +Explain that a connection pool, rate limiter, semaphore, or downstream quota +still controls real concurrency. Do not pool virtual threads themselves. + +JDK 25 scoped value for immutable request context: + +```java +private static final ScopedValue TRACE_ID = ScopedValue.newInstance(); + +static void handle(String traceId, Runnable action) { + ScopedValue.where(TRACE_ID, traceId).run(action); +} +``` + +State that framework-provided context propagation should remain authoritative +when it already defines the request context lifecycle. + +End with a measurement matrix requiring the same application artifact, traffic, +data set, CPU/memory limits, warm-up policy, framework and dependency versions, +and explicit JVM/GC flags. Compare throughput, errors, p50/p95/p99 latency, +startup, warm-up, CPU, RSS, heap, allocation rate, GC frequency, and GC pauses. +Require canary rollout and a rollback that restores both the JDK image and JVM +flags. + +- [ ] **Step 3: Run source validation to prove the new upstream host is rejected** + +Run: + +```bash +python3 scripts/docs/validate_sources.py +``` + +Expected: FAIL and report `official source host is not allowed: openjdk.org` +for the new guide. This proves the document is discovered and the source +allowlist must be updated rather than bypassed. + +- [ ] **Step 4: Allow the canonical OpenJDK source host** + +Add `openjdk.org` to `official_source_hosts` in `docs-taxonomy.yml`, keeping the +existing hosts and `required_source_host: learn.microsoft.com` unchanged: + +```yaml +official_source_hosts: + - learn.microsoft.com + - openjdk.org + - kubernetes.io + - www.cncf.io + - cncf.io + - github.com + - modelcontextprotocol.io + - py.sdk.modelcontextprotocol.io + - prices.azure.com + - www.rfc-editor.org +``` + +- [ ] **Step 5: Run targeted document-contract tests** + +Run: + +```bash +python3 scripts/docs/validate_metadata.py +python3 scripts/docs/validate_sources.py +python3 scripts/docs/validate_links.py +python3 scripts/docs/validate_public_safety.py +python3 -m pytest tests/docs/test_content.py tests/docs/test_topics.py -q +``` + +Expected: every validator exits 0 and all selected tests pass. If a command +fails, use `superpowers:systematic-debugging` before changing the document or +validator. + +- [ ] **Step 6: Review the rendered-content contract** + +Check: + +```bash +git diff --check +git diff -- docs-taxonomy.yml docs/services/application-development/java-lts-version-selection/index.md +``` + +Expected: + +- one H1 and no skipped heading levels; +- no `topic_order`, `redirect_from`, sample, image, or manual navigation change; +- every Java 25 preview/incubator feature is visibly separated from stable + recommendations; +- no universal performance percentage; +- no claim that all vendors share Microsoft's support dates; +- no secret, customer identifier, internal host, or private endpoint. + +- [ ] **Step 7: Commit the guide** + +```bash +git add docs-taxonomy.yml docs/services/application-development/java-lts-version-selection/index.md +git commit -m "docs(java): LTS 버전 선택과 현대화 가이드 추가" \ + -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +### Task 2: Verify generated navigation, search, and compatibility output + +**Files:** +- Verify: `docs/services/application-development/java-lts-version-selection/index.md` +- Verify: generated `site/services/application-development/java-lts-version-selection/index.html` +- Verify: generated service and explore indexes +- Modify: none unless a validation failure identifies a defect introduced by Task 1 + +**Interfaces:** +- Consumes: the canonical topic and taxonomy from Task 1. +- Produces: a strict MkDocs build in which the new topic appears once under `application-development`, is filterable by `java`, `evaluate`, `migrate`, and `optimize`, and does not alter the pre-Pages baseline mapping. + +- [ ] **Step 1: Run the complete documentation test suite** + +Run: + +```bash +python3 -m pytest tests/docs -q +``` + +Expected: all tests pass. + +- [ ] **Step 2: Run every repository-required validator** + +Run: + +```bash +python3 scripts/docs/validate_metadata.py +python3 scripts/docs/validate_sources.py +python3 scripts/docs/validate_links.py +python3 scripts/docs/validate_public_safety.py +mkdocs build --strict +python3 scripts/docs/validate_search_index.py +python3 scripts/docs/audit_pre_pages.py +``` + +Expected: every command exits 0. `mkdocs build --strict` creates the canonical +HTML page, and the pre-Pages audit accepts the increased current document count +without changing the fixed baseline inventory. + +- [ ] **Step 3: Verify the generated page and discovery metadata** + +Run: + +```bash +test -f site/services/application-development/java-lts-version-selection/index.html +grep -q "Java 8·17·21·25 LTS 선택과 현대화 가이드" \ + site/services/application-development/java-lts-version-selection/index.html +grep -q "java-lts-version-selection" site/services/application-development/index.html +git status --short +``` + +Expected: the page exists, its H1 text is rendered, the application-development +service index links to it, and generated `site/` output is ignored rather than +staged. + +- [ ] **Step 4: Record final verification without committing generated files** + +Run: + +```bash +git status --short +git --no-pager log -3 --oneline +``` + +Expected: no uncommitted source changes remain, `site/` is absent from status, +and the guide commit follows the committed design and plan history. diff --git a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md index d35a6669..e183fec0 100644 --- a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md +++ b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md @@ -20,6 +20,8 @@ - 태그: `evaluate`, `migrate`, `optimize` - sample과 이미지는 추가하지 않는다. - 기존 taxonomy 값으로 충분하므로 새 서비스, 기술, 태그는 추가하지 않는다. + 공식 OpenJDK 원문을 기록할 수 있도록 출처 host allowlist에 + `openjdk.org`만 추가한다. ## 대상 독자와 전제 @@ -128,7 +130,7 @@ GC와 테스트 데이터를 기록한다. 운영 반영은 canary 또는 점진 - 언어와 JVM 기능은 OpenJDK JEP 원문과 JDK 릴리스 자료를 우선한다. - LTS 지원 기간은 특정 공급자의 공식 지원 정책으로 한정해 표현한다. - 저장소의 현재 출처 검증 계약을 만족하도록 Microsoft Learn의 Java - 지원 자료를 포함하고, OpenJDK의 공식 GitHub JEP 원문을 함께 사용한다. + 지원 자료를 포함하고, `openjdk.org`의 canonical JEP 원문을 함께 사용한다. - 실제로 전문을 확인한 문서만 `official_sources`에 기록한다. - 중요한 기능 상태와 적용 범위를 모두 확인한 경우에만 `verification_status: verified`로 표시한다. 확인이 불완전하면 From a5804670c8f5101e518bf2b0fca9494a79d1ce2e Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 19:16:41 +0900 Subject: [PATCH 3/6] =?UTF-8?q?docs(java):=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=ED=99=98=EA=B2=BD=EC=9D=84=20Python=203.1?= =?UTF-8?q?3=EC=9C=BC=EB=A1=9C=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../2026-09-16-java-lts-version-selection.md | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md index e8817296..bfbf438f 100644 --- a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md +++ b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md @@ -20,7 +20,9 @@ - Add no sample project, rendered image, manual navigation entry, README entry, or hard-coded catalog entry. - Do not add a new service, technology, or tag. - Use `2026-09-16` for `sources_checked_at` and `last_verified` only after all material claims have been checked against the listed official sources. -- Use `python3` for local validation because this worktree has `/usr/bin/python3` but no `python` executable. +- Use `python3.13` for local validation because CI uses Python 3.13, while + `/usr/bin/python3` is Python 3.9 and cannot evaluate the repository's PEP 604 + type annotations during test collection. - Do not commit generated `site/` output. --- @@ -302,7 +304,7 @@ flags. Run: ```bash -python3 scripts/docs/validate_sources.py +python3.13 scripts/docs/validate_sources.py ``` Expected: FAIL and report `official source host is not allowed: openjdk.org` @@ -333,11 +335,11 @@ official_source_hosts: Run: ```bash -python3 scripts/docs/validate_metadata.py -python3 scripts/docs/validate_sources.py -python3 scripts/docs/validate_links.py -python3 scripts/docs/validate_public_safety.py -python3 -m pytest tests/docs/test_content.py tests/docs/test_topics.py -q +python3.13 scripts/docs/validate_metadata.py +python3.13 scripts/docs/validate_sources.py +python3.13 scripts/docs/validate_links.py +python3.13 scripts/docs/validate_public_safety.py +python3.13 -m pytest tests/docs/test_content.py tests/docs/test_topics.py -q ``` Expected: every validator exits 0 and all selected tests pass. If a command @@ -388,7 +390,7 @@ git commit -m "docs(java): LTS 버전 선택과 현대화 가이드 추가" \ Run: ```bash -python3 -m pytest tests/docs -q +python3.13 -m pytest tests/docs -q ``` Expected: all tests pass. @@ -398,13 +400,13 @@ Expected: all tests pass. Run: ```bash -python3 scripts/docs/validate_metadata.py -python3 scripts/docs/validate_sources.py -python3 scripts/docs/validate_links.py -python3 scripts/docs/validate_public_safety.py +python3.13 scripts/docs/validate_metadata.py +python3.13 scripts/docs/validate_sources.py +python3.13 scripts/docs/validate_links.py +python3.13 scripts/docs/validate_public_safety.py mkdocs build --strict -python3 scripts/docs/validate_search_index.py -python3 scripts/docs/audit_pre_pages.py +python3.13 scripts/docs/validate_search_index.py +python3.13 scripts/docs/audit_pre_pages.py ``` Expected: every command exits 0. `mkdocs build --strict` creates the canonical From bed8258f6ad263506d9c6e5cba1354c3f883cc05 Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 19:19:03 +0900 Subject: [PATCH 4/6] =?UTF-8?q?docs(java):=20LTS=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EA=B3=BC=20=ED=98=84=EB=8C=80=ED=99=94=20?= =?UTF-8?q?=EA=B0=80=EC=9D=B4=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs-taxonomy.yml | 1 + .../java-lts-version-selection/index.md | 391 ++++++++++++++++++ 2 files changed, 392 insertions(+) create mode 100644 docs/services/application-development/java-lts-version-selection/index.md diff --git a/docs-taxonomy.yml b/docs-taxonomy.yml index 9d09b57d..b07bd012 100644 --- a/docs-taxonomy.yml +++ b/docs-taxonomy.yml @@ -121,6 +121,7 @@ verification_statuses: [verified, needs-review] required_source_host: learn.microsoft.com official_source_hosts: - learn.microsoft.com + - openjdk.org - kubernetes.io - www.cncf.io - cncf.io diff --git a/docs/services/application-development/java-lts-version-selection/index.md b/docs/services/application-development/java-lts-version-selection/index.md new file mode 100644 index 00000000..e818d79d --- /dev/null +++ b/docs/services/application-development/java-lts-version-selection/index.md @@ -0,0 +1,391 @@ +--- +services: +- application-development +official_sources: +- title: Support roadmap for the Microsoft Build of OpenJDK + url: https://learn.microsoft.com/en-us/java/openjdk/support +- title: JDK 8 Project + url: https://openjdk.org/projects/jdk8/ +- title: JDK 8 Features + url: https://openjdk.org/projects/jdk8/features +- title: JDK 17 Project + url: https://openjdk.org/projects/jdk/17/ +- title: JEPs in JDK 17 integrated since JDK 11 + url: https://openjdk.org/projects/jdk/17/jeps-since-jdk-11 +- title: JEP 248 - Make G1 the Default Garbage Collector + url: https://openjdk.org/jeps/248 +- title: JEP 261 - Module System + url: https://openjdk.org/jeps/261 +- title: JEP 286 - Local-Variable Type Inference + url: https://openjdk.org/jeps/286 +- title: JEP 321 - HTTP Client API + url: https://openjdk.org/jeps/321 +- title: JDK 21 Project + url: https://openjdk.org/projects/jdk/21/ +- title: JEPs in JDK 21 integrated since JDK 17 + url: https://openjdk.org/projects/jdk/21/jeps-since-jdk-17 +- title: JDK 25 Project + url: https://openjdk.org/projects/jdk/25/ +- title: JEPs in JDK 25 integrated since JDK 21 + url: https://openjdk.org/projects/jdk/25/jeps-since-jdk-21 +- title: JEP 444 - Virtual Threads + url: https://openjdk.org/jeps/444 +- title: JEP 439 - Generational ZGC + url: https://openjdk.org/jeps/439 +- title: JEP 491 - Synchronize Virtual Threads without Pinning + url: https://openjdk.org/jeps/491 +- title: JEP 483 - Ahead-of-Time Class Loading and Linking + url: https://openjdk.org/jeps/483 +- title: JEP 506 - Scoped Values + url: https://openjdk.org/jeps/506 +- title: JEP 519 - Compact Object Headers + url: https://openjdk.org/jeps/519 +document_type: guide +status: current +verification_status: verified +sources_checked_at: 2026-09-16 +last_verified: 2026-09-16 +review_cycle_days: 180 +applies_to: +- Java SE 8, 17, 21, 25 +title: Java 8·17·21·25 LTS 선택과 현대화 가이드 +description: Java 주요 LTS의 기능과 성능 특성을 비교하고 신규 도입 및 업그레이드 기준과 구현 패턴을 정리합니다. +technologies: +- java +tags: +- evaluate +- migrate +- optimize +--- + +# Java 8·17·21·25 LTS 선택과 현대화 가이드 + +Java 버전 선택은 새 문법의 개수보다 **전체 애플리케이션 스택의 지원 +범위**와 **실제 워크로드에서 측정한 결과**로 결정해야 한다. 이 글은 Java +8, 17, 21, 25를 비교하지만, 모든 JEP를 나열하기보다 설계와 운영에 영향을 +주는 변화에 집중한다. + +## 먼저 내릴 결론 + +| 상황 | 기본 선택 | 이유 | +|---|---|---| +| 프레임워크, 라이브러리, Java agent, base image와 운영 플랫폼이 모두 지원하는 신규 서비스 | **Java 25** | 25까지 누적된 기능과 런타임 개선을 사용하고 다음 업그레이드까지의 여유를 확보하기 쉽다. | +| Java 25 지원이 아직 완전하지 않은 기존 서비스 | **Java 21** | virtual thread와 pattern matching을 안정 기능으로 사용하면서도 생태계가 충분히 성숙했다. | +| 제품 인증이나 핵심 의존성이 Java 21을 막는 서비스 | **Java 17** | Java 8을 벗어나는 현실적인 호환성 단계다. 제약이 없다면 신규 서비스의 최종 목표로 고르지는 않는다. | +| 애플리케이션 또는 공급자 제약으로 이전할 수 없는 시스템 | **Java 8 임시 유지** | 보안 업데이트가 제공되는 배포판을 유지하되 업그레이드 일정과 예산을 별도로 확보해야 한다. | + +한 문장으로 줄이면 **전체 스택이 지원하면 25, 아직 지원하지 않으면 21, +호환성 제약이 있으면 17, 8은 이전 계획이 있는 유지 대상**이다. 다만 실제 +지원 종료일은 버전 번호가 아니라 선택한 JDK 배포판과 계약에 따라 달라진다. + +## LTS를 해석할 때 주의할 점 + +Java SE 사양과 OpenJDK 프로젝트가 모든 배포판에 하나의 LTS 기간을 +부여하는 것은 아니다. OpenJDK의 JDK 17, 21, 25 릴리스 페이지도 +“대부분의 공급자에게 LTS가 될 것”이라고 표현한다. 실제 binary, 보안 +업데이트 주기, 지원 운영체제와 종료일은 공급자가 정한다. + +예를 들어 [Microsoft Build of OpenJDK 지원 +로드맵](https://learn.microsoft.com/en-us/java/openjdk/support)은 Microsoft +배포판의 17, 21, 25를 LTS로 분류한다. 같은 문서에서 Java 8은 Microsoft +Build의 LTS 표에 넣지 않고 일부 Azure 시나리오에 Eclipse Temurin 기반 +binary와 container image를 사용한다고 설명한다. 이는 Microsoft 배포판과 +명시된 Azure 시나리오의 정책이며 다른 공급자의 Java 8 지원 기간을 뜻하지 +않는다. + +버전을 선택할 때는 다음을 함께 확인한다. + +1. JDK 배포판의 보안 업데이트 주기와 지원 종료일 +2. 프레임워크, build plugin, annotation processor와 native library의 지원 +3. APM, profiler, 보안 agent와 동적 agent loading 방식의 호환성 +4. base image, CPU architecture와 운영체제 지원 +5. 장애 시 JDK image와 JVM option을 함께 되돌릴 수 있는지 + +## 버전별 누적 주요 변화 + +아래 표의 Java 17, 21, 25 행은 해당 버전에만 추가된 기능이 아니라 **앞선 +비교 버전 이후 누적된 주요 변화**를 요약한다. + +| 버전 | 언어와 표준 API | 동시성·JVM·운영 변화 | 적용 관점 | +|---|---|---|---| +| **Java 8** | lambda와 method reference, interface default method, Stream, `Optional`, `java.time` | `CompletableFuture`, PermGen을 대체한 Metaspace | 함수형 collection 처리와 비동기 조합의 출발점이지만 이후 10년 이상의 런타임·진단 개선은 포함하지 않는다. | +| **Java 17**
(9~17 누적) | module system, `var`, 표준 HTTP Client, switch expression, text block, record, `instanceof` pattern matching, sealed class | JDK 9부터 G1이 기본 GC, JDK 15부터 ZGC가 production 기능, helpful `NullPointerException`, elastic Metaspace, JDK internal의 강한 캡슐화 | Java 8 코드와 라이브러리의 internal API·제거 모듈 의존성을 정리하면서 데이터 모델을 간결하게 만들 수 있다. | +| **Java 21**
(18~21 누적) | record pattern, pattern matching for `switch`, sequenced collection, 기본 charset UTF-8 | virtual thread, generational ZGC 도입, 동적 agent loading 경고 | blocking I/O 서비스의 thread-per-request 모델을 유지하면서 동시성 규모를 키울 수 있다. | +| **Java 25**
(22~25 누적) | unnamed variable/pattern, FFM API, Class-File API, Stream Gatherer, Scoped Value, module import, flexible constructor body | virtual thread pinning 완화, AOT cache와 profiling, compact object header, generational Shenandoah, JFR 개선, Security Manager 비활성화, 32-bit x86 port 제거 | 새 기능뿐 아니라 agent, JNI, 보안 설정, architecture와 관측 도구의 변경도 함께 검증해야 한다. | + +### Java 25에서도 안정 기능이 아닌 항목 + +JDK 25의 **Structured Concurrency는 fifth preview**, Stable Value와 PEM +encoding은 preview, primitive type pattern은 third preview, Vector API는 +tenth incubator다. JFR CPU-time profiling도 experimental 기능이다. 이런 +기능은 `--enable-preview` 또는 별도 option이 필요한 실험 경로에서 검토하고 +운영 기본 구현 패턴으로 삼지 않는다. + +또한 Java 21에 generational ZGC가 도입됐다고 해서 당시 기본 GC가 ZGC로 +바뀐 것은 아니다. JDK 21에서는 ZGC를 선택하고 generational mode를 별도로 +활성화해야 했다. 이후 JDK 23에서 generational mode가 ZGC의 기본이 됐고, +JDK 24에서 non-generational mode가 제거됐다. + +## 어떤 버전을 선택할까 + +### 신규 서비스 + +Java 25를 우선 검토한다. 단, “최신이므로 선택”하는 것이 아니라 다음 +조건이 모두 충족되어야 한다. + +- 배포판의 Java 25 지원 기간이 서비스 수명주기와 맞는다. +- framework와 모든 build·runtime dependency가 Java 25를 지원한다. +- APM, profiler, 보안 agent와 container base image가 검증됐다. +- 운영 환경과 같은 CPU architecture, memory limit, JVM option으로 부하 + 테스트를 통과했다. + +하나라도 막히면 Java 21을 선택하고 막힌 구성요소와 재검토 날짜를 +기록한다. Java 17은 조직 표준 또는 제품 인증 때문에 21을 쓸 수 없을 때의 +전환점으로 보는 편이 좋다. + +### 기존 서비스 + +목표 버전을 먼저 고정한 뒤 Java 8 → 17 → 21 → 25를 모두 운영에 순차 +배포할 필요는 없다. 중간 버전은 호환성 문제를 좁히는 진단 단계로 사용할 +수 있지만, 운영 전환은 테스트와 rollback이 준비된 목표 LTS로 직접 +진행할 수 있다. + +Java 8 서비스라면 JDK 9의 module system과 JDK 17의 강한 캡슐화, JDK 11의 +Java EE·CORBA module 제거가 주요 경계다. Java 17 서비스라면 agent, +reflection, JNI, virtual thread 사용 library와 제거 예정 API를 중심으로 +21 또는 25 호환성을 확인한다. + +## 최신 JDK가 성능에 유리할 수 있는 이유 + +최신 JDK는 누적된 JIT, GC, runtime, platform, 진단 개선을 포함하므로 더 +나은 출발점이 될 수 있다. 하지만 **같은 코드가 항상 더 빨라진다는 보장은 +없다**. + +| 개선 경로 | 기대할 수 있는 효과 | 보장하지 않는 것 | +|---|---|---| +| JIT와 runtime 최적화 | 장기 실행 처리량, code generation, platform별 실행 효율 개선 가능성 | framework와 workload가 달라도 같은 향상률 | +| G1·ZGC·Shenandoah 개선 | pause, allocation stall, heap 회수와 memory overhead 개선 가능성 | 모든 heap 크기에서 ZGC가 G1보다 높은 처리량 | +| virtual thread | blocking I/O가 많은 서비스에서 적은 platform thread로 더 많은 동시 작업 처리 | CPU-bound 계산 가속, 개별 요청 latency 감소, downstream 용량 증가 | +| AOT class loading·linking과 profiling | 반복되는 시작 경로의 startup과 warm-up 단축 가능성 | native image와 같은 실행 모델, training 없이 자동 적용 | +| compact object header | 객체가 많은 workload에서 heap footprint와 cache locality 개선 가능성 | 기본 활성화 또는 모든 객체 구성에서 같은 효과 | +| JFR와 진단 개선 | 병목과 회귀를 더 낮은 비용으로 관찰해 튜닝 시간을 줄일 가능성 | 관측 기능 자체에 의한 application 처리량 증가 | + +특히 JDK 25의 compact object header는 experimental 단계를 벗어난 product +기능이지만 **기본 object-header layout은 아니다**. 명시적으로 활성화하고 +heap 크기, object 수, identity hash code 사용과 실제 latency를 비교해야 +한다. + +JDK 24부터는 `synchronized` 영역에서 blocking하는 virtual thread가 +platform thread에 pinning되는 경우가 대부분 제거됐다. 이는 Java 21보다 +기존 library와 함께 virtual thread를 적용하기 쉬워지는 변화지만, native +code 호출 같은 남은 pinning 원인과 외부 시스템의 동시성 한도까지 없애지는 +않는다. + +## 업그레이드는 호환성과 현대화를 분리한다 + +한 변경에서 JDK, framework major version, application 구조와 동시성 모델을 +모두 바꾸면 회귀 원인을 구분하기 어렵다. 다음 두 단계를 별도 배포 가능한 +변경으로 관리한다. + +### 1단계: 기존 동작을 목표 JDK에서 재현 + +1. JDK 배포판과 지원 기간, OS·architecture, container image를 고정한다. +2. Maven·Gradle과 compiler plugin, framework, library, annotation + processor, test tool을 목표 JDK 지원 버전으로 올린다. +3. APM·profiler·보안 agent, JNI/JNA와 reflection 기반 library를 확인한다. +4. 제거된 module·API, JDK internal 접근과 더 이상 유효하지 않은 JVM + option을 찾는다. +5. 기능 회귀, startup, 부하, 장시간 안정성 테스트를 기존 구현 그대로 + 통과시킨다. + +JDK에 포함된 도구로 정적 의존성을 먼저 찾을 수 있다. + +```bash +jdeps --jdk-internals build/app.jar +jdeprscan --release 25 build/app.jar +``` + +`jdeps`에 나오지 않는 reflection 접근과 runtime agent 동작은 실제 통합 +테스트와 startup log로 보완한다. 경고를 무시하는 `--add-opens`를 영구 +해결책으로 늘리기보다 library upgrade 또는 public API 전환을 우선한다. + +### 2단계: 안정 기능을 작은 단위로 도입 + +runtime 전환이 안정된 뒤 record, sealed type, pattern matching, virtual +thread와 Scoped Value를 독립 변경으로 도입한다. 각 변경은 동작 테스트와 +성능 지표를 다시 비교할 수 있어야 한다. preview 기능은 production +baseline과 분리한다. + +## 버전 업그레이드 후 권장 구현 패턴 + +### 불변 값은 record로 의도를 드러낸다 + +보일러플레이트를 줄이기 위해 모든 class를 record로 바꾸는 것이 아니라, +값 자체가 정체성이고 불변 data carrier인 타입에 사용한다. + +```java +public record CustomerId(String value) { + public CustomerId { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("value must not be blank"); + } + } +} +``` + +가변 lifecycle, lazy loading, 상속 기반 proxy가 필요한 entity에는 기존 +class가 더 적합할 수 있다. + +### 닫힌 도메인은 sealed type과 exhaustive switch로 모델링한다 + +허용되는 결과가 정해진 도메인은 sealed hierarchy로 표현하고 Java 21의 +record pattern과 pattern matching for `switch`를 함께 사용한다. + +```java +sealed interface PaymentResult permits Approved, Declined {} + +record Approved(String approvalId) implements PaymentResult {} + +record Declined(String reason) implements PaymentResult {} + +final class PaymentMessages { + private PaymentMessages() {} + + static String message(PaymentResult result) { + return switch (result) { + case Approved(var approvalId) -> "approved: " + approvalId; + case Declined(var reason) -> "declined: " + reason; + }; + } +} +``` + +새 subtype이 추가되면 compiler가 누락된 분기를 찾을 수 있다. 외부 plugin이 +임의 subtype을 추가해야 하는 확장 지점에는 sealed type을 사용하지 않는다. + +### blocking I/O에는 virtual-thread-per-task를 검토한다 + +virtual thread 자체를 pool로 재사용하지 않는다. 작업마다 virtual thread를 +만들되 connection pool, rate limiter, semaphore 또는 downstream quota로 +실제 외부 호출 수를 제한한다. + +```java +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; + +final class DownstreamCalls { + private static final Semaphore DOWNSTREAM_LIMIT = new Semaphore(100); + + private DownstreamCalls() {} + + static String fetch(Callable request) throws Exception { + DOWNSTREAM_LIMIT.acquire(); + try { + return request.call(); + } finally { + DOWNSTREAM_LIMIT.release(); + } + } + + static List fetchAll(List> requests) + throws Exception { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var futures = requests.stream() + .map(request -> executor.submit(() -> fetch(request))) + .toList(); + + var results = new ArrayList(futures.size()); + for (var future : futures) { + results.add(future.get()); + } + return List.copyOf(results); + } + } +} +``` + +CPU-bound 작업의 병렬도는 CPU core 수를 기준으로 제한한다. virtual thread +수를 늘려도 CPU core와 downstream connection 수는 늘어나지 않는다. + +### Java 25에서는 불변 request context에 Scoped Value를 검토한다 + +Scoped Value는 Java 25에서 final이 됐다. child thread로 읽기 전용 context를 +전달해야 할 때 mutable `ThreadLocal`보다 명확한 lifetime을 제공한다. + +```java +final class RequestContext { + private static final ScopedValue TRACE_ID = + ScopedValue.newInstance(); + + private RequestContext() {} + + static void handle(String traceId, Runnable action) { + ScopedValue.where(TRACE_ID, traceId).run(action); + } + + static String traceId() { + return TRACE_ID.get(); + } +} +``` + +framework가 tracing·security context 전파 방식을 이미 제공한다면 그 +lifecycle을 우선한다. 같은 context를 framework API와 Scoped Value에 +중복 저장하지 않는다. + +## 성능 검증 체크리스트 + +기준 JDK와 목표 JDK에서 다음 조건을 같게 유지한다. + +- application artifact, framework와 dependency version +- traffic model, test data, test duration과 concurrency +- CPU architecture, container CPU·memory limit +- JVM option, heap 크기, GC 종류와 warm-up 정책 +- 외부 API·database의 용량과 connection pool + +다음 지표를 함께 비교한다. + +- throughput과 error rate +- p50, p95, p99 latency +- startup time과 steady state까지의 warm-up 시간 +- CPU 사용량, RSS와 heap 사용량 +- allocation rate, GC 횟수와 pause 분포 +- thread·connection 수와 downstream saturation + +평균값만 비교하지 말고 여러 회차의 분포와 confidence interval을 확인한다. +운영 반영은 canary 또는 점진적 배포로 진행하고, 기준을 벗어나면 JDK image와 +JVM option을 함께 되돌린다. + +## 제약 사항 + +- 이 문서는 framework별 지원 matrix나 JDK 공급자별 가격·전체 지원 기간을 + 비교하지 않는다. +- Java 11은 요청된 비교 범위에서 제외했다. Java 8에서 17로 이동할 때 + 9~11의 module·API 변화는 포함했다. +- 성능 수치는 특정 application을 측정한 결과가 아니므로 보편적인 향상률을 + 제시하지 않는다. +- feature 상태는 2026-09-16에 확인했다. preview·incubator 기능은 이후 + 릴리스에서 변경되거나 제거될 수 있다. + +## 공식 참고 자료 + +- [Microsoft Build of OpenJDK 지원 + 로드맵](https://learn.microsoft.com/en-us/java/openjdk/support) +- [JDK 8 프로젝트](https://openjdk.org/projects/jdk8/)와 [JDK 8 주요 + 기능](https://openjdk.org/projects/jdk8/features) +- [JDK 17 프로젝트](https://openjdk.org/projects/jdk/17/)와 [JDK 11 이후 + JEP](https://openjdk.org/projects/jdk/17/jeps-since-jdk-11) +- [JDK 21 프로젝트](https://openjdk.org/projects/jdk/21/)와 [JDK 17 이후 + JEP](https://openjdk.org/projects/jdk/21/jeps-since-jdk-17) +- [JDK 25 프로젝트](https://openjdk.org/projects/jdk/25/)와 [JDK 21 이후 + JEP](https://openjdk.org/projects/jdk/25/jeps-since-jdk-21) +- [Virtual Threads](https://openjdk.org/jeps/444), [Generational + ZGC](https://openjdk.org/jeps/439), [virtual thread pinning + 개선](https://openjdk.org/jeps/491) +- [Ahead-of-Time Class Loading & + Linking](https://openjdk.org/jeps/483), [Scoped + Values](https://openjdk.org/jeps/506), [Compact Object + Headers](https://openjdk.org/jeps/519) From 33d8c5683d179ca112046bed38007474e0e9db15 Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 19:37:46 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix(java):=20LTS=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=ED=99=95=EC=84=B1=EA=B3=BC=20=EA=B3=B5?= =?UTF-8?q?=EA=B0=9C=20=EB=B2=94=EC=9C=84=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../java-lts-version-selection/index.md | 71 ++++++-- .../2026-09-16-java-lts-version-selection.md | 172 ++++++++++++++---- ...09-16-java-lts-version-selection-design.md | 2 + mkdocs.yml | 1 + tests/docs/test_pages_artifact.py | 10 + tests/docs/test_site_pipeline.py | 5 +- 6 files changed, 207 insertions(+), 54 deletions(-) diff --git a/docs/services/application-development/java-lts-version-selection/index.md b/docs/services/application-development/java-lts-version-selection/index.md index e818d79d..593a1d24 100644 --- a/docs/services/application-development/java-lts-version-selection/index.md +++ b/docs/services/application-development/java-lts-version-selection/index.md @@ -36,10 +36,14 @@ official_sources: url: https://openjdk.org/jeps/491 - title: JEP 483 - Ahead-of-Time Class Loading and Linking url: https://openjdk.org/jeps/483 +- title: JEP 515 - Ahead-of-Time Method Profiling + url: https://openjdk.org/jeps/515 - title: JEP 506 - Scoped Values url: https://openjdk.org/jeps/506 - title: JEP 519 - Compact Object Headers url: https://openjdk.org/jeps/519 +- title: JEP 521 - Generational Shenandoah + url: https://openjdk.org/jeps/521 document_type: guide status: current verification_status: verified @@ -109,9 +113,9 @@ binary와 container image를 사용한다고 설명한다. 이는 Microsoft 배 | 버전 | 언어와 표준 API | 동시성·JVM·운영 변화 | 적용 관점 | |---|---|---|---| | **Java 8** | lambda와 method reference, interface default method, Stream, `Optional`, `java.time` | `CompletableFuture`, PermGen을 대체한 Metaspace | 함수형 collection 처리와 비동기 조합의 출발점이지만 이후 10년 이상의 런타임·진단 개선은 포함하지 않는다. | -| **Java 17**
(9~17 누적) | module system, `var`, 표준 HTTP Client, switch expression, text block, record, `instanceof` pattern matching, sealed class | JDK 9부터 G1이 기본 GC, JDK 15부터 ZGC가 production 기능, helpful `NullPointerException`, elastic Metaspace, JDK internal의 강한 캡슐화 | Java 8 코드와 라이브러리의 internal API·제거 모듈 의존성을 정리하면서 데이터 모델을 간결하게 만들 수 있다. | +| **Java 17**
(9~17 누적) | module system, `var`, 표준 HTTP Client, switch expression, text block, record, `instanceof` pattern matching, sealed class | JDK 9부터 HotSpot server-class 구성은 G1이 기본 GC, JDK 15부터 ZGC가 production 기능, helpful `NullPointerException`, elastic Metaspace, JDK internal의 강한 캡슐화 | Java 8 코드와 라이브러리의 internal API·제거 모듈 의존성을 정리하면서 데이터 모델을 간결하게 만들 수 있다. | | **Java 21**
(18~21 누적) | record pattern, pattern matching for `switch`, sequenced collection, 기본 charset UTF-8 | virtual thread, generational ZGC 도입, 동적 agent loading 경고 | blocking I/O 서비스의 thread-per-request 모델을 유지하면서 동시성 규모를 키울 수 있다. | -| **Java 25**
(22~25 누적) | unnamed variable/pattern, FFM API, Class-File API, Stream Gatherer, Scoped Value, module import, flexible constructor body | virtual thread pinning 완화, AOT cache와 profiling, compact object header, generational Shenandoah, JFR 개선, Security Manager 비활성화, 32-bit x86 port 제거 | 새 기능뿐 아니라 agent, JNI, 보안 설정, architecture와 관측 도구의 변경도 함께 검증해야 한다. | +| **Java 25**
(22~25 누적) | unnamed variable/pattern, FFM API, Class-File API, Stream Gatherer, Scoped Value, module import, flexible constructor body | virtual thread pinning 완화, AOT cache와 profiling, compact object header, opt-in generational Shenandoah, JFR 개선, Security Manager 비활성화, 32-bit x86 port 제거 | 새 기능뿐 아니라 agent, JNI, 보안 설정, architecture와 관측 도구의 변경도 함께 검증해야 한다. | ### Java 25에서도 안정 기능이 아닌 항목 @@ -121,6 +125,11 @@ tenth incubator다. JFR CPU-time profiling도 experimental 기능이다. 이런 기능은 `--enable-preview` 또는 별도 option이 필요한 실험 경로에서 검토하고 운영 기본 구현 패턴으로 삼지 않는다. +Generational Shenandoah는 JDK 25에서 experimental 단계를 벗어난 product +기능이지만 기본 mode가 아니다. 사용하려면 +`-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational`을 지정해야 하며, +Shenandoah의 기본은 single-generation mode다. + 또한 Java 21에 generational ZGC가 도입됐다고 해서 당시 기본 GC가 ZGC로 바뀐 것은 아니다. JDK 21에서는 ZGC를 선택하고 generational mode를 별도로 활성화해야 했다. 이후 JDK 23에서 generational mode가 ZGC의 기본이 됐고, @@ -166,7 +175,7 @@ reflection, JNI, virtual thread 사용 library와 제거 예정 API를 중심으 | JIT와 runtime 최적화 | 장기 실행 처리량, code generation, platform별 실행 효율 개선 가능성 | framework와 workload가 달라도 같은 향상률 | | G1·ZGC·Shenandoah 개선 | pause, allocation stall, heap 회수와 memory overhead 개선 가능성 | 모든 heap 크기에서 ZGC가 G1보다 높은 처리량 | | virtual thread | blocking I/O가 많은 서비스에서 적은 platform thread로 더 많은 동시 작업 처리 | CPU-bound 계산 가속, 개별 요청 latency 감소, downstream 용량 증가 | -| AOT class loading·linking과 profiling | 반복되는 시작 경로의 startup과 warm-up 단축 가능성 | native image와 같은 실행 모델, training 없이 자동 적용 | +| AOT class loading·linking과 method profiling | 반복되는 시작 경로의 startup과 warm-up 단축 가능성 | native image와 같은 실행 모델, training 없이 자동 적용 | | compact object header | 객체가 많은 workload에서 heap footprint와 cache locality 개선 가능성 | 기본 활성화 또는 모든 객체 구성에서 같은 효과 | | JFR와 진단 개선 | 병목과 회귀를 더 낮은 비용으로 관찰해 튜닝 시간을 줄일 가능성 | 관측 기능 자체에 의한 application 처리량 증가 | @@ -181,6 +190,12 @@ platform thread에 pinning되는 경우가 대부분 제거됐다. 이는 Java 2 code 호출 같은 남은 pinning 원인과 외부 시스템의 동시성 한도까지 없애지는 않는다. +JDK 24의 AOT class loading·linking은 training run에서 얻은 class 상태를 +cache에 저장해 startup을 줄이는 기능이다. JDK 25의 AOT method profiling은 +같은 cache에 실행 profile을 더해 JIT warm-up을 앞당긴다. 두 기능 모두 +production 경로를 대표하는 training이 필요하며 native-image compile로 +바뀌는 것은 아니다. + ## 업그레이드는 호환성과 현대화를 분리한다 한 변경에서 JDK, framework major version, application 구조와 동시성 모델을 @@ -273,7 +288,9 @@ virtual thread 자체를 pool로 재사용하지 않는다. 작업마다 virtual import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.Semaphore; final class DownstreamCalls { @@ -291,29 +308,46 @@ final class DownstreamCalls { } static List fetchAll(List> requests) - throws Exception { + throws InterruptedException, ExecutionException { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { var futures = requests.stream() .map(request -> executor.submit(() -> fetch(request))) .toList(); - var results = new ArrayList(futures.size()); - for (var future : futures) { - results.add(future.get()); + try { + var results = new ArrayList(futures.size()); + for (var future : futures) { + results.add(future.get()); + } + return List.copyOf(results); + } catch (InterruptedException error) { + cancelAll(futures); + Thread.currentThread().interrupt(); + throw error; + } catch (ExecutionException error) { + cancelAll(futures); + throw error; } - return List.copyOf(results); } } + + private static void cancelAll(List> futures) { + futures.forEach(future -> future.cancel(true)); + } } ``` -CPU-bound 작업의 병렬도는 CPU core 수를 기준으로 제한한다. virtual thread -수를 늘려도 CPU core와 downstream connection 수는 늘어나지 않는다. +각 `Callable`이 사용하는 HTTP·database client에도 connect·request·read +deadline을 설정해야 한다. `cancel(true)`는 interrupt에 협조하는 작업만 +중단할 수 있으므로 client timeout을 대신하지 않는다. CPU-bound 작업의 +병렬도는 CPU core 수를 기준으로 제한한다. virtual thread 수를 늘려도 CPU +core와 downstream connection 수는 늘어나지 않는다. -### Java 25에서는 불변 request context에 Scoped Value를 검토한다 +### Java 25에서는 같은 thread의 불변 context에 Scoped Value를 검토한다 -Scoped Value는 Java 25에서 final이 됐다. child thread로 읽기 전용 context를 -전달해야 할 때 mutable `ThreadLocal`보다 명확한 lifetime을 제공한다. +Scoped Value는 Java 25에서 final이 됐다. 한 thread의 호출 경로에서 읽기 +전용 context를 전달할 때 mutable `ThreadLocal`보다 명확한 lifetime을 +제공한다. ```java final class RequestContext { @@ -334,7 +368,10 @@ final class RequestContext { framework가 tracing·security context 전파 방식을 이미 제공한다면 그 lifecycle을 우선한다. 같은 context를 framework API와 Scoped Value에 -중복 저장하지 않는다. +중복 저장하지 않는다. 일반 `Thread`나 executor에 제출한 작업은 binding을 +자동 상속하지 않는다. Child task 상속은 JDK 25에서도 preview인 Structured +Concurrency의 `StructuredTaskScope`로 생성한 task에 한정되므로, 이 문서의 +운영 기본 패턴에는 포함하지 않는다. ## 성능 검증 체크리스트 @@ -386,6 +423,8 @@ JVM option을 함께 되돌린다. ZGC](https://openjdk.org/jeps/439), [virtual thread pinning 개선](https://openjdk.org/jeps/491) - [Ahead-of-Time Class Loading & - Linking](https://openjdk.org/jeps/483), [Scoped + Linking](https://openjdk.org/jeps/483), [Ahead-of-Time Method + Profiling](https://openjdk.org/jeps/515), [Scoped Values](https://openjdk.org/jeps/506), [Compact Object - Headers](https://openjdk.org/jeps/519) + Headers](https://openjdk.org/jeps/519), [Generational + Shenandoah](https://openjdk.org/jeps/521) diff --git a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md index bfbf438f..85ccdf7d 100644 --- a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md +++ b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md @@ -19,6 +19,8 @@ - Recommend only final features in production modernization examples. - Add no sample project, rendered image, manual navigation entry, README entry, or hard-coded catalog entry. - Do not add a new service, technology, or tag. +- Keep `docs/superpowers/**` available as internal design history but exclude it + from MkDocs pages, navigation, and the search index. - Use `2026-09-16` for `sources_checked_at` and `last_verified` only after all material claims have been checked against the listed official sources. - Use `python3.13` for local validation because CI uses Python 3.13, while `/usr/bin/python3` is Python 3.9 and cannot evaluate the repository's PEP 604 @@ -31,10 +33,14 @@ - Modify `docs-taxonomy.yml` - Allow `openjdk.org` as an official source host so Java feature claims can cite their canonical upstream documentation. +- Modify `mkdocs.yml` + - Exclude internal `superpowers/**` design and plan artifacts from publication. - Create `docs/services/application-development/java-lts-version-selection/index.md` - Own all version comparisons, decision guidance, performance caveats, migration stages, code patterns, and verification guidance. -- Do not modify tests. - - Existing metadata, source, topic, link, search-index, public-safety, and pre-Pages tests automatically discover canonical documents. +- Modify `tests/docs/test_pages_artifact.py` + - Assert that internal `superpowers/**` artifacts do not enter the built site or search index. +- Modify `tests/docs/test_site_pipeline.py` + - Lock the intended MkDocs exclusion rules. ### Task 1: Publish the source-backed Java LTS guide @@ -73,8 +79,10 @@ Read the complete contents of these official sources before drafting: | `https://openjdk.org/jeps/439` | Generational ZGC goals and trade-offs in JDK 21. | | `https://openjdk.org/jeps/491` | JDK 24 removes nearly all `synchronized`-related virtual-thread pinning cases. | | `https://openjdk.org/jeps/483` | Opt-in AOT class loading and linking in JDK 24 targets startup and warm-up time. | +| `https://openjdk.org/jeps/515` | AOT method profiles in JDK 25 target JIT warm-up time. | | `https://openjdk.org/jeps/506` | Scoped values are final in JDK 25 and are intended for immutable context sharing. | | `https://openjdk.org/jeps/519` | Compact object headers become a product feature in JDK 25 but are not enabled by default. | +| `https://openjdk.org/jeps/521` | Generational Shenandoah becomes a product feature in JDK 25 but remains opt-in. | Do not generalize the Microsoft support dates to other vendors. Do not repeat the workload-specific percentages shown in JEP 483 or JEP 519 as universal @@ -124,10 +132,14 @@ official_sources: url: https://openjdk.org/jeps/491 - title: JEP 483 - Ahead-of-Time Class Loading and Linking url: https://openjdk.org/jeps/483 +- title: JEP 515 - Ahead-of-Time Method Profiling + url: https://openjdk.org/jeps/515 - title: JEP 506 - Scoped Values url: https://openjdk.org/jeps/506 - title: JEP 519 - Compact Object Headers url: https://openjdk.org/jeps/519 +- title: JEP 521 - Generational Shenandoah + url: https://openjdk.org/jeps/521 document_type: guide status: current verification_status: verified @@ -180,11 +192,13 @@ that require deliberate enablement: | 8 | lambda/method references, Stream, default methods, `Optional`, `java.time`, `CompletableFuture` | Metaspace replaces PermGen | | 17 | module system, `var`, standard HTTP Client, switch expressions, text blocks, records, pattern matching for `instanceof`, sealed classes | G1 default since 9, production ZGC since 15, helpful NPEs, strong encapsulation of JDK internals | | 21 | virtual threads, record patterns, pattern matching for `switch`, sequenced collections, UTF-8 by default | Generational ZGC; dynamic agent loading warning | -| 25 | unnamed variables/patterns, FFM API, Class-File API, Stream Gatherers, Scoped Values, module imports, flexible constructor bodies | improved virtual-thread `synchronized` behavior, AOT cache/tooling, compact object headers, Generational Shenandoah, richer JFR; Security Manager disabled and 32-bit x86 removed | +| 25 | unnamed variables/patterns, FFM API, Class-File API, Stream Gatherers, Scoped Values, module imports, flexible constructor bodies | improved virtual-thread `synchronized` behavior, AOT cache/tooling, compact object headers, opt-in Generational Shenandoah, richer JFR; Security Manager disabled and 32-bit x86 removed | Add a separate note that Structured Concurrency, Stable Values, primitive patterns, PEM encodings, and the Vector API are not final in JDK 25. Do not -recommend them as the default production migration pattern. +recommend them as the default production migration pattern. State separately +that Generational Shenandoah and compact object headers are product features +but are not enabled by default. Explain performance through mechanisms rather than promises: @@ -238,59 +252,102 @@ record Approved(String approvalId) implements PaymentResult {} record Declined(String reason) implements PaymentResult {} -static String message(PaymentResult result) { - return switch (result) { - case Approved(var approvalId) -> "approved: " + approvalId; - case Declined(var reason) -> "declined: " + reason; - }; +final class PaymentMessages { + private PaymentMessages() {} + + static String message(PaymentResult result) { + return switch (result) { + case Approved(var approvalId) -> "approved: " + approvalId; + case Declined(var reason) -> "declined: " + reason; + }; + } } ``` Virtual-thread fan-out with an explicit downstream limit: ```java -private static final Semaphore DOWNSTREAM_LIMIT = new Semaphore(100); - -static String fetch(Callable request) throws Exception { - DOWNSTREAM_LIMIT.acquire(); - try { - return request.call(); - } finally { - DOWNSTREAM_LIMIT.release(); +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; + +final class DownstreamCalls { + private static final Semaphore DOWNSTREAM_LIMIT = new Semaphore(100); + + private DownstreamCalls() {} + + static String fetch(Callable request) throws Exception { + DOWNSTREAM_LIMIT.acquire(); + try { + return request.call(); + } finally { + DOWNSTREAM_LIMIT.release(); + } } -} -static List fetchAll(List> requests) - throws Exception { - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - var futures = requests.stream() - .map(request -> executor.submit(() -> fetch(request))) - .toList(); - - var results = new ArrayList(futures.size()); - for (var future : futures) { - results.add(future.get()); + static List fetchAll(List> requests) + throws InterruptedException, ExecutionException { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var futures = requests.stream() + .map(request -> executor.submit(() -> fetch(request))) + .toList(); + + try { + var results = new ArrayList(futures.size()); + for (var future : futures) { + results.add(future.get()); + } + return List.copyOf(results); + } catch (InterruptedException error) { + cancelAll(futures); + Thread.currentThread().interrupt(); + throw error; + } catch (ExecutionException error) { + cancelAll(futures); + throw error; + } } - return List.copyOf(results); + } + + private static void cancelAll(List> futures) { + futures.forEach(future -> future.cancel(true)); } } ``` Explain that a connection pool, rate limiter, semaphore, or downstream quota still controls real concurrency. Do not pool virtual threads themselves. +Require client-level connect, request, and read deadlines because interruption +and future cancellation do not replace I/O timeouts. JDK 25 scoped value for immutable request context: ```java -private static final ScopedValue TRACE_ID = ScopedValue.newInstance(); +final class RequestContext { + private static final ScopedValue TRACE_ID = + ScopedValue.newInstance(); -static void handle(String traceId, Runnable action) { - ScopedValue.where(TRACE_ID, traceId).run(action); + private RequestContext() {} + + static void handle(String traceId, Runnable action) { + ScopedValue.where(TRACE_ID, traceId).run(action); + } + + static String traceId() { + return TRACE_ID.get(); + } } ``` State that framework-provided context propagation should remain authoritative -when it already defines the request context lifecycle. +when it already defines the request context lifecycle. Scope the stable example +to callees on the same thread. State that ordinary threads and executor tasks +do not inherit bindings, while inheritance through `StructuredTaskScope` +depends on Structured Concurrency, which remains preview in JDK 25. End with a measurement matrix requiring the same application artifact, traffic, data set, CPU/memory limits, warm-up policy, framework and dependency versions, @@ -330,7 +387,46 @@ official_source_hosts: - www.rfc-editor.org ``` -- [ ] **Step 5: Run targeted document-contract tests** +- [ ] **Step 5: Write a failing publication-boundary test** + +In `tests/docs/test_pages_artifact.py`, after the real-site build, assert: + +```python +assert not (site / "superpowers").exists() +search = json.loads( + (site / "search" / "search_index.json").read_text(encoding="utf-8") +) +assert not any( + entry["location"].startswith("superpowers/") + for entry in search["docs"] +) +``` + +Run: + +```bash +python3.13 -m pytest \ + tests/docs/test_pages_artifact.py::test_current_published_assets_survive_pages_artifact_hidden_exclusion \ + -q +``` + +Expected: FAIL because `site/superpowers/` exists before the exclusion is added. + +- [ ] **Step 6: Exclude internal planning artifacts** + +Append `superpowers/**` to `exclude_docs` in `mkdocs.yml`. Update +`test_mkdocs_keeps_navigation_and_search_metadata_driven` to expect: + +```python +assert config["exclude_docs"].splitlines() == [ + "services/**/samples/**", + "superpowers/**", +] +``` + +Run the failing test and the config contract test together. Expected: both pass. + +- [ ] **Step 7: Run targeted document-contract tests** Run: @@ -346,7 +442,7 @@ Expected: every validator exits 0 and all selected tests pass. If a command fails, use `superpowers:systematic-debugging` before changing the document or validator. -- [ ] **Step 6: Review the rendered-content contract** +- [ ] **Step 8: Review the rendered-content contract** Check: @@ -365,10 +461,12 @@ Expected: - no claim that all vendors share Microsoft's support dates; - no secret, customer identifier, internal host, or private endpoint. -- [ ] **Step 7: Commit the guide** +- [ ] **Step 9: Commit the guide and publication boundary** ```bash -git add docs-taxonomy.yml docs/services/application-development/java-lts-version-selection/index.md +git add docs-taxonomy.yml mkdocs.yml \ + docs/services/application-development/java-lts-version-selection/index.md \ + tests/docs/test_pages_artifact.py tests/docs/test_site_pipeline.py git commit -m "docs(java): LTS 버전 선택과 현대화 가이드 추가" \ -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" ``` diff --git a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md index e183fec0..bec79474 100644 --- a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md +++ b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md @@ -22,6 +22,8 @@ - 기존 taxonomy 값으로 충분하므로 새 서비스, 기술, 태그는 추가하지 않는다. 공식 OpenJDK 원문을 기록할 수 있도록 출처 host allowlist에 `openjdk.org`만 추가한다. +- `docs/superpowers/`의 내부 설계·계획 문서는 MkDocs page, navigation과 + search index에서 제외한다. ## 대상 독자와 전제 diff --git a/mkdocs.yml b/mkdocs.yml index ecf9af16..c6d7acf9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,7 @@ site_dir: site use_directory_urls: true exclude_docs: | services/**/samples/** + superpowers/** validation: links: diff --git a/tests/docs/test_pages_artifact.py b/tests/docs/test_pages_artifact.py index c8d60075..279562a5 100644 --- a/tests/docs/test_pages_artifact.py +++ b/tests/docs/test_pages_artifact.py @@ -1,3 +1,4 @@ +import json from pathlib import Path, PurePosixPath import tarfile @@ -31,6 +32,15 @@ def test_current_published_assets_survive_pages_artifact_hidden_exclusion(tmp_pa site = tmp_path / "site" build(load_config(str(ROOT / "mkdocs.yml"), site_dir=str(site), strict=True)) + assert not (site / "superpowers").exists() + search = json.loads( + (site / "search" / "search_index.json").read_text(encoding="utf-8") + ) + assert not any( + entry["location"].startswith("superpowers/") + for entry in search["docs"] + ) + hidden_controls = [ ".artifact-control", ".artifact-controls/file.txt", diff --git a/tests/docs/test_site_pipeline.py b/tests/docs/test_site_pipeline.py index c9ccf92d..f8589445 100644 --- a/tests/docs/test_site_pipeline.py +++ b/tests/docs/test_site_pipeline.py @@ -331,7 +331,10 @@ def test_mkdocs_keeps_navigation_and_search_metadata_driven() -> None: if isinstance(plugin, dict) and "search" in plugin ) assert search["lang"] == ["ko", "en"] - assert config["exclude_docs"].strip() == "services/**/samples/**" + assert config["exclude_docs"].splitlines() == [ + "services/**/samples/**", + "superpowers/**", + ] def test_topic_and_sample_styles_are_responsive_and_accessible() -> None: From faa4e498a3a0f1fb6c59fdb8ec0e331d2309e55e Mon Sep 17 00:00:00 2001 From: hellices Date: Wed, 16 Sep 2026 23:45:40 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore(java):=20PR=EC=97=90=EB=8A=94=20?= =?UTF-8?q?=EC=B5=9C=EC=A2=85=20=EA=B0=80=EC=9D=B4=EB=93=9C=EB=A7=8C=20?= =?UTF-8?q?=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 내부 설계·계획 산출물과 이를 위한 사이트·테스트 변경을 제거해 공개 가이드와 필수 taxonomy 변경만 남깁니다.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../2026-09-16-java-lts-version-selection.md | 540 ------------------ ...09-16-java-lts-version-selection-design.md | 173 ------ mkdocs.yml | 1 - tests/docs/test_pages_artifact.py | 10 - tests/docs/test_site_pipeline.py | 5 +- 5 files changed, 1 insertion(+), 728 deletions(-) delete mode 100644 docs/superpowers/plans/2026-09-16-java-lts-version-selection.md delete mode 100644 docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md diff --git a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md b/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md deleted file mode 100644 index 85ccdf7d..00000000 --- a/docs/superpowers/plans/2026-09-16-java-lts-version-selection.md +++ /dev/null @@ -1,540 +0,0 @@ -# Java LTS Version Selection Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Publish a concise, source-backed Korean guide that compares Java 8, 17, 21, and 25, recommends a version by operating constraint, explains conditional performance benefits, and shows safe modernization patterns. - -**Architecture:** Add one canonical `guide` topic under the existing `application-development` service. Keep feature claims vendor-neutral with OpenJDK release and JEP sources, use Microsoft Build of OpenJDK only as one concrete example of vendor-specific LTS support, and integrate the page through the repository's generated navigation and search pipeline. - -**Tech Stack:** Markdown, YAML front matter, OpenJDK JEP documentation, Microsoft Learn, MkDocs Material, Python documentation validators - -## Global Constraints - -- Create the canonical topic at `docs/services/application-development/java-lts-version-selection/index.md`. -- Use `document_type: guide`, service `application-development`, technology `java`, and tags `evaluate`, `migrate`, `optimize`. -- Cover Java SE 8, 17, 21, and 25; Java 11 is outside this guide's requested comparison scope. -- Treat LTS as a JDK-vendor support policy, not an intrinsic OpenJDK or Java SE designation. -- Separate final features from preview, incubator, experimental, and opt-in features. -- Do not claim that a JDK upgrade alone guarantees a performance improvement. -- Recommend only final features in production modernization examples. -- Add no sample project, rendered image, manual navigation entry, README entry, or hard-coded catalog entry. -- Do not add a new service, technology, or tag. -- Keep `docs/superpowers/**` available as internal design history but exclude it - from MkDocs pages, navigation, and the search index. -- Use `2026-09-16` for `sources_checked_at` and `last_verified` only after all material claims have been checked against the listed official sources. -- Use `python3.13` for local validation because CI uses Python 3.13, while - `/usr/bin/python3` is Python 3.9 and cannot evaluate the repository's PEP 604 - type annotations during test collection. -- Do not commit generated `site/` output. - ---- - -## File Structure - -- Modify `docs-taxonomy.yml` - - Allow `openjdk.org` as an official source host so Java feature claims can cite their canonical upstream documentation. -- Modify `mkdocs.yml` - - Exclude internal `superpowers/**` design and plan artifacts from publication. -- Create `docs/services/application-development/java-lts-version-selection/index.md` - - Own all version comparisons, decision guidance, performance caveats, migration stages, code patterns, and verification guidance. -- Modify `tests/docs/test_pages_artifact.py` - - Assert that internal `superpowers/**` artifacts do not enter the built site or search index. -- Modify `tests/docs/test_site_pipeline.py` - - Lock the intended MkDocs exclusion rules. - -### Task 1: Publish the source-backed Java LTS guide - -**Files:** -- Modify: `docs-taxonomy.yml` -- Create: `docs/services/application-development/java-lts-version-selection/index.md` -- Test: `tests/docs/test_content.py` -- Test: `tests/docs/test_topics.py` - -**Interfaces:** -- Consumes: `services.application-development`, `technologies.java`, tags `evaluate`, `migrate`, and `optimize` from `docs-taxonomy.yml`. -- Consumes: the common and `guide` metadata contract in `scripts/docs/content.py`. -- Produces: canonical topic key `application-development/java-lts-version-selection`. -- Produces: an official-source set containing at least one `learn.microsoft.com` URL and canonical `openjdk.org` URLs. - -- [ ] **Step 1: Verify the source set and record the claim boundaries** - -Read the complete contents of these official sources before drafting: - -| Source | Claims it supports | -|---|---| -| `https://learn.microsoft.com/en-us/java/openjdk/support` | Vendors assign different support timelines; Microsoft lists 17, 21, and 25 as LTS releases and handles Java 8 separately through Temurin-based images in its stated scenarios. | -| `https://openjdk.org/projects/jdk8/` | JDK 8 GA date and its role as the Java SE 8 reference implementation. | -| `https://openjdk.org/projects/jdk8/features` | Java 8 lambda, collection bulk operations/Stream foundation, date/time API, and removal of PermGen. | -| `https://openjdk.org/projects/jdk/17/` | JDK 17 GA date and vendor-qualified LTS wording. | -| `https://openjdk.org/projects/jdk/17/jeps-since-jdk-11` | Final and preview additions from 12 through 17, including records, sealed classes, switch expressions, text blocks, strong encapsulation, ZGC, and G1 improvements. | -| `https://openjdk.org/jeps/248` | G1 becomes the default garbage collector in JDK 9. | -| `https://openjdk.org/jeps/261` | The Java Platform Module System is delivered in JDK 9. | -| `https://openjdk.org/jeps/286` | Local-variable type inference is delivered in JDK 10. | -| `https://openjdk.org/jeps/321` | The standard HTTP Client API is delivered in JDK 11. | -| `https://openjdk.org/projects/jdk/21/` | JDK 21 GA date and vendor-qualified LTS wording. | -| `https://openjdk.org/projects/jdk/21/jeps-since-jdk-17` | Final and preview additions from 18 through 21. | -| `https://openjdk.org/projects/jdk/25/` | JDK 25 GA date and vendor-qualified LTS wording. | -| `https://openjdk.org/projects/jdk/25/jeps-since-jdk-21` | Final, preview, experimental, deprecated, and removed changes from 22 through 25. | -| `https://openjdk.org/jeps/444` | Virtual threads improve the thread-per-request style and throughput for high-concurrency blocking workloads, not CPU execution speed or latency by themselves. | -| `https://openjdk.org/jeps/439` | Generational ZGC goals and trade-offs in JDK 21. | -| `https://openjdk.org/jeps/491` | JDK 24 removes nearly all `synchronized`-related virtual-thread pinning cases. | -| `https://openjdk.org/jeps/483` | Opt-in AOT class loading and linking in JDK 24 targets startup and warm-up time. | -| `https://openjdk.org/jeps/515` | AOT method profiles in JDK 25 target JIT warm-up time. | -| `https://openjdk.org/jeps/506` | Scoped values are final in JDK 25 and are intended for immutable context sharing. | -| `https://openjdk.org/jeps/519` | Compact object headers become a product feature in JDK 25 but are not enabled by default. | -| `https://openjdk.org/jeps/521` | Generational Shenandoah becomes a product feature in JDK 25 but remains opt-in. | - -Do not generalize the Microsoft support dates to other vendors. Do not repeat -the workload-specific percentages shown in JEP 483 or JEP 519 as universal -performance expectations. - -- [ ] **Step 2: Create the canonical document with upstream sources** - -Create `docs/services/application-development/java-lts-version-selection/index.md` -with this front matter: - -```yaml ---- -services: -- application-development -official_sources: -- title: Support roadmap for the Microsoft Build of OpenJDK - url: https://learn.microsoft.com/en-us/java/openjdk/support -- title: JDK 8 Project - url: https://openjdk.org/projects/jdk8/ -- title: JDK 8 Features - url: https://openjdk.org/projects/jdk8/features -- title: JDK 17 Project - url: https://openjdk.org/projects/jdk/17/ -- title: JEPs in JDK 17 integrated since JDK 11 - url: https://openjdk.org/projects/jdk/17/jeps-since-jdk-11 -- title: JEP 248 - Make G1 the Default Garbage Collector - url: https://openjdk.org/jeps/248 -- title: JEP 261 - Module System - url: https://openjdk.org/jeps/261 -- title: JEP 286 - Local-Variable Type Inference - url: https://openjdk.org/jeps/286 -- title: JEP 321 - HTTP Client API - url: https://openjdk.org/jeps/321 -- title: JDK 21 Project - url: https://openjdk.org/projects/jdk/21/ -- title: JEPs in JDK 21 integrated since JDK 17 - url: https://openjdk.org/projects/jdk/21/jeps-since-jdk-17 -- title: JDK 25 Project - url: https://openjdk.org/projects/jdk/25/ -- title: JEPs in JDK 25 integrated since JDK 21 - url: https://openjdk.org/projects/jdk/25/jeps-since-jdk-21 -- title: JEP 444 - Virtual Threads - url: https://openjdk.org/jeps/444 -- title: JEP 439 - Generational ZGC - url: https://openjdk.org/jeps/439 -- title: JEP 491 - Synchronize Virtual Threads without Pinning - url: https://openjdk.org/jeps/491 -- title: JEP 483 - Ahead-of-Time Class Loading and Linking - url: https://openjdk.org/jeps/483 -- title: JEP 515 - Ahead-of-Time Method Profiling - url: https://openjdk.org/jeps/515 -- title: JEP 506 - Scoped Values - url: https://openjdk.org/jeps/506 -- title: JEP 519 - Compact Object Headers - url: https://openjdk.org/jeps/519 -- title: JEP 521 - Generational Shenandoah - url: https://openjdk.org/jeps/521 -document_type: guide -status: current -verification_status: verified -sources_checked_at: 2026-09-16 -last_verified: 2026-09-16 -review_cycle_days: 180 -applies_to: -- Java SE 8, 17, 21, 25 -title: Java 8·17·21·25 LTS 선택과 현대화 가이드 -description: Java 주요 LTS의 기능과 성능 특성을 비교하고 신규 도입 및 업그레이드 기준과 구현 패턴을 정리합니다. -technologies: -- java -tags: -- evaluate -- migrate -- optimize ---- -``` - -Use exactly one H1 and organize the body under these H2 sections: - -1. `먼저 내릴 결론` -2. `LTS를 해석할 때 주의할 점` -3. `버전별 누적 주요 변화` -4. `어떤 버전을 선택할까` -5. `최신 JDK가 성능에 유리할 수 있는 이유` -6. `업그레이드는 호환성과 현대화를 분리한다` -7. `버전 업그레이드 후 권장 구현 패턴` -8. `성능 검증 체크리스트` -9. `제약 사항` -10. `공식 참고 자료` - -The opening decision table must give these conditional recommendations: - -| Situation | Default choice | Reason | -|---|---|---| -| New service with a fully certified framework, libraries, agents, base image, and platform | Java 25 | Longest forward-looking support candidate and access to improvements delivered through 25 | -| Existing service where 25 support is not yet complete | Java 21 | Mature virtual-thread and pattern-matching baseline with broad ecosystem adoption | -| Dependency or product certification blocks 21 | Java 17 | Transitional compatibility target, not the preferred endpoint for unconstrained new work | -| Only legacy application/vendor constraints allow it | Java 8 | Maintain temporarily with a funded migration plan | - -Immediately qualify the table: the selected JDK distribution's support policy, -not the version number alone, determines the actual support period. - -The cumulative feature table must distinguish stable features from features -that require deliberate enablement: - -| Version | Stable highlights to explain | Operational or migration highlights | -|---|---|---| -| 8 | lambda/method references, Stream, default methods, `Optional`, `java.time`, `CompletableFuture` | Metaspace replaces PermGen | -| 17 | module system, `var`, standard HTTP Client, switch expressions, text blocks, records, pattern matching for `instanceof`, sealed classes | G1 default since 9, production ZGC since 15, helpful NPEs, strong encapsulation of JDK internals | -| 21 | virtual threads, record patterns, pattern matching for `switch`, sequenced collections, UTF-8 by default | Generational ZGC; dynamic agent loading warning | -| 25 | unnamed variables/patterns, FFM API, Class-File API, Stream Gatherers, Scoped Values, module imports, flexible constructor bodies | improved virtual-thread `synchronized` behavior, AOT cache/tooling, compact object headers, opt-in Generational Shenandoah, richer JFR; Security Manager disabled and 32-bit x86 removed | - -Add a separate note that Structured Concurrency, Stable Values, primitive -patterns, PEM encodings, and the Vector API are not final in JDK 25. Do not -recommend them as the default production migration pattern. State separately -that Generational Shenandoah and compact object headers are product features -but are not enabled by default. - -Explain performance through mechanisms rather than promises: - -- Newer HotSpot builds include cumulative JIT, GC, runtime, platform, and - diagnostic work, but application results depend on workload and configuration. -- Virtual threads can increase throughput for high-concurrency blocking I/O by - reducing the cost of one-thread-per-task; they do not make CPU-bound work - faster and do not remove downstream capacity limits. -- Generational ZGC can reduce allocation stalls and memory overhead relative to - non-generational ZGC for suitable workloads, but G1 can still be the better - throughput or simplicity choice. -- JDK 24/25 AOT cache facilities target startup and warm-up, require a - representative training workflow, and are not equivalent to native-image - compilation. -- Compact object headers can improve object density when explicitly enabled, - but JEP 519 says the layout is not the default. -- Language features mainly improve clarity, modeling, and maintainability; - never present records or pattern matching as automatic runtime speedups. - -Use a two-stage migration: - -1. Compatibility: inventory the distribution/support policy, build tool, - framework, dependencies, annotation processors, agents, native/JNI access, - removed modules/APIs, internal JDK access, JVM flags, container image, and - rollback artifact. Run the existing application on the target runtime before - changing its programming model. -2. Modernization: adopt final language/API features in small reviewable changes, - re-run correctness and load tests after each change, and keep preview - features outside the default production path. - -Include these complete code patterns. - -Record for an immutable value: - -```java -public record CustomerId(String value) { - public CustomerId { - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("value must not be blank"); - } - } -} -``` - -Sealed domain model with exhaustive Java 21 matching: - -```java -sealed interface PaymentResult permits Approved, Declined {} - -record Approved(String approvalId) implements PaymentResult {} - -record Declined(String reason) implements PaymentResult {} - -final class PaymentMessages { - private PaymentMessages() {} - - static String message(PaymentResult result) { - return switch (result) { - case Approved(var approvalId) -> "approved: " + approvalId; - case Declined(var reason) -> "declined: " + reason; - }; - } -} -``` - -Virtual-thread fan-out with an explicit downstream limit: - -```java -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; - -final class DownstreamCalls { - private static final Semaphore DOWNSTREAM_LIMIT = new Semaphore(100); - - private DownstreamCalls() {} - - static String fetch(Callable request) throws Exception { - DOWNSTREAM_LIMIT.acquire(); - try { - return request.call(); - } finally { - DOWNSTREAM_LIMIT.release(); - } - } - - static List fetchAll(List> requests) - throws InterruptedException, ExecutionException { - try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { - var futures = requests.stream() - .map(request -> executor.submit(() -> fetch(request))) - .toList(); - - try { - var results = new ArrayList(futures.size()); - for (var future : futures) { - results.add(future.get()); - } - return List.copyOf(results); - } catch (InterruptedException error) { - cancelAll(futures); - Thread.currentThread().interrupt(); - throw error; - } catch (ExecutionException error) { - cancelAll(futures); - throw error; - } - } - } - - private static void cancelAll(List> futures) { - futures.forEach(future -> future.cancel(true)); - } -} -``` - -Explain that a connection pool, rate limiter, semaphore, or downstream quota -still controls real concurrency. Do not pool virtual threads themselves. -Require client-level connect, request, and read deadlines because interruption -and future cancellation do not replace I/O timeouts. - -JDK 25 scoped value for immutable request context: - -```java -final class RequestContext { - private static final ScopedValue TRACE_ID = - ScopedValue.newInstance(); - - private RequestContext() {} - - static void handle(String traceId, Runnable action) { - ScopedValue.where(TRACE_ID, traceId).run(action); - } - - static String traceId() { - return TRACE_ID.get(); - } -} -``` - -State that framework-provided context propagation should remain authoritative -when it already defines the request context lifecycle. Scope the stable example -to callees on the same thread. State that ordinary threads and executor tasks -do not inherit bindings, while inheritance through `StructuredTaskScope` -depends on Structured Concurrency, which remains preview in JDK 25. - -End with a measurement matrix requiring the same application artifact, traffic, -data set, CPU/memory limits, warm-up policy, framework and dependency versions, -and explicit JVM/GC flags. Compare throughput, errors, p50/p95/p99 latency, -startup, warm-up, CPU, RSS, heap, allocation rate, GC frequency, and GC pauses. -Require canary rollout and a rollback that restores both the JDK image and JVM -flags. - -- [ ] **Step 3: Run source validation to prove the new upstream host is rejected** - -Run: - -```bash -python3.13 scripts/docs/validate_sources.py -``` - -Expected: FAIL and report `official source host is not allowed: openjdk.org` -for the new guide. This proves the document is discovered and the source -allowlist must be updated rather than bypassed. - -- [ ] **Step 4: Allow the canonical OpenJDK source host** - -Add `openjdk.org` to `official_source_hosts` in `docs-taxonomy.yml`, keeping the -existing hosts and `required_source_host: learn.microsoft.com` unchanged: - -```yaml -official_source_hosts: - - learn.microsoft.com - - openjdk.org - - kubernetes.io - - www.cncf.io - - cncf.io - - github.com - - modelcontextprotocol.io - - py.sdk.modelcontextprotocol.io - - prices.azure.com - - www.rfc-editor.org -``` - -- [ ] **Step 5: Write a failing publication-boundary test** - -In `tests/docs/test_pages_artifact.py`, after the real-site build, assert: - -```python -assert not (site / "superpowers").exists() -search = json.loads( - (site / "search" / "search_index.json").read_text(encoding="utf-8") -) -assert not any( - entry["location"].startswith("superpowers/") - for entry in search["docs"] -) -``` - -Run: - -```bash -python3.13 -m pytest \ - tests/docs/test_pages_artifact.py::test_current_published_assets_survive_pages_artifact_hidden_exclusion \ - -q -``` - -Expected: FAIL because `site/superpowers/` exists before the exclusion is added. - -- [ ] **Step 6: Exclude internal planning artifacts** - -Append `superpowers/**` to `exclude_docs` in `mkdocs.yml`. Update -`test_mkdocs_keeps_navigation_and_search_metadata_driven` to expect: - -```python -assert config["exclude_docs"].splitlines() == [ - "services/**/samples/**", - "superpowers/**", -] -``` - -Run the failing test and the config contract test together. Expected: both pass. - -- [ ] **Step 7: Run targeted document-contract tests** - -Run: - -```bash -python3.13 scripts/docs/validate_metadata.py -python3.13 scripts/docs/validate_sources.py -python3.13 scripts/docs/validate_links.py -python3.13 scripts/docs/validate_public_safety.py -python3.13 -m pytest tests/docs/test_content.py tests/docs/test_topics.py -q -``` - -Expected: every validator exits 0 and all selected tests pass. If a command -fails, use `superpowers:systematic-debugging` before changing the document or -validator. - -- [ ] **Step 8: Review the rendered-content contract** - -Check: - -```bash -git diff --check -git diff -- docs-taxonomy.yml docs/services/application-development/java-lts-version-selection/index.md -``` - -Expected: - -- one H1 and no skipped heading levels; -- no `topic_order`, `redirect_from`, sample, image, or manual navigation change; -- every Java 25 preview/incubator feature is visibly separated from stable - recommendations; -- no universal performance percentage; -- no claim that all vendors share Microsoft's support dates; -- no secret, customer identifier, internal host, or private endpoint. - -- [ ] **Step 9: Commit the guide and publication boundary** - -```bash -git add docs-taxonomy.yml mkdocs.yml \ - docs/services/application-development/java-lts-version-selection/index.md \ - tests/docs/test_pages_artifact.py tests/docs/test_site_pipeline.py -git commit -m "docs(java): LTS 버전 선택과 현대화 가이드 추가" \ - -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" -``` - -### Task 2: Verify generated navigation, search, and compatibility output - -**Files:** -- Verify: `docs/services/application-development/java-lts-version-selection/index.md` -- Verify: generated `site/services/application-development/java-lts-version-selection/index.html` -- Verify: generated service and explore indexes -- Modify: none unless a validation failure identifies a defect introduced by Task 1 - -**Interfaces:** -- Consumes: the canonical topic and taxonomy from Task 1. -- Produces: a strict MkDocs build in which the new topic appears once under `application-development`, is filterable by `java`, `evaluate`, `migrate`, and `optimize`, and does not alter the pre-Pages baseline mapping. - -- [ ] **Step 1: Run the complete documentation test suite** - -Run: - -```bash -python3.13 -m pytest tests/docs -q -``` - -Expected: all tests pass. - -- [ ] **Step 2: Run every repository-required validator** - -Run: - -```bash -python3.13 scripts/docs/validate_metadata.py -python3.13 scripts/docs/validate_sources.py -python3.13 scripts/docs/validate_links.py -python3.13 scripts/docs/validate_public_safety.py -mkdocs build --strict -python3.13 scripts/docs/validate_search_index.py -python3.13 scripts/docs/audit_pre_pages.py -``` - -Expected: every command exits 0. `mkdocs build --strict` creates the canonical -HTML page, and the pre-Pages audit accepts the increased current document count -without changing the fixed baseline inventory. - -- [ ] **Step 3: Verify the generated page and discovery metadata** - -Run: - -```bash -test -f site/services/application-development/java-lts-version-selection/index.html -grep -q "Java 8·17·21·25 LTS 선택과 현대화 가이드" \ - site/services/application-development/java-lts-version-selection/index.html -grep -q "java-lts-version-selection" site/services/application-development/index.html -git status --short -``` - -Expected: the page exists, its H1 text is rendered, the application-development -service index links to it, and generated `site/` output is ignored rather than -staged. - -- [ ] **Step 4: Record final verification without committing generated files** - -Run: - -```bash -git status --short -git --no-pager log -3 --oneline -``` - -Expected: no uncommitted source changes remain, `site/` is absent from status, -and the guide commit follows the committed design and plan history. diff --git a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md b/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md deleted file mode 100644 index bec79474..00000000 --- a/docs/superpowers/specs/2026-09-16-java-lts-version-selection-design.md +++ /dev/null @@ -1,173 +0,0 @@ -# Java LTS 버전 선택과 현대화 가이드 설계 - -## 목적 - -운영 중인 Java 8 또는 17 서비스를 Java 21 또는 25로 전환하려는 백엔드 -팀이 다음 질문에 답할 수 있는 공개 기술 가이드를 작성한다. - -- Java 8, 17, 21, 25에서 실무에 영향을 주는 주요 변화는 무엇인가? -- 신규 서비스와 기존 서비스는 어떤 LTS 버전을 선택해야 하는가? -- 최신 JDK가 성능에 유리할 수 있는 이유와 보장할 수 없는 부분은 무엇인가? -- 버전을 올린 뒤 어떤 순서와 코드 패턴으로 현대화해야 하는가? - -## 산출물 - -- canonical topic: - `docs/services/application-development/java-lts-version-selection/index.md` -- 문서 유형: `guide` -- 대표 서비스: `application-development` -- 기술: `java` -- 태그: `evaluate`, `migrate`, `optimize` -- sample과 이미지는 추가하지 않는다. -- 기존 taxonomy 값으로 충분하므로 새 서비스, 기술, 태그는 추가하지 않는다. - 공식 OpenJDK 원문을 기록할 수 있도록 출처 host allowlist에 - `openjdk.org`만 추가한다. -- `docs/superpowers/`의 내부 설계·계획 문서는 MkDocs page, navigation과 - search index에서 제외한다. - -## 대상 독자와 전제 - -주 독자는 Java 8 또는 17 기반의 운영 서비스를 보유하고 있으며, 지원 기간, -호환성, 성능과 개발 생산성을 함께 고려해 다음 LTS를 선택해야 하는 백엔드 -개발·플랫폼 팀이다. 특정 클라우드, 프레임워크 또는 JDK 배포판에 종속되지 -않는 결정을 돕는다. - -문서에서 “LTS”는 Java SE 또는 OpenJDK 프로젝트가 모든 배포판에 일괄 -부여하는 속성이 아니라 JDK 공급자의 지원 정책이라는 점을 먼저 밝힌다. -지원 기간은 실제로 선택한 배포판의 정책을 별도로 확인하도록 안내한다. - -## 콘텐츠 구조 - -### 1. 결론과 빠른 선택표 - -문서 첫 부분에서 조건별 기본 선택을 제시한다. - -- Java 8: 애플리케이션이나 상용 제품 제약으로 즉시 이전할 수 없는 기존 - 시스템에만 유지한다. -- Java 17: 의존성 또는 플랫폼이 21 이상을 아직 인증하지 않은 경우의 - 호환성 전환점으로 사용한다. -- Java 21: 생태계 성숙도와 현대 기능의 균형을 중시하는 기존 서비스의 - 보수적인 기본 선택으로 사용한다. -- Java 25: 런타임·프레임워크·관측 도구가 지원하고 검증을 통과한 신규 - 서비스 또는 장기 운영 서비스의 우선 후보로 사용한다. - -이는 절대 규칙이 아니다. 배포판 지원 기간, 프레임워크 최소·최대 버전, -라이브러리와 에이전트 호환성, 롤백 가능성, 워크로드 벤치마크를 최종 -결정 조건으로 둔다. - -### 2. 버전별 주요 기능 - -모든 JEP를 나열하지 않고, 코딩 방식이나 운영 특성을 바꾸는 기능만 네 -범주로 요약한다. - -| 버전 | 언어·모델링 | 표준 API | 동시성 | JVM·GC | -|---|---|---|---|---| -| 8 | lambda, method reference, default method | Stream, Optional, `java.time` | `CompletableFuture` | Metaspace, 당시 기본 GC 변화 | -| 17 | record, sealed class, pattern matching for `instanceof`, switch expression, text block | 표준 HTTP Client 등 9~17의 누적 변화 | 기존 executor 중심 | G1 성숙, 저지연 GC 선택지와 컨테이너 인식 개선 | -| 21 | record pattern, pattern matching for `switch` | sequenced collections | virtual thread | generational ZGC | -| 25 | 22~25에서 확정된 API·언어 기능과 25의 주요 기능 | 최신 표준 API 변화 | scoped value 등 확정 상태를 확인한 기능 | compact object headers 등 25의 주요 런타임 변화 | - -각 항목은 최종 확정 기능과 preview/incubator 기능을 명시적으로 구분한다. -Java 17과 21, 25는 직전 LTS 이후 누적된 기능이라는 점도 표에 표시한다. -정확한 JEP 번호와 최종 상태는 집필 시 공식 원문으로 다시 확인한다. - -### 3. 최신 버전이 성능에 유리할 수 있는 이유 - -성능을 “버전 번호만 올리면 자동 향상”으로 설명하지 않는다. 다음 경로를 -분리해 설명한다. - -- JIT와 런타임 최적화가 장기 실행 처리량과 코드 생성 품질에 미치는 영향 -- G1, ZGC 등 GC 개선이 pause time, 처리량, 힙 크기와 메모리 사용량에 - 미치는 영향 -- compact object headers 같은 메모리 레이아웃 변화가 객체가 많은 - 워크로드에 미칠 수 있는 영향 -- virtual thread가 blocking I/O 서비스의 동시성 비용을 줄일 수 있지만 - CPU-bound 연산 자체를 빠르게 만들지는 않는다는 경계 -- 컨테이너 자원 인식과 진단 기능 개선이 운영 안정성에 주는 간접 효과 - -워크로드, 힙 크기, GC, JVM 옵션, 워밍업, 프레임워크 버전이 다르면 결과가 -달라지므로 숫자로 된 보편적 향상률은 제시하지 않는다. - -### 4. 버전 업그레이드와 현대화 패턴 - -업그레이드를 두 단계로 분리한다. - -1. **호환성 단계**: 목표 JDK에서 기존 동작을 유지한다. 빌드 도구, - 프레임워크, 라이브러리, Java agent, 제거된 모듈·API, 강한 캡슐화, - JVM 옵션을 먼저 점검하고 회귀·부하 테스트를 통과시킨다. -2. **현대화 단계**: 안정 기능을 작은 변경 단위로 도입한다. 기능 전환과 - 런타임 전환을 한 번에 섞지 않아 회귀 원인과 롤백 경계를 명확히 한다. - -본문에는 다음 전후 패턴을 짧은 코드로 제시한다. - -- 불변 DTO와 값 객체: 보일러플레이트 class에서 `record`로 전환 -- 닫힌 도메인 모델: 상속 가능한 class 계층에서 `sealed` 계층으로 전환 -- 타입 분기: cast 중심 `instanceof` 연쇄에서 pattern matching으로 전환 -- 값 계산 분기: statement형 `switch`에서 exhaustive switch expression으로 - 전환 -- blocking I/O fan-out: 고정 thread pool을 무조건 크게 만드는 방식에서 - virtual-thread-per-task 실행기로 전환 - -virtual thread 예제에는 요청별 무제한 외부 호출을 허용하지 않고 semaphore, -connection pool 또는 대상 시스템의 용량에 맞춘 별도 동시성 제한이 -필요하다고 설명한다. preview 기능은 운영 기본 패턴에서 제외하고 별도 -참고로만 다룬다. - -### 5. 측정과 도입 체크리스트 - -동일한 애플리케이션과 트래픽으로 다음을 비교하도록 한다. - -- 처리량과 오류율 -- p50, p95, p99 latency -- 시작 시간과 warm-up 구간 -- CPU 사용량, RSS, heap 사용량 -- GC pause, allocation rate, collection 빈도 - -기준 JDK와 목표 JDK에서 프레임워크·의존성, 컨테이너 제한, JVM 옵션, -GC와 테스트 데이터를 기록한다. 운영 반영은 canary 또는 점진적 배포로 -진행하고 JDK 이미지와 JVM 옵션을 함께 되돌릴 수 있는 롤백 조건을 둔다. - -## 주장과 출처 원칙 - -- 언어와 JVM 기능은 OpenJDK JEP 원문과 JDK 릴리스 자료를 우선한다. -- LTS 지원 기간은 특정 공급자의 공식 지원 정책으로 한정해 표현한다. -- 저장소의 현재 출처 검증 계약을 만족하도록 Microsoft Learn의 Java - 지원 자료를 포함하고, `openjdk.org`의 canonical JEP 원문을 함께 사용한다. -- 실제로 전문을 확인한 문서만 `official_sources`에 기록한다. -- 중요한 기능 상태와 적용 범위를 모두 확인한 경우에만 - `verification_status: verified`로 표시한다. 확인이 불완전하면 - `needs-review`로 남긴다. -- 출처 확인 기준일은 `2026-09-16`으로 기록한다. - -## 비목표 - -- Java 8 이후 모든 JEP의 완전한 목록 -- 특정 JDK 배포판의 가격 또는 전체 지원 기간 비교 -- Spring Boot, Jakarta EE 등 프레임워크별 상세 호환성 행렬 -- 근거 데이터가 없는 보편적 성능 향상률 -- preview 기능을 사용하는 운영 코드 권장 -- 실행 가능한 벤치마크 sample 제공 - -## 검증 - -내용 작성 뒤 다음을 확인한다. - -1. 각 기능의 도입 버전과 final/preview/incubator 상태를 공식 원문과 - 대조한다. -2. 버전 선택 문구가 조건부 권장이고 배포판 지원 정책을 일반화하지 - 않았는지 검토한다. -3. 코드 예제가 해당 기능이 final인 최소 버전과 일치하는지 확인한다. -4. 저장소 루트에서 필수 문서 검증을 모두 실행한다. - -```text -python -m pytest tests/docs -q -python scripts/docs/validate_metadata.py -python scripts/docs/validate_sources.py -python scripts/docs/validate_links.py -python scripts/docs/validate_public_safety.py -mkdocs build --strict -python scripts/docs/validate_search_index.py -python scripts/docs/audit_pre_pages.py -``` - -생성된 `site/`와 검색 인덱스 산출물은 커밋하지 않는다. diff --git a/mkdocs.yml b/mkdocs.yml index c6d7acf9..ecf9af16 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,7 +9,6 @@ site_dir: site use_directory_urls: true exclude_docs: | services/**/samples/** - superpowers/** validation: links: diff --git a/tests/docs/test_pages_artifact.py b/tests/docs/test_pages_artifact.py index 279562a5..c8d60075 100644 --- a/tests/docs/test_pages_artifact.py +++ b/tests/docs/test_pages_artifact.py @@ -1,4 +1,3 @@ -import json from pathlib import Path, PurePosixPath import tarfile @@ -32,15 +31,6 @@ def test_current_published_assets_survive_pages_artifact_hidden_exclusion(tmp_pa site = tmp_path / "site" build(load_config(str(ROOT / "mkdocs.yml"), site_dir=str(site), strict=True)) - assert not (site / "superpowers").exists() - search = json.loads( - (site / "search" / "search_index.json").read_text(encoding="utf-8") - ) - assert not any( - entry["location"].startswith("superpowers/") - for entry in search["docs"] - ) - hidden_controls = [ ".artifact-control", ".artifact-controls/file.txt", diff --git a/tests/docs/test_site_pipeline.py b/tests/docs/test_site_pipeline.py index f8589445..c9ccf92d 100644 --- a/tests/docs/test_site_pipeline.py +++ b/tests/docs/test_site_pipeline.py @@ -331,10 +331,7 @@ def test_mkdocs_keeps_navigation_and_search_metadata_driven() -> None: if isinstance(plugin, dict) and "search" in plugin ) assert search["lang"] == ["ko", "en"] - assert config["exclude_docs"].splitlines() == [ - "services/**/samples/**", - "superpowers/**", - ] + assert config["exclude_docs"].strip() == "services/**/samples/**" def test_topic_and_sample_styles_are_responsive_and_accessible() -> None: