Java 15 Features Explained: Sealed Classes, Records, Text Blocks, and More

~
~
Published on
Authors
java-15-banner

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-preview to 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.