with one click
java-8-to-11
// 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
// 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
Assess a Java project's modernization posture — Java version, framework versions, dependency health, vulnerabilities, and migration opportunities
Deploy Java applications to Azure services including App Service, Container Apps, AKS, and Spring Apps
Upgrade Java projects from JDK 11 to JDK 17 covering Records, Sealed Classes, Pattern Matching for instanceof, Switch Expressions, Text Blocks
Upgrade Java projects from JDK 17 to JDK 21 covering Virtual Threads, Pattern Matching for switch, Record Patterns, Sequenced Collections
Upgrade Java projects from JDK 21 to JDK 25 covering Flexible Constructor Bodies, Stream Gatherers, Class-File API, Scoped Values, Security Manager removal
Migrate Java application services and dependencies to Azure equivalents using predefined migration tasks
| 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 |
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.
Migration Pattern: Add explicit dependencies for removed modules
// These packages were bundled in JDK 8 but REMOVED in JDK 11:
// - java.xml.ws (JAX-WS + SAAJ + Web Services Metadata)
// - java.xml.bind (JAXB)
// - java.activation (JAF)
// - java.xml.ws.annotation (Common Annotations)
// - java.corba (CORBA)
// - java.transaction (JTA)
Maven dependencies to add:
<!-- JAXB (XML binding) -->
<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>
<!-- JAX-WS (SOAP web services) -->
<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>
<!-- Java Activation Framework (JAF) -->
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Common Annotations (@PostConstruct, @PreDestroy, @Resource) -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
</dependency>
<!-- JTA (Java Transaction API) -->
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>1.3.3</version>
</dependency>
Gradle dependencies to add:
// JAXB
implementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.3")
runtimeOnly("org.glassfish.jaxb:jaxb-runtime:2.3.9")
// JAX-WS
implementation("jakarta.xml.ws:jakarta.xml.ws-api:2.3.3")
runtimeOnly("com.sun.xml.ws:jaxws-rt:2.3.7")
// Java Activation Framework
implementation("jakarta.activation:jakarta.activation-api:1.2.2")
// Common Annotations
implementation("jakarta.annotation:jakarta.annotation-api:1.3.5")
// JTA
implementation("jakarta.transaction:jakarta.transaction-api:1.3.3")
Migration Pattern: Understand and address module encapsulation
// Internal APIs that are NO LONGER ACCESSIBLE by default in Java 9+:
// - sun.misc.Unsafe → use java.lang.invoke.VarHandle or java.util.concurrent.atomic
// - sun.misc.BASE64Encoder/Decoder → use java.util.Base64
// - sun.reflect.ReflectionFactory → use java.lang.invoke.MethodHandles
// - com.sun.* internal APIs → use supported public APIs
// Old: sun.misc.BASE64Encoder (Java 8)
// sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
// String encoded = encoder.encode(data);
// New: java.util.Base64 (Java 8+, required in Java 9+)
String encoded = Base64.getEncoder().encodeToString(data);
byte[] decoded = Base64.getDecoder().decode(encoded);
// Old: sun.misc.Unsafe for atomic operations
// Unsafe unsafe = Unsafe.getUnsafe();
// New: VarHandle (Java 9+)
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:
# Allow access to internal APIs during migration (temporary workaround)
--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
# Common flags needed for frameworks (Hibernate, Spring, etc.)
--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
// You do NOT need module-info.java to run on Java 11.
// Without it, your code runs on the "unnamed module" with full classpath access.
// Only add module-info.java if you want explicit module boundaries.
module com.example.myapp {
requires java.sql;
requires java.logging;
requires java.net.http; // HTTP Client (Java 11)
exports com.example.myapp.api;
opens com.example.myapp.model to com.fasterxml.jackson.databind;
}
Migration Pattern: Use var for local variable declarations
// Old: Explicit type 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());
// New: Local variable type inference with var (Java 10+)
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());
// Best practices for var:
// DO use var when the type is obvious from the right-hand side
var count = 0; // int
var names = List.of("Alice", "Bob"); // List<String>
var stream = names.stream(); // Stream<String>
var path = Path.of("/tmp/data.txt"); // Path
var connection = DriverManager.getConnection(url); // Connection
// DON'T use var when it reduces readability
// Bad: What type is this?
// var result = service.process(data);
// Good: Explicit type makes intent clear
ProcessingResult result = service.process(data);
Migration Pattern: Use var in lambda expressions for annotations
// Old: No type annotations possible on lambda parameters without explicit types
list.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// New: var allows annotations on lambda parameters (Java 11+)
list.stream()
.map((@NotNull var s) -> s.toUpperCase())
.collect(Collectors.toList());
// Consistent use of var in multi-parameter lambdas
BiFunction<String, String, String> concat = (var a, var b) -> a + b;
Migration Pattern: Replace verbose collection initialization
// Old: Verbose collection creation (Java 8)
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);
}}
);
// New: Factory methods (Java 9+) — returns immutable collections
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
);
// For more than 10 entries, use Map.ofEntries
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("one", 1),
Map.entry("two", 2),
Map.entry("three", 3)
// ... more entries
);
// Java 10+: Create unmodifiable copies from existing collections
List<String> copy = List.copyOf(existingList);
Set<String> setCopy = Set.copyOf(existingSet);
Map<String, Integer> mapCopy = Map.copyOf(existingMap);
// Java 10+: Collectors for unmodifiable collections
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.
Migration Pattern: Use new stream operations
// takeWhile: Take elements while predicate is true (Java 9)
// Old
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
if (n >= 10) break;
result.add(n);
}
// New
List<Integer> result = numbers.stream()
.takeWhile(n -> n < 10)
.collect(Collectors.toList());
// dropWhile: Skip elements while predicate is true (Java 9)
List<Integer> afterTen = numbers.stream()
.dropWhile(n -> n < 10)
.collect(Collectors.toList());
// ofNullable: Create a stream from a nullable element (Java 9)
// Old
Stream<String> stream = value != null ? Stream.of(value) : Stream.empty();
// New
Stream<String> stream = Stream.ofNullable(value);
// iterate with predicate (Java 9) — replaces traditional for loops
// Old
for (int i = 0; i < 10; i++) { /* ... */ }
// New
IntStream.iterate(0, i -> i < 10, i -> i + 1)
.forEach(i -> { /* ... */ });
Migration Pattern: Use enhanced Optional methods
// ifPresentOrElse (Java 9)
// Old
if (optional.isPresent()) {
process(optional.get());
} else {
handleEmpty();
}
// New
optional.ifPresentOrElse(
this::process,
this::handleEmpty
);
// or: Provide alternative Optional (Java 9)
// Old
Optional<String> result = primary.isPresent() ? primary : fallback();
// New
Optional<String> result = primary.or(this::fallback);
// stream: Convert Optional to Stream (Java 9)
// Old
List<String> list = optionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
// New
List<String> list = optionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
// isEmpty (Java 11)
if (optional.isEmpty()) {
// handle absent value — clearer than !optional.isPresent()
}
Migration Pattern: Replace HttpURLConnection with the new HTTP Client
// Old: HttpURLConnection (verbose and error-prone)
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();
// New: HTTP Client (Java 11+)
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
// Synchronous GET
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();
// Asynchronous GET
CompletableFuture<HttpResponse<String>> future = client.sendAsync(
request, HttpResponse.BodyHandlers.ofString());
future.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
// POST with JSON body
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();
// Configurable client with timeouts and HTTP/2
HttpClient configuredClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
Migration Pattern: Use new String methods
// isBlank: Check if string is empty or contains only whitespace (Java 11)
// Old
boolean blank = str == null || str.trim().isEmpty();
// New
boolean blank = str.isBlank(); // handles whitespace-only strings
// lines: Split string into stream of lines (Java 11)
// Old
String[] lines = text.split("\\R");
// New
text.lines()
.filter(line -> !line.isBlank())
.map(String::strip)
.forEach(System.out::println);
// strip / stripLeading / stripTrailing: Unicode-aware trim (Java 11)
// Old: trim() only removes ASCII whitespace (<= U+0020)
String trimmed = str.trim();
// New: strip() handles all Unicode whitespace
String stripped = str.strip();
String leftStripped = str.stripLeading();
String rightStripped = str.stripTrailing();
// repeat: Repeat a string N times (Java 11)
// Old
String dashes = new String(new char[20]).replace('\0', '-');
// or
String dashes = String.join("", Collections.nCopies(20, "-"));
// New
String dashes = "-".repeat(20);
String indent = " ".repeat(4);
Migration Pattern: Simplified file read/write operations
// Old: Reading entire file to string
String content = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
// New: readString / writeString (Java 11)
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);
Migration Pattern: Apply small but useful language enhancements
// Private methods in interfaces (Java 9)
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 helper method — avoids duplicating logic across defaults
private boolean isNonEmpty(String value) {
return value != null && !value.isBlank();
}
}
// Try-with-resources on effectively final variables (Java 9)
// Old: Must declare new variable in try
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (BufferedReader r = reader) {
return r.readLine();
}
// New: Use effectively final variable directly
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
return reader.readLine();
}
// Diamond operator with anonymous inner classes (Java 9)
// Old: Required explicit type
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
};
// New: Diamond operator works with anonymous classes
Comparator<String> comp = new Comparator<>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
};
// @SafeVarargs on private methods (Java 9)
// Old: Only allowed on final or static methods
// New: Also allowed on private instance methods
@SafeVarargs
private <T> List<T> asList(T... elements) {
return List.of(elements);
}
Migration Pattern: Use the enhanced Process API
// Old: Limited process information
Process process = Runtime.getRuntime().exec("mycommand");
int exitCode = process.waitFor();
// New: ProcessHandle with rich info (Java 9+)
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));
// List all processes
ProcessHandle.allProcesses()
.filter(ProcessHandle::isAlive)
.forEach(ph -> System.out.printf("PID %d: %s%n",
ph.pid(), ph.info().command().orElse("unknown")));
// Get notified when a process exits
ProcessHandle.current().onExit()
.thenAccept(ph -> System.out.println("Process exited: " + ph.pid()));
// Destroy process trees
process.toHandle().descendants()
.forEach(ProcessHandle::destroy);
Migration Pattern: Use built-in reactive streams interfaces
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
// The Flow API provides standard interfaces:
// Flow.Publisher, Flow.Subscriber, Flow.Subscription, Flow.Processor
// Simple publisher/subscriber example
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();
Migration Pattern: Replace Thread.getStackTrace() with StackWalker
// Old: Captures entire stack trace (expensive)
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
String callerClass = stack[2].getClassName();
// New: StackWalker — lazy and efficient (Java 9+)
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
// Get caller class
Class<?> callerClass = walker.getCallerClass();
// Walk the stack lazily
walker.walk(frames -> frames
.filter(f -> f.getClassName().startsWith("com.example"))
.map(StackWalker.StackFrame::getMethodName)
.collect(Collectors.toList()));
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
<!-- Maven configuration for multi-release JARs -->
<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>
<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>
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
tasks.withType<JavaCompile> {
options.release.set(11)
}
tasks.withType<Test> {
useJUnitPlatform()
// Add if frameworks need internal API access
jvmArgs(
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED"
)
}
Migration Pattern: Use JFR for production profiling (was commercial in JDK 8)
# Start recording with the application
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr -jar myapp.jar
# Continuous recording with disk rotation
java -XX:StartFlightRecording=disk=true,maxsize=500m,maxage=1d -jar myapp.jar
// Programmatic JFR recording (Java 9+)
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;
}
// Usage
HttpRequestEvent event = new HttpRequestEvent();
event.begin();
// perform HTTP request
event.url = "https://api.example.com/data";
event.statusCode = 200;
event.end();
event.commit();
Migration Pattern: Improved startup time for applications
# Create shared archive from application classes
java -Xshare:dump -XX:SharedArchiveFile=app-cds.jsa \
-XX:SharedClassListFile=classlist.txt -cp myapp.jar
# Run with shared archive
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -cp myapp.jar com.example.Main
Migration Pattern: G1 now uses parallel threads for full GC
# G1 is default since Java 9 — full GC is now parallel by default in Java 10+
# No changes needed, but review GC flags:
-XX:+UseG1GC # default since Java 9
-XX:ParallelGCThreads=4 # tune for your hardware
Migration Pattern: TLS 1.3 is available by default
// TLS 1.3 is supported automatically. To enforce it:
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
// Or via system property
// -Djdk.tls.client.protocols=TLSv1.3
Migration Pattern: Use OpenJFX as a separate dependency
<!-- Add OpenJFX dependencies -->
<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>
Migration Guidance: Nashorn is deprecated in 11, removed in 15
// Old: Nashorn (still works in 11, but deprecated)
// ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Alternatives:
// 1. GraalVM JavaScript engine
// 2. External Node.js process
// 3. Mozilla Rhino
Migration Pattern: Convert to standalone applications or use alternatives
# Java Web Start (javaws) is removed in Java 11.
# Alternatives:
# - jlink to create self-contained application images
# - jpackage (Java 14+) for native installers
# - Install4j, Launch4j, or other packaging tools
Usage: Run .java files directly without explicit compilation
# Old: Compile then run
javac HelloWorld.java
java HelloWorld
# New: Run directly (Java 11+)
java HelloWorld.java
# With shebang for scripts (Unix)
#!/usr/bin/java --source 11
public class Script {
public static void main(String[] args) {
System.out.println("Hello from Java script!");
}
}
Starting with Java 9, the version scheme changed:
# Old (Java 8): 1.8.0_292
java -version
# java version "1.8.0_292"
# New (Java 11): 11.0.x
java -version
# java version "11.0.21"
# In code, update version checks:
// 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
Update build system
Fix removed module dependencies
Address internal API usage
Update dependencies
Fix reflection-based frameworks
Language features
var for local variables where it improves readabilityAPI migration
JVM tuning
Testing and validation
Use var Judiciously
Prefer Immutable Collections
List.of(), Set.of(), Map.of() for constantsList.copyOf() for defensive copiesReplace HttpURLConnection
Embrace the New String Methods
isBlank() over trim().isEmpty()strip() over trim() (Unicode-aware)lines() over split("\\R")repeat() over manual loopsUse Flight Recorder in Production
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.