Sealed Classes (Preview)
Sealed Classes (JEP 360) is a feature that explicitly restricts which classes can extend a given class. It introduces an “allow list” to Java’s inheritance system.
Using an apartment building access control analogy: a public class is like a building anyone can enter, a final class is a building with no entry allowed at all, and a Sealed class is a building only registered residents can enter.
// === Sealed class: permitted subclasses explicitly listed ===
sealed interface Shape permits Circle, Rectangle, Triangle {}
// final: cannot be extended further
record Circle(double radius) implements Shape {
double area() {
return Math.PI * radius * radius;
}
}
// final: Record is implicitly final
record Rectangle(double width, double height) implements Shape {
double area() {
return width * height;
}
}
// non-sealed: reopened — anyone can extend Triangle
non-sealed class Triangle implements Shape {
private final double base;
private final double height;
Triangle(double base, double height) {
this.base = base;
this.height = height;
}
double area() {
return 0.5 * base * height;
}
}
public class SealedClassDemo {
// Advantage of Sealed classes: since all subtypes are known,
// the compiler can detect missing branches
static String describe(Shape shape) {
if (shape instanceof Circle c) {
return "Circle(radius=" + c.radius() + ", area=" +
String.format("%.2f", c.area()) + ")";
} else if (shape instanceof Rectangle r) {
return "Rectangle(" + r.width() + "x" + r.height() + ", area=" +
String.format("%.2f", r.area()) + ")";
} else if (shape instanceof Triangle t) {
return "Triangle(area=" + String.format("%.2f", t.area()) + ")";
}
// Since it's Sealed, no other Shape implementations exist
throw new AssertionError("Unknown shape");
}
public static void main(String[] args) {
Shape circle = new Circle(5.0);
Shape rect = new Rectangle(3.0, 4.0);
Shape tri = new Triangle(6.0, 3.0);
System.out.println(describe(circle));
System.out.println(describe(rect));
System.out.println(describe(tri));
// Output:
// Circle(radius=5.0, area=78.54)
// Rectangle(3.0x4.0, area=12.00)
// Triangle(area=9.00)
}
}
Subclasses of a Sealed class must choose one of three modifiers:
| Keyword | Meaning |
|---|---|
final | Completely prevents further inheritance |
sealed | Also specifies an allow list (extends the hierarchy) |
non-sealed | Reopens for inheritance (free inheritance like regular classes) |
Sealed Classes are finalized in Java 17 (LTS), and when combined with Pattern Matching for switch in Java 21, exhaustive switch (guaranteed coverage of all cases) becomes possible.
Text Blocks Finalized
After Java 13 (First Preview) and Java 14 (Second Preview), Text Blocks became a final feature in Java 15 (JEP 378). The --enable-preview flag is no longer needed.
Here’s a summary of the finalized Text Block features:
public class TextBlockFinal {
public static void main(String[] args) {
// === 1. Writing SQL queries ===
String sql = """
SELECT u.name, u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'COMPLETED'
AND o.total > 10000
ORDER BY o.total DESC
""";
System.out.println("SQL:");
System.out.println(sql);
// === 2. Combined with formatted() — dynamic value insertion ===
String name = "John Doe";
int minTotal = 50000;
String dynamicSql = """
SELECT * FROM orders
WHERE customer_name = '%s'
AND total >= %d
""".formatted(name, minTotal);
System.out.println("Dynamic SQL:");
System.out.println(dynamicSql);
// === 3. Escape sequences ===
// \s: preserve trailing whitespace (one space)
// \(end of line): prevent line break
String poem = """
Spring comes\s\s\
to mountains and fields\s\s\
flowers bloom.
""";
System.out.println("Joined as one line:");
System.out.println(poem);
// Output: Spring comes to mountains and fields flowers bloom.
// Output:
// SQL:
// SELECT u.name, u.email, o.total
// FROM users u
// JOIN orders o ON u.id = o.user_id
// WHERE o.status = 'COMPLETED'
// AND o.total > 10000
// ORDER BY o.total DESC
//
// Dynamic SQL:
// SELECT * FROM orders
// WHERE customer_name = 'John Doe'
// AND total >= 50000
//
// Joined as one line:
// Spring comes to mountains and fields flowers bloom.
}
}
Hidden Classes (JEP 371)
Hidden Classes is a feature for classes that frameworks and libraries generate at runtime. It’s rarely used directly in application code, but it’s an important change for the JVM ecosystem.
Key characteristics:
- Cannot be referenced by name from other classes (undiscoverable)
- Can be unloaded independently -> improved memory efficiency
- Created via
Lookup.defineHiddenClass()
They replace Unsafe.defineAnonymousClass() when frameworks dynamically generate classes for proxy patterns, Lambda metafactories, etc. Spring, Hibernate, and similar frameworks are transitioning internally.
ZGC Production Ready (JEP 377)
ZGC, which started experimentally in Java 11, has been promoted to production ready after 4 years. The -XX:+UnlockExperimentalVMOptions flag is no longer needed.
# Before Java 15: experimental flag required
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar
# Java 15 and later: use directly
java -XX:+UseZGC -jar app.jar
Here are ZGC’s production-ready performance numbers:
| Metric | G1 (default) | ZGC |
|---|---|---|
| Max GC pause time | Tens to hundreds of ms | 1-2ms |
| Average GC pause time | ~50ms | Under 0.5ms |
| Supported heap size | Tens of GB | Up to 16TB |
| Throughput overhead | Baseline | ~5-15% |
ZGC is suited for latency-sensitive services. It eliminates latency spikes caused by GC pauses in real-time trading systems, game servers, interactive web services, etc. For batch processing where throughput matters more, G1 may still be preferable.
Shenandoah GC Production Ready (JEP 379)
Shenandoah GC, led by Red Hat, was also simultaneously promoted to production ready. Like ZGC, it targets low latency but takes a different approach.
| Attribute | ZGC | Shenandoah |
|---|---|---|
| Origin | Oracle | Red Hat |
| Pause time | 1-2ms | Under 10ms |
| Colored pointers | Used | Not used (brooks pointers) |
| Max heap | 16TB | Several TB |
Since both GCs are production-ready, benchmarking with actual workloads before choosing is recommended.
# Enable Shenandoah
java -XX:+UseShenandoahGC -jar app.jar
# Compare performance with GC logs
java -XX:+UseZGC -Xlog:gc*:file=zgc.log -jar app.jar
java -XX:+UseShenandoahGC -Xlog:gc*:file=shenandoah.log -jar app.jar
Nashorn JavaScript Engine Removed (JEP 372)
The Nashorn JavaScript engine, introduced in Java 8, was removed. Java 11 issued deprecation warnings, and Java 15 completely removed it.
The alternative is GraalVM’s JavaScript engine. GraalVM is a runtime supporting Java as well as JavaScript, Python, Ruby, and other languages, with better performance than Nashorn.
If you have existing code using Nashorn via ScriptEngine, migration to GraalJS is required.
Summary
Here’s a comprehensive view of preview feature progression from Java 12 to 15:
| Feature | JDK 12 | JDK 13 | JDK 14 | JDK 15 | Final |
|---|---|---|---|---|---|
| Switch Expressions | Preview | Improved (yield) | Final | - | JDK 14 |
| Text Blocks | - | Preview | 2nd Preview | Final | JDK 15 |
| Records | - | - | Preview | 2nd Preview | JDK 16 |
| Pattern Matching (instanceof) | - | - | Preview | 2nd Preview | JDK 16 |
| Sealed Classes | - | - | - | Preview | JDK 17 |
JDK 12-15 mark the transitional period where Java’s new development process of “preview -> feedback integration -> finalization” became established. Thanks to the 6-month release cycle and preview feature system, features refined through community feedback settled in gradually with high quality.
If you’re using Java 11 LTS in production, these features can all be enjoyed at once when upgrading to Java 17 LTS. Records, Pattern Matching, Sealed Classes, and Text Blocks — these four alone will noticeably reduce code volume.