| name | java-8-to-11 |
| description | Upgrade Java projects from JDK 8 to JDK 11 covering module system, removed Java EE modules (JAXB, JAX-WS, CORBA), var keyword, HTTP Client, collection factory methods |
GitHub Copilot Instructions: Java 8 to Java 11 Upgrade Guide
Project Context
This guide provides comprehensive GitHub Copilot instructions for upgrading Java projects from JDK 8 to JDK 11, covering the module system, removed Java EE modules, new language features, API changes, and migration patterns based on JEPs integrated in Java 9, 10, and 11.
Critical Migration: Removed Java EE and CORBA Modules
JEP 320: Remove the Java EE and CORBA Modules (Java 11)
Migration Pattern: Add explicit dependencies for removed modules
Maven dependencies to add:
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.9</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>1.3.3</version>
</dependency>
Gradle dependencies to add:
implementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.3")
runtimeOnly("org.glassfish.jaxb:jaxb-runtime:2.3.9")
implementation("jakarta.xml.ws:jakarta.xml.ws-api:2.3.3")
runtimeOnly("com.sun.xml.ws:jaxws-rt:2.3.7")
implementation("jakarta.activation:jakarta.activation-api:1.2.2")
implementation("jakarta.annotation:jakarta.annotation-api:1.3.5")
implementation("jakarta.transaction:jakarta.transaction-api:1.3.3")
Critical Migration: Module System (JPMS)
JEP 261: Module System (Java 9)
Migration Pattern: Understand and address module encapsulation
String encoded = Base64.getEncoder().encodeToString(data);
byte[] decoded = Base64.getDecoder().decode(encoded);
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class AtomicCounter {
private volatile int count;
private static final VarHandle COUNT_HANDLE;
static {
try {
COUNT_HANDLE = MethodHandles.lookup()
.findVarHandle(AtomicCounter.class, "count", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public int incrementAndGet() {
return (int) COUNT_HANDLE.getAndAdd(this, 1) + 1;
}
public boolean compareAndSet(int expected, int newValue) {
return COUNT_HANDLE.compareAndSet(this, expected, newValue);
}
}
JVM flags for gradual migration:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-exports java.base/sun.nio.ch=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
Optional: Adding a module-info.java
module com.example.myapp {
requires java.sql;
requires java.logging;
requires java.net.http;
exports com.example.myapp.api;
opens com.example.myapp.model to com.fasterxml.jackson.databind;
}
Language Features and API Changes
JEP 286: Local-Variable Type Inference (Java 10)
Migration Pattern: Use var for local variable declarations
Map<String, List<Employee>> departmentEmployees = new HashMap<>();
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
List<String> filteredNames = names.stream()
.filter(n -> n.length() > 3)
.collect(Collectors.toList());
var departmentEmployees = new HashMap<String, List<Employee>>();
var reader = new BufferedReader(new FileReader("data.txt"));
var filteredNames = names.stream()
.filter(n -> n.length() > 3)
.collect(Collectors.toList());
var count = 0;
var names = List.of("Alice", "Bob");
var stream = names.stream();
var path = Path.of("/tmp/data.txt");
var connection = DriverManager.getConnection(url);
ProcessingResult result = service.process(data);
JEP 323: Local-Variable Syntax for Lambda Parameters (Java 11)
Migration Pattern: Use var in lambda expressions for annotations
list.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
list.stream()
.map((@NotNull var s) -> s.toUpperCase())
.collect(Collectors.toList());
BiFunction<String, String, String> concat = (var a, var b) -> a + b;
JEP 269: Convenience Factory Methods for Collections (Java 9)
Migration Pattern: Replace verbose collection initialization
List<String> list = Collections.unmodifiableList(
Arrays.asList("a", "b", "c")
);
Set<String> set = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("a", "b", "c"))
);
Map<String, Integer> map = Collections.unmodifiableMap(
new HashMap<String, Integer>() {{
put("one", 1);
put("two", 2);
put("three", 3);
}}
);
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of(
"one", 1,
"two", 2,
"three", 3
);
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("one", 1),
Map.entry("two", 2),
Map.entry("three", 3)
);
List<String> copy = List.copyOf(existingList);
Set<String> setCopy = Set.copyOf(existingSet);
Map<String, Integer> mapCopy = Map.copyOf(existingMap);
var unmodifiableList = stream.collect(Collectors.toUnmodifiableList());
var unmodifiableSet = stream.collect(Collectors.toUnmodifiableSet());
var unmodifiableMap = stream.collect(
Collectors.toUnmodifiableMap(Item::key, Item::value)
);
Important: List.of(), Set.of(), and Map.of() return immutable collections. They do not allow null elements and throw UnsupportedOperationException on mutation attempts.
Stream API Improvements (Java 9)
Migration Pattern: Use new stream operations
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
if (n >= 10) break;
result.add(n);
}
List<Integer> result = numbers.stream()
.takeWhile(n -> n < 10)
.collect(Collectors.toList());
List<Integer> afterTen = numbers.stream()
.dropWhile(n -> n < 10)
.collect(Collectors.toList());
Stream<String> stream = value != null ? Stream.of(value) : Stream.empty();
Stream<String> stream = Stream.ofNullable(value);
for (int i = 0; i < 10; i++) { }
IntStream.iterate(0, i -> i < 10, i -> i + 1)
.forEach(i -> { });
Optional Improvements (Java 9-11)
Migration Pattern: Use enhanced Optional methods
if (optional.isPresent()) {
process(optional.get());
} else {
handleEmpty();
}
optional.ifPresentOrElse(
this::process,
this::handleEmpty
);
Optional<String> result = primary.isPresent() ? primary : fallback();
Optional<String> result = primary.or(this::fallback);
List<String> list = optionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
List<String> list = optionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
if (optional.isEmpty()) {
}
JEP 321: HTTP Client (Java 11)
Migration Pattern: Replace HttpURLConnection with the new HTTP Client
URL url = new URL("https://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
int status = conn.getResponseCode();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
conn.disconnect();
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode();
String body = response.body();
CompletableFuture<HttpResponse<String>> future = client.sendAsync(
request, HttpResponse.BodyHandlers.ofString());
future.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"name\": \"Alice\", \"email\": \"alice@example.com\"}"))
.build();
HttpClient configuredClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
String API Enhancements (Java 11)
Migration Pattern: Use new String methods
boolean blank = str == null || str.trim().isEmpty();
boolean blank = str.isBlank();
String[] lines = text.split("\\R");
text.lines()
.filter(line -> !line.isBlank())
.map(String::strip)
.forEach(System.out::println);
String trimmed = str.trim();
String stripped = str.strip();
String leftStripped = str.stripLeading();
String rightStripped = str.stripTrailing();
String dashes = new String(new char[20]).replace('\0', '-');
String dashes = String.join("", Collections.nCopies(20, "-"));
String dashes = "-".repeat(20);
String indent = " ".repeat(4);
Files API Enhancements (Java 11)
Migration Pattern: Simplified file read/write operations
String content = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
String content = Files.readString(Path.of("data.txt"));
Files.writeString(Path.of("output.txt"), content);
Files.writeString(Path.of("log.txt"), "appended line\n",
StandardOpenOption.APPEND, StandardOpenOption.CREATE);
JEP 213: Milling Project Coin — Small Language Improvements (Java 9)
Migration Pattern: Apply small but useful language enhancements
public interface Validator {
default boolean validateName(String name) {
return isNonEmpty(name) && name.length() <= 100;
}
default boolean validateEmail(String email) {
return isNonEmpty(email) && email.contains("@");
}
private boolean isNonEmpty(String value) {
return value != null && !value.isBlank();
}
}
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (BufferedReader r = reader) {
return r.readLine();
}
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
return reader.readLine();
}
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
};
Comparator<String> comp = new Comparator<>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
};
@SafeVarargs
private <T> List<T> asList(T... elements) {
return List.of(elements);
}
JEP 102: Process API Updates (Java 9)
Migration Pattern: Use the enhanced Process API
Process process = Runtime.getRuntime().exec("mycommand");
int exitCode = process.waitFor();
ProcessHandle current = ProcessHandle.current();
System.out.println("PID: " + current.pid());
System.out.println("Command: " + current.info().command().orElse("unknown"));
System.out.println("Start time: " + current.info().startInstant().orElse(null));
System.out.println("CPU duration: " + current.info().totalCpuDuration().orElse(null));
ProcessHandle.allProcesses()
.filter(ProcessHandle::isAlive)
.forEach(ph -> System.out.printf("PID %d: %s%n",
ph.pid(), ph.info().command().orElse("unknown")));
ProcessHandle.current().onExit()
.thenAccept(ph -> System.out.println("Process exited: " + ph.pid()));
process.toHandle().descendants()
.forEach(ProcessHandle::destroy);
JEP 266: Reactive Streams — Flow API (Java 9)
Migration Pattern: Use built-in reactive streams interfaces
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
publisher.subscribe(new Flow.Subscriber<>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(String item) {
System.out.println("Received: " + item);
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("Done");
}
});
publisher.submit("Hello");
publisher.submit("World");
publisher.close();
JEP 259: Stack-Walking API (Java 9)
Migration Pattern: Replace Thread.getStackTrace() with StackWalker
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
String callerClass = stack[2].getClassName();
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
Class<?> callerClass = walker.getCallerClass();
walker.walk(frames -> frames
.filter(f -> f.getClassName().startsWith("com.example"))
.map(StackWalker.StackFrame::getMethodName)
.collect(Collectors.toList()));
I/O and Networking Improvements
JEP 238: Multi-Release JAR Files (Java 9)
Migration Pattern: Support multiple Java versions in a single JAR
META-INF/
MANIFEST.MF # Must include: Multi-Release: true
versions/
9/
com/example/Helper.class # Java 9+ version
11/
com/example/Helper.class # Java 11+ version
com/
example/
Helper.class # Base version (Java 8)
Main.class
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<executions>
<execution>
<id>compile-java-11</id>
<phase>compile</phase>
<goals><goal>compile</goal></goals>
<configuration>
<release>11</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java11</compileSourceRoot>
</compileSourceRoots>
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/11</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Build System Configuration
Maven Configuration
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
If code relies on internal APIs during migration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>11</release>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.base/sun.nio.ch=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
Gradle Configuration
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
tasks.withType<JavaCompile> {
options.release.set(11)
}
tasks.withType<Test> {
useJUnitPlatform()
jvmArgs(
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED"
)
}
JVM and Performance Improvements
JEP 328: Flight Recorder (Java 11)
Migration Pattern: Use JFR for production profiling (was commercial in JDK 8)
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr -jar myapp.jar
java -XX:StartFlightRecording=disk=true,maxsize=500m,maxage=1d -jar myapp.jar
import jdk.jfr.*;
@Label("HTTP Request")
@Category({"Application", "HTTP"})
public class HttpRequestEvent extends Event {
@Label("URL")
public String url;
@Label("Status Code")
public int statusCode;
@Label("Duration (ms)")
@Timespan(Timespan.MILLISECONDS)
public long duration;
}
HttpRequestEvent event = new HttpRequestEvent();
event.begin();
event.url = "https://api.example.com/data";
event.statusCode = 200;
event.end();
event.commit();
JEP 310: Application Class-Data Sharing (Java 10)
Migration Pattern: Improved startup time for applications
java -Xshare:dump -XX:SharedArchiveFile=app-cds.jsa \
-XX:SharedClassListFile=classlist.txt -cp myapp.jar
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -cp myapp.jar com.example.Main
JEP 307: Parallel Full GC for G1 (Java 10)
Migration Pattern: G1 now uses parallel threads for full GC
-XX:+UseG1GC
-XX:ParallelGCThreads=4
JEP 332: Transport Layer Security (TLS) 1.3 (Java 11)
Migration Pattern: TLS 1.3 is available by default
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
Deprecations and Removals
Removed: JavaFX
Migration Pattern: Use OpenJFX as a separate dependency
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>11.0.2</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>11.0.2</version>
</dependency>
JEP 335: Deprecate the Nashorn JavaScript Engine (Java 11)
Migration Guidance: Nashorn is deprecated in 11, removed in 15
Removed: Java Web Start and Applets
Migration Pattern: Convert to standalone applications or use alternatives
JEP 330: Launch Single-File Source-Code Programs (Java 11)
Usage: Run .java files directly without explicit compilation
javac HelloWorld.java
java HelloWorld
java HelloWorld.java
public class Script {
public static void main(String[] args) {
System.out.println("Hello from Java script!");
}
}
Version Numbering Change
Starting with Java 9, the version scheme changed:
java -version
java -version
// Old
String version = System.getProperty("java.version"); // "1.8.0_292"
// New: Use Runtime.Version (Java 9+)
Runtime.Version version = Runtime.version();
int major = version.feature(); // 11
int minor = version.interim(); // 0
int patch = version.update(); // 21
Testing and Migration Strategy
Phase 1: Compilation and Dependencies (Weeks 1-2)
-
Update build system
- Set source/target/release to 11
- Update Maven/Gradle plugins
- Update CI/CD pipelines
-
Fix removed module dependencies
- Add JAXB, JAX-WS, JTA dependencies
- Add Common Annotations if using @PostConstruct/@PreDestroy
- Add JavaFX dependencies if needed
-
Address internal API usage
- Replace sun.misc.BASE64Encoder/Decoder with java.util.Base64
- Replace sun.misc.Unsafe with VarHandle or supported APIs
- Add --add-opens/--add-exports flags for remaining issues
Phase 2: Library and Framework Updates (Weeks 3-4)
-
Update dependencies
- Spring Boot 2.1+ for Java 11 support
- Hibernate 5.4+ for Java 11 support
- Jackson, Lombok, and annotation processors
- Testing frameworks (JUnit 5, Mockito 3+)
-
Fix reflection-based frameworks
- Add --add-opens for frameworks using deep reflection
- Update ORM configurations if needed
Phase 3: Adopt New Features (Weeks 5-6)
-
Language features
- Use
var for local variables where it improves readability
- Replace collection initialization with factory methods
- Adopt new Stream and Optional methods
-
API migration
- Replace HttpURLConnection with HTTP Client
- Use new String methods (isBlank, lines, strip, repeat)
- Use Files.readString/writeString
Phase 4: Runtime Optimization (Weeks 7-8)
-
JVM tuning
- Review GC settings (G1 is now default)
- Enable Flight Recorder for profiling
- Configure Application CDS for faster startup
-
Testing and validation
- Run full test suite
- Performance benchmarking
- Verify production deployment
Best Practices
-
Use var Judiciously
- Use when the type is obvious from context
- Avoid when it reduces readability
- Never use for method return types or fields (not allowed)
-
Prefer Immutable Collections
- Use
List.of(), Set.of(), Map.of() for constants
- Use
List.copyOf() for defensive copies
- Be aware these do not permit null elements
-
Replace HttpURLConnection
- The new HTTP Client supports HTTP/2, async, and WebSocket
- Use it for all new HTTP code; migrate existing code gradually
-
Embrace the New String Methods
isBlank() over trim().isEmpty()
strip() over trim() (Unicode-aware)
lines() over split("\\R")
repeat() over manual loops
-
Use Flight Recorder in Production
- JFR is now free and open source (was commercial in JDK 8)
- Low overhead — safe for production use
- Create custom events for application-level monitoring
This comprehensive guide enables GitHub Copilot to provide contextually appropriate suggestions when upgrading Java 8 projects to Java 11, focusing on module system compatibility, removed Java EE modules, new language features, and modern API patterns.