Checkpoint: Modern Java
Reviewed & published by Brayan K
This checkpoint consolidates the modern Java unit — var, text blocks, switch expressions, pattern matching, sealed types, record patterns, method references, and functional interfaces — into one build challenge you complete end to end.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
How This Checkpoint Works
First skim the recap, then tackle the multi-step build challenge on your own. A full, compiled solution is hidden under "Show solution" — try before you peek. Finish with the written checkpoint quiz and the timed quiz to confirm it all stuck.
Unit Recap — Eight Features
- var — infer a local's type from its initializer
- Text blocks — multi-line """ string literals
- Switch expressions — a switch that returns a value
- Pattern matching — bind a typed variable in instanceof/switch
- Sealed classes — a closed, permits-listed hierarchy
- Record patterns — deconstruct records inline
- Method references — Type::method shorthand for lambdas
- Functional interfaces — Function, Predicate, Consumer, Supplier
Quick Recap in Code
A single program touching several of the features at once: var, a Predicate + method reference, a switch expression, a text block, and an instanceof pattern.
import java.util.List;
import java.util.function.Predicate;
public class Main {
// Recap in one place: var, text block, switch expression,
// pattern matching, functional interface, method reference.
public static void main(String[] args) {
var items = List.of("Java", "", "Streams", " ", "Records");
// Predicate + method reference (Predicate.not + String::isBlank).
Predicate<String> meaningful = Predicate.not(String::isBlank);
long kept = items.stream().filter(meaningful).count();
// switch expression returning a value.
String verdict = switch ((int) kept) {
case 0 -> "nothing";
case 1 -> "one item";
default -> kept + " items";
};
// text block summary.
String report = """
Filtered report
---------------
Kept: %s""".formatted(verdict);
System.out.println(report);
// pattern matching for instanceof on a mixed value.
Object value = 42;
if (value instanceof Integer i && i > 0) {
System.out.println("positive int: " + i);
}
}
}🏗️ Build Challenge — A Shape Report
Model a small sealed hierarchy of shape records, then process it with a pattern switch that deconstructs each record — wiring in var, a text block, a guard, method references, and streams. Follow the five steps in the starter, aim for the exact expected output, and only then open the solution.
import java.util.List;
public class Main {
// BUILD CHALLENGE — model shapes and process them with modern Java.
//
// STEP 1: a sealed interface Shape permits Circle, Rectangle, Triangle.
// STEP 2: each shape is a record:
// Circle(double radius), Rectangle(double width, double height),
// Triangle(double base, double height).
// STEP 3: write area(Shape) as an EXHAUSTIVE pattern switch that
// DECONSTRUCTS each record (no default):
// Circle(r) -> PI * r * r
// Rectangle(w, h) -> w * h
// Triangle(b, h) -> 0.5 * b * h
// STEP 4: write describe(Shape) returning a text like
// "Circle: area=12.57" using "...".formatted(...).
// Add a GUARD so a Rectangle with w == h is called "Square".
// STEP 5: in main, use 'var' + List.of for the shapes, print a text-block
// header, then a method-reference forEach over describe, and a
// stream sum of area for the total.
//
// Expected output:
// === Shape Report ===
// Circle: area=12.57
// Rectangle: area=12.00
// Square: area=25.00
// Triangle: area=6.00
// Total area = 55.57
public static void main(String[] args) {
// var shapes = List.of(new Circle(2), new Rectangle(3, 4),
// new Rectangle(5, 5), new Triangle(6, 2));
// ...
}
}✅ Full Solution
Attempt the challenge first. When you're ready, expand the compiled, runnable solution below.
import java.util.List;
public class Main {
// A sealed hierarchy of shapes, each a record (algebraic data type).
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
// Process the hierarchy with a pattern switch + record deconstruction.
// Exhaustive over the sealed type -> no default needed.
static String describe(Shape s) {
return switch (s) {
case Circle(double r) ->
"Circle: area=%.2f".formatted(Math.PI * r * r);
case Rectangle(double w, double h) when w == h ->
"Square: area=%.2f".formatted(w * h);
case Rectangle(double w, double h) ->
"Rectangle: area=%.2f".formatted(w * h);
case Triangle(double b, double h) ->
"Triangle: area=%.2f".formatted(0.5 * b * h);
};
}
// A second exhaustive switch reusing the record patterns.
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Rectangle(double w, double h) -> w * h;
case Triangle(double b, double h) -> 0.5 * b * h;
};
}
public static void main(String[] args) {
// 'var' + List.of, text block header, method-reference forEach.
var shapes = List.of(
new Circle(2),
new Rectangle(3, 4),
new Rectangle(5, 5),
new Triangle(6, 2)
);
String header = """
=== Shape Report ===
""";
System.out.print(header);
shapes.stream()
.map(Main::describe)
.forEach(System.out::println);
double total = shapes.stream()
.mapToDouble(Main::area)
.sum();
System.out.printf("Total area = %.2f%n", total);
}
}=== Shape Report ===
Circle: area=12.57
Rectangle: area=12.00
Square: area=25.00
Triangle: area=6.00
Total area = 55.57Notice the design: a sealed Shape of three records; describe and area are both exhaustive pattern switches with no default; the when w == h guard splits squares from rectangles; and main uses var, a text block, Main::describe as a method reference, and a stream sum.
📝 Checkpoint Quiz
Answer each in your head, then reveal.
Answer: Shape is sealed and permits exactly Circle, Rectangle, and Triangle. With an arm for each, the compiler proves the switch is exhaustive, so a default is unnecessary — and omitting it means adding a fourth shape would turn the switch into a compile error until you handle it.
Answer: It is a record pattern. It matches a Circle and immediately deconstructs it, binding the component to r — so you use r directly instead of calling c.radius().
Answer: A switch tries arms top to bottom and takes the first match. The plain case Rectangle(double w, double h) matches every rectangle, so the when w == h arm must precede it — otherwise the guarded arm would be unreachable, which is a compile error.
Answer: var shapes = List.of(...) is fine — a local with an initializer. var could not appear on the record components, the method parameters (Shape s), or the return types of area/describe, because those are part of the API contract.
Answer: Type::staticMethod — describe is a static method of Main. In .map(Main::describe) it stands in for s -> Main.describe(s).
Answer: "Circle: area=%.2f".formatted(value) is the instance form of String.format; %.2f inserts the area rounded to two decimals. There is no string interpolation in Java, so placeholders plus .formatted are the idiom.
Watch Out For
- ❌ Putting the plain Rectangle arm before the guarded one. Unreachable code. Fix: guard first.
- ❌ Adding a default to the sealed switch. Hides the safety net. Fix: rely on exhaustiveness.
- ❌ Using var on a record component or parameter. Illegal. Fix: declare the type.
- ❌ Wrong record-pattern arity. Each component needs a sub-pattern. Fix: Rectangle(double w, double h).
- ❌ Expecting ${area} interpolation in the text. None exists. Fix: %.2f + .formatted.
- ❌ Compiling on Java 20 or earlier. Record patterns need 21. Fix: use Java 21+.
Carry These Forward
- 💡 sealed + records + switch is the modern recipe for modelling and processing a closed set of cases safely.
- 💡 Let the compiler be your checklist — skip default on closed hierarchies so new cases surface as errors.
- 💡 Favour small functional pieces — predicates, mappers, and method references compose into readable pipelines.
- 💡 Reach for var and text blocks to cut boilerplate, but keep explicit types where they document intent.
📋 Quick Reference — Modern Java at a Glance
| Feature | Syntax | Since |
|---|---|---|
| var | var x = expr; | Java 10 |
| Text block | """\n...\n""" | Java 15 |
| Switch expression | var v = switch (k) {...}; | Java 14 |
| yield | case X -> { yield v; } | Java 14 |
| instanceof pattern | o instanceof String s | Java 16 |
| sealed / permits | sealed ... permits A, B | Java 17 |
| switch patterns | case Integer i -> ... | Java 21 |
| Guard | case X x when cond -> ... | Java 21 |
| Record pattern | case Point(int x, int y) | Java 21 |
| Method reference | Type::method, Type::new | Java 8 |
| Functional interface | Function, Predicate, ... | Java 8 |
| .formatted() | "%s".formatted(x) | Java 15 |
📚 Resources / Keep Learning
- 📘 Oracle: Java Language Changes — every feature, version by version.
- 📗 dev.java: Learn Java — the official, up-to-date tutorial hub.
- 📙 Oracle: Pattern Matching — instanceof, switch, and record patterns in depth.
🎉 Checkpoint Cleared!
You combined var, text blocks, switch expressions, pattern matching, sealed types, record patterns, method references, and functional interfaces into a single working program — the heart of modern, data-oriented Java.
Practice quiz
Where can var be used?
- Fields and parameters
- Return types
- Only local variables with an initializer
- Anywhere
Answer: Only local variables with an initializer. var is for local variables with an initializer — never fields, parameters, or return types.
How do you insert values into a text block?
- .formatted(...) or String.format
- ${var} interpolation
- + only
- You can't
Answer: .formatted(...) or String.format. Java has no interpolation; use %s/%d placeholders with .formatted(...).
What does a switch expression produce?
- Nothing
- Only side effects
- A boolean
- A value you can assign or return
Answer: A value you can assign or return. A switch expression evaluates to a value, using arrow arms and yield for blocks.
What does if (o instanceof String s) give you?
- A boolean only
- A test plus a ready-to-use String s
- A cast exception
- A new string
Answer: A test plus a ready-to-use String s. Pattern matching for instanceof tests the type and binds s, no explicit cast.
Why can a switch over a sealed type omit default?
- permits lists all subtypes, so the compiler proves exhaustiveness
- Switches never need default
- Sealed disables default
- It uses reflection
Answer: permits lists all subtypes, so the compiler proves exhaustiveness. Because the set of subtypes is closed and known, an arm per subtype is exhaustive.
What does case Point(int x, int y) do?
- Creates a Point
- Compares points
- Matches a Point and deconstructs it into x and y
- Calls a method
Answer: Matches a Point and deconstructs it into x and y. A record pattern matches and binds the components in one step.
Which is a constructor reference?
- Integer::parseInt
- ArrayList::new
- System.out::println
- String::length
Answer: ArrayList::new. Type::new is a constructor reference; the others are static, bound, and unbound instance references.
Which functional interface takes a T and returns a boolean?
- Function<T,R>
- Consumer<T>
- Supplier<T>
- Predicate<T>
Answer: Predicate<T>. Predicate<T>.test returns boolean.
f.andThen(g).apply(x) computes:
- f(g(x))
- g(f(x))
- f(x) and g(x)
- Nothing
Answer: g(f(x)). andThen runs f first, then feeds the result to g: g(f(x)).
Which combination enables data-oriented programming in modern Java?
- var + loops
- threads + locks
- sealed types + records + pattern matching
- reflection + generics
Answer: sealed types + records + pattern matching. Sealed types model a closed set, records hold the data, and patterns process them exhaustively.
Continue this course
- Previous: Functional Interfaces
- Next: Collectors & Grouping — groupingBy, partitioningBy, toMap and joining to reshape stream results
- Quick reference: Java cheat sheet
Frequently asked questions
What did this Modern Java unit cover?
It walked through the features that define Java from version 10 to 21: var for local-variable type inference; text blocks for multi-line strings; switch expressions that produce a value; pattern matching for instanceof and switch; sealed classes and interfaces for closed hierarchies; record patterns for deconstruction; method references as a compact lambda shorthand; and the built-in functional interfaces (Function, Predicate, Consumer, Supplier and friends). Together they enable a clear, data-oriented style.
How do sealed types, records, and pattern matching fit together?
They are three parts of one idea. A sealed interface declares a closed set of cases. Records define each case as immutable data with named components. A pattern switch then dispatches over those cases — deconstructing each record inline — and because the set is closed, the compiler proves the switch is exhaustive. The payoff is that adding a new case turns every incomplete switch into a compile error, so the compiler guides you to every place that must change.
When should I reach for var versus an explicit type?
Use var when the right-hand side already states the type clearly — new ArrayList<String>(), a constructor, or an obvious literal — so it just removes redundant noise. Prefer an explicit type when the right-hand side is an opaque method call whose result type a reader couldn't otherwise see. And remember var is locals-only: it never appears on fields, parameters, or return types, which still document your API.
Do these features require the latest Java?
They were finalized across several releases: var in Java 10, switch expressions in 14, text blocks in 15, instanceof patterns in 16, sealed types in 17, and switch patterns plus record patterns in 21. Method references and functional interfaces date back to Java 8. To use everything in this unit together — especially record patterns — compile and run on Java 21 or newer, which is the current long-term-support release.
What comes after this checkpoint?
The next lesson dives into Stream Collectors — the Collectors utility class that turns a stream into grouped maps, joined strings, partitions, and summary statistics. Collectors pair naturally with the functional interfaces and method references you just practised, so this checkpoint is the ideal springboard into deeper stream processing.