Modern Java Features
Java records, pattern matching, sealed classes vs C# equivalents
Introduction
In this lesson, you'll learn about modern java features in Java. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C#, you're familiar with java records, pattern matching, sealed classes vs c# equivalents.
Java has its own approach to java records, pattern matching, sealed classes vs c# equivalents, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
// Java record — same as C# record
public record Point(double x, double y) {}
var p = new Point(1, 2);
// No 'with' — use copy constructor or builder pattern
// Sealed interface + records
public sealed interface Shape
permits Circle, Rect {}
public record Circle(double radius) implements Shape {}
public record Rect(double w, double h) implements Shape {}
// Pattern matching switch (Java 21)
String describe(Shape s) {
return switch (s) {
case Circle c when c.radius() > 10 -> "large circle r=" + c.radius();
case Circle c -> "circle r=" + c.radius();
case Rect r -> "rect " + r.w() + "x" + r.h();
};
}
// instanceof pattern binding (Java 16+)
if (s instanceof Circle c) {
System.out.println("radius: " + c.radius());
}
// var — local type inference (Java 10+)
var list = new ArrayList<String>();
// Text blocks (Java 15+)
var json = """
{"name": "Alice"}
""";Comparing to C#
Here's how you might have written similar code in C#:
// C# record — immutable with value equality
public record Point(double X, double Y);
var p = new Point(1, 2);
var p2 = p with { X = 5 }; // non-destructive mutation
// switch expression
string Describe(Shape s) => s switch {
Circle { Radius: > 10 } c => $"large circle r={c.Radius}",
Circle c => $"circle r={c.Radius}",
_ => "other"
};
// Sealed hierarchy
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rect(double W, double H) : Shape;
// Primary constructors (C# 12)
public class Config(string Host, int Port)
{
public string Host { get; } = Host;
public int Port { get; } = Port;
}
// Required properties (C# 11)
public class UserDto
{
public required string Name { get; init; }
public required string Email { get; init; }
}You may be used to different syntax or behavior.
Java records use record (lower-case); C# uses record keyword too — similar syntax
You may be used to different syntax or behavior.
Java has no 'with' expression — must create a new record manually
You may be used to different syntax or behavior.
Java pattern matching uses 'when' guard (C# uses 'when' too)
You may be used to different syntax or behavior.
Java sealed uses 'permits' to list allowed subclasses; C# uses no special syntax
You may be used to different syntax or behavior.
Java switch expression uses -> (like C#); each arm doesn't need break
Step-by-Step Breakdown
1. Records
Both C# and Java have records for immutable data. Java getters use the field name directly (c.radius()), not C#'s property syntax (c.Radius).
public record Point(double X, double Y);
var p = new Point(1, 2);
p.X; // C# propertyrecord Point(double x, double y) {}
var p = new Point(1, 2);
p.x(); // Java accessor — note parentheses!2. Sealed Hierarchy
Java uses 'sealed interface permits ...' to list all allowed implementors. C# uses 'sealed' on the subclasses but doesn't enumerate them.
public abstract record Shape;
public record Circle(double Radius) : Shape;sealed interface Shape permits Circle, Rect {}
record Circle(double radius) implements Shape {}
record Rect(double w, double h) implements Shape {}3. Pattern Matching Switch
Java 21 switch expressions support type patterns and guards with 'when'. Sealed types make the switch exhaustive.
s switch {
Circle { Radius: > 10 } c => $"large {c.Radius}",
_ => "other"
}switch (s) {
case Circle c when c.radius() > 10 -> "large " + c.radius();
case Circle c -> "circle " + c.radius();
case Rect r -> "rect";
}4. var and Text Blocks
Java 10+ var for local type inference. Java 15+ text blocks use triple-quote strings — identical purpose to C# verbatim/raw strings.
var list = new List<string>();
var json = """
{"name": "Alice"}
""";var list = new ArrayList<String>(); // Java 10+
var json = """
{"name": "Alice"}
"""; // Java 15+ text blockCommon Mistakes
When coming from C#, developers often make these mistakes:
- Java records use record (lower-case); C# uses record keyword too — similar syntax
- Java has no 'with' expression — must create a new record manually
- Java pattern matching uses 'when' guard (C# uses 'when' too)
Key Takeaways
- Java records similar to C# records; Java getters use method syntax: p.x() not p.X
- sealed interface permits A, B, C makes switch exhaustive — Java's version of discriminated unions
- Java pattern switch uses 'when' for guards (C# uses 'when' too)
- var (Java 10+) for type inference; text blocks (Java 15+) for multi-line strings