Java 15 Features Explained: Sealed Classes, Records, Text Blocks, and More
- Published on
- Authors
- Name
- Spaghetti Code Jungle
- @spagcodejungle

Java 15 Features Explained: Sealed Classes, Records, Text Blocks, and More
Java 15 (September 2020) isn’t an LTS release, but it’s a milestone for language clarity and JVM performance. Think of it as a feature showcase: try new capabilities early, give feedback, and help shape what becomes stable in future LTS versions.
Why Java 15 Matters
- Faster evolution: Regular, smaller releases keep Java modern without big-bang upgrades.
- Preview & incubator features: Safely test-drive language/JDK changes before they lock in.
- Real-world feedback loop: Your experiments today help refine tomorrow’s LTS.
Heads up: Preview/incubator features require flags at compile/run time. You’ll see them noted below.
Getting Started (Quick Setup)
# Check version
java -version
# Compile with preview features
javac --enable-preview --release 15 MyDemo.java
# Run with preview features
java --enable-preview MyDemo
Use a JDK 15 distribution (Oracle, Temurin/Adoptium, etc.).
1) Sealed Classes (Preview)
What it is: A way to control inheritance. A sealed class or interface explicitly lists the types that are allowed to extend/implement it.
Why it’s good:
- Tightens your API contracts
- Enables exhaustive reasoning in
switch/pattern matching (when paired with future features) - Improves security and maintainability of hierarchies
Example:
// Compile/run with --enable-preview on JDK 15
public sealed interface Shape permits Circle, Rectangle, Triangle {}
final class Circle implements Shape {
final double radius;
Circle(double r) { this.radius = r; }
}
final class Rectangle implements Shape {
final double w, h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
}
non-sealed class Triangle implements Shape {
final double a, b, c;
Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
}
sealed says “only these types.” final means “no further subtypes.” non-sealed reopens the hierarchy below that type.
Use it when: You own an API and want explicit, compile-time control over extension points.
2) Hidden Classes (JEP 371)
What it is: Classes that can’t be used directly by bytecode but can be loaded and linked by frameworks at runtime.
Why it’s good:
- Great for bytecode-generation libraries (e.g., proxies, lambdas)
- Reduces classloader pollution and improves performance + security
Mental model: They’re like internal, throwaway implementation classes for frameworks—invisible to normal user code.
3) Text Blocks (Standardized 🎉)
What it is: Multi-line string literals using triple quotes (""" ... """).
Why it’s good:
- No more
"\n"chaos or+concatenation - Perfect for JSON, SQL, HTML
- Preserves readable indentation
Example (Before):
String json = "{\n" +
" \"name\": \"Java\",\n" +
" \"version\": 15\n" +
"}";
Example (Java 15 Text Block):
String json = """
{
"name": "Java",
"version": 15
}
""";
Pro tips:
Manage indentation visually; Java trims common leading whitespace.
Use
.formatted(...)for inline formatting:String user = "Ada"; String msg = """ { "hello": "%s" } """.formatted(user);
4) Records (Second Preview)
What it is: A compact syntax for immutable data carriers. Java generates constructor, accessors, equals, hashCode, and toString.
Why it’s good:
- Radically less boilerplate
- Convey intent: “this is just data”
Example:
// Compile/run with --enable-preview on JDK 15
public record User(String name, int level) {}
class Demo {
public static void main(String[] args) {
var u = new User("Lin", 42);
System.out.println(u.name()); // accessor
System.out.println(u); // User[name=Lin, level=42]
}
}
Customization: You can still add validation, methods, or a compact constructor.
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) throw new IllegalArgumentException("non-negative only");
}
public int manhattan() { return Math.abs(x) + Math.abs(y); }
}
5) ZGC & Shenandoah Improvements
What it is: Enhancements to two low-pause-time GCs aimed at large heaps and latency-sensitive services.
Why it’s good:
- Predictable pauses measured in milliseconds
- Better throughput and responsiveness for big services
Try it:
# Enable ZGC
java -XX:+UseZGC MyServer
# Enable Shenandoah (on distributions that ship it)
java -XX:+UseShenandoahGC MyServer
Pick based on workload, measure with real traffic, and compare GC logs.
6) Foreign-Memory Access API (Second Incubator)
What it is: An API for safe, efficient access to memory outside the Java heap.
Why it’s good:
- Interop with native data structures
- Potential performance wins vs.
ByteBuffer/JNI
Conceptual taste (incubator style):
// Pseudocode-flavored sketch to illustrate usage style at the time
try (var scope = MemorySession.openConfined()) {
MemorySegment seg = MemorySegment.allocateNative(1024, scope);
// ... put/get primitives using VarHandle-like accessors ...
}
Use cases: Native libraries, file formats, zero-copy I/O.
Why You Should Care (Even If It’s Not LTS)
- Future-proof skills: When these features hit LTS, you’ll already be fluent.
- Influence the roadmap: Preview/incubator feedback shapes the final design.
- Immediate wins: Text Blocks and GC upgrades are ready to improve your day-to-day now.
Migration & Tooling Tips
- Use multi-module test projects to isolate preview features.
- Add CI jobs that compile with
--enable-previewto catch drift early. - For libraries, gate preview usage behind profiles so consumers without JDK 15 aren’t blocked.
Conclusion
Java 15 sets a clear direction: cleaner syntax (Text Blocks, Records), safer architecture (Sealed Classes), and faster runtimes (ZGC/Shenandoah, Foreign Memory). Even without LTS status, it’s a practical release to learn and experiment with—today’s experiments become tomorrow’s best practices.