| name | java-11-to-17 |
| description | Upgrade Java projects from JDK 11 to JDK 17 covering Records, Sealed Classes, Pattern Matching for instanceof, Switch Expressions, Text Blocks |
GitHub Copilot Instructions: Java 11 to Java 17 Upgrade Guide
Project Context
This guide provides comprehensive GitHub Copilot instructions for upgrading Java projects from JDK 11 to JDK 17, covering major language features, API changes, and migration patterns based on 47 JEPs integrated between these versions.
Language Features and API Changes
JEP 395: Records (Java 16)
Migration Pattern: Convert data classes to records
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String name() { return name; }
public int age() { return age; }
@Override
public boolean equals(Object obj) { }
@Override
public int hashCode() { }
@Override
public String toString() { }
}
public record Person(String name, int age) {
public Person {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
}
public boolean isAdult() {
return age >= 18;
}
}
JEP 409: Sealed Classes (Java 17)
Migration Pattern: Use sealed classes for restricted inheritance
public sealed class Shape
permits Circle, Rectangle, Triangle {
public abstract double area();
}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
public non-sealed class Triangle extends Shape {
private final double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double area() {
return 0.5 * base * height;
}
}
JEP 394: Pattern Matching for instanceof (Java 16)
Migration Pattern: Simplify instanceof checks
public String processObject(Object obj) {
if (obj instanceof String) {
String str = (String) obj;
return str.toUpperCase();
} else if (obj instanceof Integer) {
Integer num = (Integer) obj;
return "Number: " + num;
} else if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
return "List with " + list.size() + " elements";
}
return "Unknown type";
}
public String processObject(Object obj) {
if (obj instanceof String str) {
return str.toUpperCase();
} else if (obj instanceof Integer num) {
return "Number: " + num;
} else if (obj instanceof List<?> list) {
return "List with " + list.size() + " elements";
}
return "Unknown type";
}
public String describeShape(Shape shape) {
if (shape instanceof Circle circle) {
return "Circle with radius " + circle.radius();
} else if (shape instanceof Rectangle rect) {
return "Rectangle " + rect.width() + "x" + rect.height();
} else if (shape instanceof Triangle triangle) {
return "Triangle with base " + triangle.base();
}
return "Unknown shape";
}
JEP 361: Switch Expressions (Java 14)
Migration Pattern: Convert switch statements to expressions
public String getDayType(DayOfWeek day) {
String result;
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
result = "Workday";
break;
case SATURDAY:
case SUNDAY:
result = "Weekend";
break;
default:
throw new IllegalArgumentException("Unknown day: " + day);
}
return result;
}
public String getDayType(DayOfWeek day) {
return switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Workday";
case SATURDAY, SUNDAY -> "Weekend";
};
}
public int calculateScore(Grade grade) {
return switch (grade) {
case A -> 100;
case B -> 85;
case C -> 70;
case D -> {
System.out.println("Consider improvement");
yield 55;
}
case F -> {
System.out.println("Needs retake");
yield 0;
}
};
}
JEP 378: Text Blocks (Java 15)
Migration Pattern: Use text blocks for multi-line strings
String html = "<html>\n" +
" <body>\n" +
" <h1>Hello World</h1>\n" +
" <p>Welcome to Java 17!</p>\n" +
" </body>\n" +
"</html>";
String sql = "SELECT p.id, p.name, p.email, " +
" a.street, a.city, a.state " +
"FROM person p " +
"JOIN address a ON p.address_id = a.id " +
"WHERE p.active = true " +
"ORDER BY p.name";
String html = """
<html>
<body>
<h1>Hello World</h1>
<p>Welcome to Java 17!</p>
</body>
</html>
""";
String sql = """
SELECT p.id, p.name, p.email,
a.street, a.city, a.state
FROM person p
JOIN address a ON p.address_id = a.id
WHERE p.active = true
ORDER BY p.name
""";
String json = """
{
"name": "%s",
"age": %d,
"city": "%s"
}
""".formatted(name, age, city);
JEP 358: Helpful NullPointerExceptions (Java 14)
Migration Guidance: Better NPE debugging (enabled by default in Java 17)
public class PersonProcessor {
public void processPersons(List<Person> persons) {
persons.stream()
.mapToInt(person -> person.getName().length())
.sum();
}
public void complexExample(Map<String, List<Person>> groups) {
int totalNameLength = groups.get("admins")
.get(0)
.getName()
.length();
}
}
JEP 371: Hidden Classes (Java 15)
Migration Pattern: Use for framework and proxy generation
public class DynamicProxyExample {
public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
return interfaceClass.cast(
Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
handler
)
);
}
}
JEP 334: JVM Constants API (Java 12)
Migration Pattern: Use for compile-time constants
import java.lang.constant.*;
public class ConstantExample {
public static final DynamicConstantDesc<String> COMPUTED_CONSTANT =
DynamicConstantDesc.of(
ConstantDescs.BSM_INVOKE,
"computeValue",
ConstantDescs.CD_String
);
public static String computeValue() {
return "Computed at runtime, cached as constant";
}
}
JEP 415: Context-Specific Deserialization Filters (Java 17)
Migration Pattern: Enhanced security for object deserialization
import java.io.*;
public class SecureDeserialization {
public static void setupSerializationFilters() {
ObjectInputFilter globalFilter = ObjectInputFilter.Config.createFilter(
"java.base/*;java.util.*;!*"
);
ObjectInputFilter.Config.setSerialFilter(globalFilter);
}
public <T> T deserializeSecurely(byte[] data, Class<T> expectedType) throws IOException, ClassNotFoundException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis)) {
ObjectInputFilter contextFilter = ObjectInputFilter.Config.createFilter(
expectedType.getName() + ";java.lang.*;!*"
);
ois.setObjectInputFilter(contextFilter);
return expectedType.cast(ois.readObject());
}
}
}
JEP 356: Enhanced Pseudo-Random Number Generators (Java 17)
Migration Pattern: Use new random generator interfaces
import java.util.random.*;
Random oldRandom = new Random();
int oldValue = oldRandom.nextInt(100);
RandomGenerator generator = RandomGeneratorFactory
.of("Xoshiro256PlusPlus")
.create(System.nanoTime());
RandomGenerator.SplittableGenerator splittableGenerator =
RandomGeneratorFactory.of("L64X128MixRandom").create();
splittableGenerator.splits(4)
.parallel()
.mapToInt(rng -> rng.nextInt(1000))
.forEach(System.out::println);
generator.ints(10, 1, 101)
.forEach(System.out::println);
I/O and Networking Improvements
JEP 380: Unix-Domain Socket Channels (Java 16)
Migration Pattern: Use Unix domain sockets for local IPC
import java.net.UnixDomainSocketAddress;
import java.nio.channels.*;
public class UnixSocketExample {
public void createUnixDomainServer() throws IOException {
Path socketPath = Path.of("/tmp/my-app.socket");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath);
try (ServerSocketChannel server = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
server.bind(address);
while (true) {
try (SocketChannel client = server.accept()) {
handleClient(client);
}
}
}
}
public void connectToUnixSocket() throws IOException {
Path socketPath = Path.of("/tmp/my-app.socket");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath);
try (SocketChannel client = SocketChannel.open(address)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
}
}
private void handleClient(SocketChannel client) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
}
}
JEP 352: Non-Volatile Mapped Byte Buffers (Java 14)
Migration Pattern: Use for persistent memory operations
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
public class PersistentMemoryExample {
public void usePersistentMemory() throws IOException {
Path nvmFile = Path.of("/mnt/pmem/data.bin");
try (FileChannel channel = FileChannel.open(nvmFile,
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, 1024,
ExtendedMapMode.READ_WRITE_SYNC
);
buffer.putLong(0, System.currentTimeMillis());
buffer.putInt(8, 12345);
buffer.force();
}
}
}
Build System Configuration
Maven Configuration
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</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>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
Gradle Configuration
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType<JavaCompile> {
options.release.set(17)
}
tasks.withType<Test> {
useJUnitPlatform()
}
Deprecations and Removals
JEP 411: Deprecate the Security Manager for Removal
Migration Pattern: Remove Security Manager dependencies
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
JEP 398: Deprecate the Applet API for Removal
Migration Pattern: Migrate from Applets to modern web technologies
public class MyApplet extends Applet {
@Override
public void start() {
}
}
public class MyApplication extends JFrame {
public MyApplication() {
setTitle("My Application");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new MyApplication().setVisible(true);
});
}
}
JEP 372: Remove the Nashorn JavaScript Engine
Migration Pattern: Use alternative JavaScript engines
ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js");
ProcessBuilder pb = new ProcessBuilder("node", "script.js");
Process process = pb.start();
JVM and Performance Improvements
JEP 377: ZGC - A Scalable Low-Latency Garbage Collector (Java 15)
Migration Pattern: Enable ZGC for low-latency applications
-XX:+UseZGC
-XX:+LogVMOutput
-XX:LogFile=gc.log
JEP 379: Shenandoah - A Low-Pause-Time Garbage Collector (Java 15)
Migration Pattern: Enable Shenandoah for consistent latency
-XX:+UseShenandoahGC
-XX:ShenandoahGCHeuristics=adaptive
JEP 341: Default CDS Archives (Java 12) & JEP 350: Dynamic CDS Archives (Java 13)
Migration Pattern: Improved startup performance
java -XX:DumpLoadedClassList=classes.lst -cp myapp.jar com.example.Main
java -Xshare:dump -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=myapp.jsa -cp myapp.jar
java -XX:SharedArchiveFile=myapp.jsa -cp myapp.jar com.example.Main
Testing and Migration Strategy
Phase 1: Foundation (Weeks 1-2)
-
Update build system
- Modify Maven/Gradle configuration for Java 17
- Update CI/CD pipelines
- Verify dependency compatibility
-
Address removals and deprecations
- Remove Nashorn JavaScript engine usage
- Replace deprecated Applet APIs
- Update Security Manager usage
Phase 2: Language Features (Weeks 3-4)
-
Implement Records
- Convert data classes to records
- Add validation in compact constructors
- Test serialization compatibility
-
Add Pattern Matching
- Convert instanceof chains
- Implement type-safe casting patterns
Phase 3: Advanced Features (Weeks 5-6)
-
Switch Expressions
- Convert switch statements to expressions
- Use new arrow syntax
- Implement complex yield logic
-
Text Blocks
- Replace concatenated multi-line strings
- Update SQL and HTML generation
- Use formatting methods
Phase 4: Sealed Classes (Weeks 7-8)
-
Design sealed hierarchies
- Identify inheritance restrictions
- Implement sealed class patterns
- Combine with pattern matching
-
Testing and validation
- Comprehensive test coverage
- Performance benchmarking
- Compatibility verification
Performance Considerations
Records vs Traditional Classes
- Records are more memory efficient
- Faster creation and equality checks
- Automatic serialization support
- Consider for data transfer objects
Pattern Matching Performance
- Eliminates redundant type checks
- Reduces casting overhead
- Better JVM optimization opportunities
- Use with sealed classes for exhaustiveness
Switch Expressions Optimization
- More efficient bytecode generation
- Better constant folding
- Improved branch prediction
- Use for complex conditional logic
Best Practices
-
Use Records for Data Classes
- Immutable data containers
- API data transfer objects
- Configuration objects
-
Apply Pattern Matching Strategically
- Replace instanceof chains
- Use with sealed classes
- Combine with switch expressions
-
Adopt Text Blocks for Multi-line Content
- SQL queries
- JSON templates
- HTML content
- Configuration files
-
Design with Sealed Classes
- Domain modeling
- State machines
- Algebraic data types
- API evolution control
-
Leverage Enhanced Random Generators
- Parallel processing scenarios
- High-quality random numbers
- Statistical applications
- Gaming and simulation
This comprehensive guide enables GitHub Copilot to provide contextually appropriate suggestions when upgrading Java 11 projects to Java 17, focusing on language enhancements, API improvements, and modern Java development practices.