- name
- java-migration
- description
- Guide for upgrading Java projects between major versions (8→11→17→21→25). Use when user says "upgrade Java", "migrate to Java 25", "update Java version", or when modernizing legacy projects.
# Java Migration Skill
Step-by-step guide for upgrading Java projects between major versions.
## When to Use
- User says "upgrade to Java 25" / "migrate from Java 8" / "update Java version"
- Modernizing legacy projects
- Spring Boot 2.x → 3.x → 4.x migration
- Preparing for LTS version adoption
## Migration Paths
```
Java 8 (LTS) → Java 11 (LTS) → Java 17 (LTS) → Java 21 (LTS) → Java 25 (LTS)
│ │ │ │ │
└──────────────┴───────────────┴──────────────┴───────────────┘
Always migrate LTS → LTS
```
---
## Quick Reference: What Breaks
| From → To | Major Breaking Changes |
|-----------|------------------------|
| 8 → 11 | Removed `javax.xml.bind`, module system, internal APIs |
| 11 → 17 | Sealed classes (preview→final), strong encapsulation |
| 17 → 21 | Pattern matching changes, `finalize()` deprecated for removal |
| 21 → 25 | Security Manager removed, Unsafe methods removed, 32-bit dropped |
---
## Migration Workflow
### Step 1: Assess Current State
```bash
# Check current Java version
java -version
# Check compiler target in Maven
grep -r "maven.compiler" pom.xml
# Find usage of removed APIs
grep -r "sun\." --include="*.java" src/
grep -r "javax\.xml\.bind" --include="*.java" src/
```
### Step 2: Update Build Configuration
**Maven:**
```xml
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<!-- Or with compiler plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
```
**Gradle:**
```groovy
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
```
### Step 3: Fix Compilation Errors
Run compile and fix errors iteratively:
```bash
mvn clean compile 2>&1 | head -50
```
### Step 4: Run Tests
```bash
mvn test
```
### Step 5: Check Runtime Warnings
```bash
# Run with illegal-access warnings
java --illegal-access=warn -jar app.jar
```
---
## Java 8 → 11 Migration
### Removed APIs
| Removed | Replacement |
|---------|-------------|
| `javax.xml.bind` (JAXB) | Add dependency: `jakarta.xml.bind-api` + `jaxb-runtime` |
| `javax.activation` | Add dependency: `jakarta.activation-api` |
| `javax.annotation` | Add dependency: `jakarta.annotation-api` |
| `java.corba` | No replacement (rarely used) |
| `java.transaction` | Add dependency: `jakarta.transaction-api` |
| `sun.misc.Base64*` | Use `java.util.Base64` |
| `sun.misc.Unsafe` (partially) | Use `VarHandle` where possible |
### Add Missing Dependencies (Maven)
```xml
<!-- JAXB (if needed) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.4</version>
<scope>runtime</scope>
</dependency>
<!-- Annotation API -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
```
### Module System Issues
If using reflection on JDK internals, add JVM flags:
```bash
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
```
**Maven Surefire:**
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
```
### New Features to Adopt
```java
// var (local variable type inference)
var list = new ArrayList<String>(); // instead of ArrayList<String> list = ...
// String methods
" hello ".isBlank(); // true for whitespace-only
" hello ".strip(); // better trim() (Unicode-aware)
"line1\nline2".lines(); // Stream<String>
"ha".repeat(3); // "hahaha"
// Collection factory methods (Java 9+)
List.of("a", "b", "c"); // immutable list
Set.of(1, 2, 3); // immutable set
Map.of("k1", "v1"); // immutable map
// Optional improvements
optional.ifPresentOrElse(
value -> process(value),
() -> handleEmpty()
);
// HTTP Client (replaces HttpURLConnection)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
```
---
## Java 11 → 17 Migration
### Breaking Changes
| Change | Impact |
|--------|--------|
| Strong encapsulation | `--illegal-access` no longer works, must use explicit `--add-opens` |
| Sealed classes (final) | If you used preview features |
| Pattern matching instanceof | Preview → final syntax change |
### New Features to Adopt
```java
// Records (immutable data classes)
public record User(String name, String email) {}
// Auto-generates: constructor, getters, equals, hashCode, toString
// Sealed classes
public sealed class Shape permits Circle, Rectangle {}
public final class Circle extends Shape {}
public final class Rectangle extends Shape {}
// Pattern matching for instanceof
if (obj instanceof String s) {
System.out.println(s.length()); // s already cast
}
// Switch expressions
String result = switch (day) {
case MONDAY, FRIDAY -> "Work";
case SATURDAY, SUNDAY -> "Rest";
default -> "Midweek";
};
// Text blocks
String json = """
{
"name": "John",
"age": 30
}
""";
// Helpful NullPointerException messages
// a.b.c.d() → tells exactly which part was null
```
---
## Java 17 → 21 Migration
### Breaking Changes
| Change | Impact |
|--------|--------|
| Pattern matching switch (final) | Minor syntax differences from preview |
| `finalize()` deprecated for removal | Replace with `Cleaner` or try-with-resources |
| UTF-8 by default | May affect file reading if assumed platform encoding |
### New Features to Adopt
```java
// Virtual Threads (Project Loom) - MAJOR
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> handleRequest());
}
// Or simply:
Thread.startVirtualThread(() -> doWork());
// Pattern matching in switch
String formatted = switch (obj) {
case Integer i -> "int: " + i;
case String s -> "string: " + s;
case null -> "null value";
default -> "unknown";
};
// Record patterns
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
// Sequenced Collections
List<String> list = new ArrayList<>();
list.addFirst("first"); // new method
list.addLast("last"); // new method
list.reversed(); // reversed view
// String templates (preview in 21)
// May need --enable-preview
// Scoped Values (preview) - replace ThreadLocal
ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
ScopedValue.where(CURRENT_USER, user).run(() -> {
// CURRENT_USER.get() available here
});
```
---
## Java 21 → 25 Migration
### Breaking Changes
| Change | Impact |
|--------|--------|
| Security Manager removed | Applications relying on it need alternative security approaches |
| `sun.misc.Unsafe` methods removed | Use `VarHandle` or FFM API instead |
| 32-bit platforms dropped | No more x86-32 support |
| Record pattern variables final | Cannot reassign pattern variables in switch |
| `ScopedValue.orElse(null)` disallowed | Must provide non-null default |
| Dynamic agents restricted | Requires `-XX:+EnableDynamicAgentLoading` flag |
### Check for Unsafe Usage
```bash
# Find sun.misc.Unsafe usage
grep -rn "sun\.misc\.Unsafe" --include="*.java" src/
# Find Security Manager usage
grep -rn "SecurityManager\|System\.getSecurityManager" --include="*.java" src/
```
### New Features to Adopt
```java
// Scoped Values (FINAL in Java 25) - replaces ThreadLocal
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public void handleRequest(User user) {
ScopedValue.where(CURRENT_USER, user).run(() -> {
processRequest(); // CURRENT_USER.get() available here and in child threads
});
}
// Structured Concurrency (Preview, redesigned API in 25)
try (StructuredTaskScope.ShutdownOnFailure scope = StructuredTaskScope.open()) {
Subtask<User> userTask = scope.fork(() -> fetchUser(id));
Subtask<Orders> ordersTask = scope.fork(() -> fetchOrders(id));
scope.join();
scope.throwIfFailed();
return new Profile(userTask.get(), ordersTask.get());
}
// Stable Values (Preview) - lazy initialization made easy
private static final StableValue<ExpensiveService> SERVICE =
StableValue.of(() -> new ExpensiveService());
public void useService() {
SERVICE.get().doWork(); // Initialized on first access, cached thereafter
}
// Compact Object Headers - automatic, no code changes
// Objects now use 64-bit headers instead of 128-bit (less memory)
// Primitive Patterns in instanceof (Preview)
if (obj instanceof int i) {
System.out.println("int value: " + i);
}
// Module Import Declarations (Preview)
import module java.sql; // Import all public types from module
```
### Performance Improvements (Automatic)
Java 25 includes several automatic performance improvements:
- **Compact Object Headers**: 8 bytes instead of 16 bytes per object
- **String.hashCode() constant folding**: Faster Map lookups with String keys
- **AOT class loading**: Faster startup with ahead-of-time cache
- **Generational Shenandoah GC**: Better throughput, lower pauses
### Migration with OpenRewrite
```bash
# Automated Java 25 migration
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:LATEST \
-Drewrite.activeRecipes=org.openrewrite.java.migrate.UpgradeToJava25
```
---
## Spring Boot Migration
### Spring Boot 2.x → 3.x
**Requirements:**
- Java 17+ (mandatory)
- Jakarta EE 9+ (javax.* → jakarta.*)
**Package Renames:**
```java
// Before (Spring Boot 2.x)
import javax.persistence.*;
import javax.validation.*;
import javax.servlet.*;
// After (Spring Boot 3.x)
import jakarta.persistence.*;
import jakarta.validation.*;
GitHub에서 보기