Java Module 6 – Inheritance, interfaces and polymorphism
Sealed classes and interfaces
Close a Java type hierarchy with sealed, permits, final and non-sealed, model payment methods as records, and let javac check every switch over them.
What you will learn
- Limit a class hierarchy with sealed, permits, final and non-sealed
- Model a closed domain such as payment methods with a sealed interface and records
- Explain how sealing lets javac check that a switch handles every case
- Read the compile errors that a new permitted type causes, and fix each one
Before you start
What you’ll learn · Before you start
What you will learn
- Limit a class hierarchy with sealed, permits, final and non-sealed
- Model a closed domain such as payment methods with a sealed interface and records
- Explain how sealing lets javac check that a switch handles every case
- Read the compile errors that a new permitted type causes, and fix each one
Before you start
On this page
A sealed class or interface names every class that may extend or implement it, in a permits clause, and both the
compiler and the JVM refuse any other. Each permitted subclass then says how the hierarchy goes on: final stops it,
sealed gives that branch a list of its own, and non-sealed opens the branch to anyone. Seal a type when it stands
for a fixed set of alternatives, such as the ways a customer can pay. Because the compiler then knows every
alternative, a switch that handles each one needs no default branch, and when someone adds an alternative, javac
lists every switch that has to decide what to do with it. Sealed classes and interfaces are part of Java since Java 17
(JEP 409), and switches over them with type patterns since Java 21.
This lesson models a checkout’s payment methods as a sealed interface of records, breaks each sealing rule once to read what javac says, adds a fifth payment method to see which code the compiler stops and which code quietly charges the wrong fee, and compares sealed types with enums. Each session was recorded with Eclipse Temurin 25.0.4.1 (Java 25), and one file also runs on Java 17 to show what that release lacks. The fees are made up for the examples.
A closed set of payment methods
Payment permits four types. Three are records, which are implicitly final, so they qualify as permitted subtypes
without another word. The fourth, Wallet, is an abstract class declared non-sealed: the checkout accepts wallets
from partners, and each partner writes its own subclass, such as QuickPay, in its own code. The set of direct
subtypes of Payment is closed; one branch below it stays open.
The sealed Payment hierarchy
Text description of the diagram
The diagram is one large box, "sealed interface Payment, permits exactly these four", with four boxes inside it, one under the other.
- "record Card", marked final: no class can extend it.
- "record Upi", also final.
- "record CashOnDelivery", also final.
- "non-sealed class Wallet", marked as open again: anyone may extend it. Inside it are two smaller boxes, "QuickPay" and "any other partner's wallet", the subclasses that partners write in their own code.
So the set of direct subtypes of Payment is closed and known to the compiler, while one branch of the hierarchy, Wallet, stays open for extension.
India-specific
UPI (Unified Payments Interface) is India’s system for instant transfers between bank accounts. A customer is
identified by a virtual payment address such as asha@okbank, which is all that the Upi record holds.
checkout/Payment.java
/**
* How a customer pays at checkout. Payment is sealed: the four types it permits are the only direct subtypes there
* will ever be, so javac knows all of them.
*/
public sealed interface Payment permits Card, Upi, CashOnDelivery, Wallet {}
/** A record is implicitly final: nothing can extend it. */
record Card(String network, String last4) implements Payment {}
/** A UPI payment, identified by the customer's virtual payment address. */
record Upi(String vpa) implements Payment {}
record CashOnDelivery() implements Payment {}
/** Partner wallets: non-sealed opens this one branch again, so partners extend it in their own code. */
abstract non-sealed class Wallet implements Payment {
abstract String provider();
abstract long feePaise(long amountPaise);
} checkout/QuickPay.java
/** A partner's wallet, written outside the checkout code: allowed, because Wallet is non-sealed. */
public class QuickPay extends Wallet {
@Override
String provider() {
return "QuickPay";
}
/** 1 %, rounded down to whole paise. */
@Override
long feePaise(long amountPaise) {
return amountPaise / 100;
}
} checkout/Fees.java
/** Fees and descriptions: one case per permitted type, and no default. */
public class Fees {
/** The checkout's fee for a payment, in paise. */
static long feePaise(Payment payment, long amountPaise) {
return switch (payment) {
case Card(String network, String last4) when network.equals("AMEX") -> amountPaise * 3 / 100;
case Card card -> amountPaise * 2 / 100;
case Upi upi -> 0;
case CashOnDelivery cash -> 4_000;
case Wallet wallet -> wallet.feePaise(amountPaise);
};
}
static String describe(Payment payment) {
return switch (payment) {
case Card(String network, String last4) -> network + " card ending " + last4;
case Upi(String vpa) -> "UPI to " + vpa;
case CashOnDelivery() -> "cash on delivery";
case Wallet wallet -> wallet.provider() + " wallet";
};
}
} checkout/Main.java
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
long amount = 125_000; // 1,250.00
List<Payment> payments = List.of(
new Card("VISA", "4242"), new Card("AMEX", "0005"), new Upi("asha@okbank"), new CashOnDelivery(), new QuickPay());
for (Payment p : payments) {
System.out.println("%-22s fee %s".formatted(Fees.describe(p), money(Fees.feePaise(p, amount))));
}
System.out.println("Payment is sealed: " + Payment.class.isSealed());
System.out.println("its permitted subtypes: " + Arrays.stream(Payment.class.getPermittedSubclasses()).map(Class::getSimpleName).toList());
System.out.println("Wallet is sealed: " + Wallet.class.isSealed());
}
/** Paise as rupees with two decimals, the same in every locale. */
static String money(long paise) {
return "%d.%02d".formatted(paise / 100, paise % 100);
}
} from session import session, version
# Five payments through one sealed interface, with each fee and description chosen by a switch without a default.
version()
session(
"""
javac -d build checkout/Payment.java checkout/QuickPay.java checkout/Fees.java checkout/Main.java
java -cp build Main
"""
) Output
# openjdk version "25.0.4.1" LTS $ javac -d build checkout/Payment.java checkout/QuickPay.java checkout/Fees.java checkout/Main.java $ java -cp build Main VISA card ending 4242 fee 25.00 AMEX card ending 0005 fee 37.50 UPI to asha@okbank fee 0.00 cash on delivery fee 40.00 QuickPay wallet fee 12.50 Payment is sealed: true its permitted subtypes: [Card, Upi, CashOnDelivery, Wallet] Wallet is sealed: false
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 checkout_session.py
Both methods of Fees are switches with one case for each permitted type and no default:
case Card(String network, String last4) when network.equals("AMEX")is a record pattern with a guard: it matches aCard, takes its two components apart, and applies only when the condition afterwhenholds, so AMEX cards pay 3 % and every other card falls tocase Card cardat 2 %. The guarded case must come first. Written the other way round, the general case would match every card, and javac rejects the guarded one as “dominated by a preceding case label” (JEP 441).case Wallet walletcoversQuickPayand every other subclass ofWallet, written by anyone, so the switch stays complete although that branch is open. Inside the open branch, each wallet computes its own fee: an ordinary overridden method.- The last three lines use reflection.
Class.isSealed()andClass.getPermittedSubclasses()arrived with sealed classes in Java 17 and read the list that javac wrote into the class file.
The records and Wallet sit in Payment.java beside the interface. That keeps the closed set in one place, but the
java launcher’s multi-file mode looks for each class in a file named after it (JEP 458), so it does not find classes
declared in another class’s file. The session compiles the program with javac first; the exercise below nests its
records inside the interface instead, which the launcher handles.
What javac checks in a sealed hierarchy
Sealing is a list, and javac holds both sides to it (JLS §8.1.1.2, §8.1.6, §9.1.4):
- Every permitted class must directly extend or implement the sealed type and declare
final,sealedornon-sealed. Records are implicitlyfinal, and enums implicitlyfinalorsealed, so both fit as they are. An interface that extends a sealed interface must itself besealedornon-sealed, since an interface cannot be final. - No class outside the list may extend or implement the sealed type.
- The permitted classes live in the same module as the sealed type or, in a program without modules, in the same package.
- When every permitted type is declared in the same file as the sealed type, the
permitsclause can be left out: javac permits exactly those types. - Anonymous classes, local classes and lambdas are refused, because they have no name a
permitsclause could list. For the same reason a sealed interface is never a functional interface (JLS §9.8), even with a single abstract method.
The JVM enforces the list too: a sealed type’s class file carries its permitted subclasses, and the JVM refuses to load
a class that claims a sealed supertype without being on that list (JEP 409). Broken.java breaks rules 1, 2 and 5,
and misuses non-sealed once; the files under shop break rule 3:
rules/Broken.java
/** Six classes that a sealed hierarchy refuses, one per numbered comment. */
sealed interface Payment permits Card, Upi {}
record Card(String last4) implements Payment {}
// 1. A permitted class must say how the hierarchy goes on: final, sealed or non-sealed.
class Upi implements Payment {}
// 2. A class that the permits clause does not list.
final class Voucher implements Payment {}
// 3. non-sealed only reopens a sealed hierarchy; Plain has no sealed supertype.
non-sealed class Plain {}
/** A sealed interface with one abstract method. */
sealed interface Rule permits MaxAmount {
boolean allows(long paise);
}
record MaxAmount(long limit) implements Rule {
@Override
public boolean allows(long paise) {
return paise <= limit;
}
}
class Checkout {
void pay() {
// 4. A lambda would be a new, unnamed class implementing Rule: a sealed interface is never functional.
Rule anything = paise -> true;
// 5. Nor may an anonymous class or (6.) a local class extend a sealed type.
Payment gift = new Payment() {};
record Coupon(String code) implements Payment {}
}
} shop/pay/Payment.java · rules/shop/pay/Payment.java
package shop.pay;
/** Permits a class of another package: refused, because the program is not in a named module. */
public sealed interface Payment permits Card, shop.partners.Voucher {} shop/pay/Card.java · rules/shop/pay/Card.java
package shop.pay;
public record Card(String last4) implements Payment {} shop/partners/Voucher.java · rules/shop/partners/Voucher.java
package shop.partners;
public record Voucher(String code) implements shop.pay.Payment {} from session import session, version
# Broken.java breaks six rules of sealed hierarchies; then a sealed interface that permits a class of another
# package, in a program without modules.
version()
session(
"""
!javac -d build rules/Broken.java
!javac -d build rules/shop/pay/Payment.java rules/shop/pay/Card.java rules/shop/partners/Voucher.java
"""
) Output
# openjdk version "25.0.4.1" LTS
$ javac -d build rules/Broken.java
rules/Broken.java:7: error: sealed, non-sealed or final modifiers expected
class Upi implements Payment {}
^
rules/Broken.java:10: error: class is not allowed to extend sealed class: Payment (as it is not listed in its 'permits' clause)
final class Voucher implements Payment {}
^
rules/Broken.java:13: error: non-sealed modifier not allowed here
non-sealed class Plain {}
^
(class Plain does not have any sealed supertypes)
rules/Broken.java:30: error: incompatible types: Rule is not a functional interface
Rule anything = paise -> true;
^
rules/Broken.java:32: error: anonymous classes must not extend sealed classes
Payment gift = new Payment() {};
^
rules/Broken.java:33: error: local classes must not extend sealed classes
record Coupon(String code) implements Payment {}
^
6 errors
[exit status 1]
$ javac -d build rules/shop/pay/Payment.java rules/shop/pay/Card.java rules/shop/partners/Voucher.java
rules/shop/pay/Payment.java:4: error: class Payment in unnamed module cannot extend a sealed class in a different package
public sealed interface Payment permits Card, shop.partners.Voucher {}
^
1 error
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rules_session.py
The messages map onto the rules one by one. Upi names no modifier (“sealed, non-sealed or final modifiers
expected”). Voucher is not on the list (“not listed in its ‘permits’ clause”). Plain uses non-sealed without a
sealed supertype, which means nothing. The lambda, the anonymous class and the local record would each be a new class
without a name. In the last run, shop.partners.Voucher is in another package, and the program has no
module-info.java, so javac refuses the permits clause itself.
Adding a payment method
Sealing pays off when the set changes. A fifth method, NetBanking, needs one word in the permits clause and one new
record, and the checkout’s fees must then cover it. The second tab is the old fee written two more ways, with a
default branch and as an if-else chain:
more/Payment.java · more/Payment.java
/**
* How a customer pays at checkout. Payment is sealed: the four types it permits are the only direct subtypes there
* will ever be, so javac knows all of them.
*/
public sealed interface Payment permits Card, Upi, CashOnDelivery, Wallet, NetBanking {}
/** A record is implicitly final: nothing can extend it. */
record Card(String network, String last4) implements Payment {}
/** A UPI payment, identified by the customer's virtual payment address. */
record Upi(String vpa) implements Payment {}
record CashOnDelivery() implements Payment {}
/** Partner wallets: non-sealed opens this one branch again, so partners extend it in their own code. */
abstract non-sealed class Wallet implements Payment {
abstract String provider();
abstract long feePaise(long amountPaise);
}
/** New: a payment through the customer's bank. */
record NetBanking(String bank) implements Payment {} checkout/LenientFees.java
/** The same fee written two other ways, which javac cannot check. */
public class LenientFees {
/** A switch with a default branch: every type that is not listed falls into it. */
static long withDefault(Payment payment, long amountPaise) {
return switch (payment) {
case Card card -> amountPaise * 2 / 100;
case Wallet wallet -> wallet.feePaise(amountPaise);
case CashOnDelivery cash -> 4_000;
default -> 0;
};
}
/** An if-else chain: the last branch takes whatever is left. */
static long withIfChain(Payment payment, long amountPaise) {
if (payment instanceof Card) return amountPaise * 2 / 100;
else if (payment instanceof Upi) return 0;
else if (payment instanceof Wallet wallet) return wallet.feePaise(amountPaise);
else return 4_000;
}
} more/Try.java · more/Try.java
/** What the two unchecked versions charge for the new payment type. */
public class Try {
public static void main(String[] args) {
Payment p = new NetBanking("Example Bank");
System.out.println("NetBanking, switch with a default: fee " + money(LenientFees.withDefault(p, 125_000)));
System.out.println("NetBanking, if-else chain: fee " + money(LenientFees.withIfChain(p, 125_000)));
}
static String money(long paise) {
return "%d.%02d".formatted(paise / 100, paise % 100);
}
} from session import session, version
# A fifth payment type, NetBanking: javac lists the switches in Fees.java that do not handle it, while the switch
# with a default and the if-else chain of LenientFees.java compile and charge it a fee nobody chose.
version()
session(
"""
!javac -d added more/Payment.java checkout/QuickPay.java checkout/Fees.java
javac -d lenient more/Payment.java checkout/QuickPay.java checkout/LenientFees.java more/Try.java
java -cp lenient Try
"""
) Output
# openjdk version "25.0.4.1" LTS
$ javac -d added more/Payment.java checkout/QuickPay.java checkout/Fees.java
checkout/Fees.java:6: error: the switch expression does not cover all possible input values
return switch (payment) {
^
checkout/Fees.java:16: error: the switch expression does not cover all possible input values
return switch (payment) {
^
2 errors
[exit status 1]
$ javac -d lenient more/Payment.java checkout/QuickPay.java checkout/LenientFees.java more/Try.java
$ java -cp lenient Try
NetBanking, switch with a default: fee 0.00
NetBanking, if-else chain: fee 40.00
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 netbanking_session.py
javac refuses Fees.java and names both switches, at lines 6 and 16: “the switch expression does not cover all
possible input values”. Those two lines are exactly the decisions the change needs, a fee and a description for net
banking, and nothing compiles until someone makes them. LenientFees.java compiles without a word. Its switch puts
NetBanking into the default branch and charges 0.00; its if-else chain lets it fall through to the last branch
and charges it 40.00, as if it were cash on delivery. Neither fee was chosen by anybody.
So in a switch over your own sealed type, leave the default out. When several types share a result, list them in one
case with unnamed patterns, case Upi _, CashOnDelivery _ -> 0 (JEP 456, Java 22), which still breaks the build when a
new type arrives. What a class file compiled before the change does when it meets the new type at run time is shown
in the low-level design track’s Java lesson, with a payment’s states instead of its
methods.
A sealed type, an enum or an open interface?
| Criterion | Enum | Sealed records |
|---|---|---|
| Fixed | The set of values | The set of types |
| Data | The same fields for every constant | Each record has its own components |
| Switch | Checked, with no default | Checked, with no default |
| Who adds | Only the enum's own file | Only the sealed type's owners |
| When to choose | Named values of one shape: seat classes, card networks | Alternatives with different data: payment methods, results, events |
An enum is the simpler tool when every alternative has the same shape: VISA and AMEX are values of one type. Once
the alternatives carry different data, a card number for one and a payment address for another, an enum ends up with
fields that are empty for most of its constants, and a sealed interface of records fits better. An open interface,
not sealed at all, is right when you cannot know the alternatives, such as plugins that other code adds; the price is
that no switch over it can be checked, so each one needs a default. That is why Wallet is the one open branch of
Payment.
Version note
Sealed classes were previews in Java 15 and 16 and became final in Java 17 (JEP 409). Switches with type patterns
and record patterns became final in Java 21 (JEPs 441 and 440), and the unnamed pattern _ in Java 22 (JEP 456).
So Java 17 compiles a sealed interface but not the switch that makes it useful, as this run shows.
/** A sealed interface and a switch with type patterns: the first compiles on Java 17, the second needs Java 21. */
public class Versions {
sealed interface Delivery permits Standard, Pickup {}
record Standard(int km) implements Delivery {}
record Pickup(String store) implements Delivery {}
static String label(Delivery d) {
return switch (d) {
case Standard s -> "standard, " + s.km() + " km";
case Pickup p -> "pickup at " + p.store();
};
}
public static void main(String[] args) {
System.out.println(label(new Standard(12)));
System.out.println(label(new Pickup("Andheri")));
}
} from session import session, version
# The same file on Java 17, which has sealed interfaces but not yet switches with type patterns, and on Java 25.
version("17")
session("!java Versions.java", jdk="17")
version()
session("java Versions.java") Output
# openjdk version "17.0.20.1"
$ java Versions.java
Versions.java:11: error: patterns in switch statements are a preview feature and are disabled by default.
case Standard s -> "standard, " + s.km() + " km";
^
(use --enable-preview to enable patterns in switch statements)
1 error
error: compilation failed
[exit status 1]
# openjdk version "25.0.4.1" LTS
$ java Versions.java
standard, 12 km
pickup at Andheri
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 versions_session.py
Your turn: delivery fees
A shop delivers in three ways, modelled in Delivery.java as records nested in a sealed interface without a permits
clause. DeliveryFees.feePaise() should compute each fee, and it returns 0 for everything so far:
delivery/Delivery.java
/**
* How an order reaches the customer. The three records are nested in the sealed interface, so the permits clause
* can be left out: javac permits exactly the types declared in this file that implement Delivery.
*/
public sealed interface Delivery {
/** Delivered within two days; `km` is the distance from the store in whole kilometres. */
record Standard(int km) implements Delivery {}
/** Delivered within two hours, and at night if `night` is true. */
record Express(int km, boolean night) implements Delivery {}
/** Collected by the customer at a store. */
record Pickup(String store) implements Delivery {}
} delivery/DeliveryFees.java
public class DeliveryFees {
/**
* The delivery fee in paise:
* - Standard: 30.00 for the first 5 km, then 5.00 for each further kilometre;
* - Express: twice the standard fee for the same distance, plus 50.00 at night;
* - Pickup: nothing.
* TODO: write it as one switch over the sealed interface, with a case for each record and no default.
*/
static long feePaise(Delivery delivery) {
return 0;
}
} delivery/Checks.java
/** The sample checks of the exercise: run with java delivery/Checks.java. */
public class Checks {
private static int passed, run;
private static void check(String what, long got, long expected) {
run++;
if (got == expected) passed++;
System.out.printf("%s %-26s got %6d, expected %6d%n", got == expected ? "ok " : "FAIL", what, got, expected);
}
public static void main(String[] args) {
check("standard, 3 km", DeliveryFees.feePaise(new Delivery.Standard(3)), 3_000);
check("standard, 5 km", DeliveryFees.feePaise(new Delivery.Standard(5)), 3_000);
check("standard, 12 km", DeliveryFees.feePaise(new Delivery.Standard(12)), 6_500);
check("express by day, 3 km", DeliveryFees.feePaise(new Delivery.Express(3, false)), 6_000);
check("express at night, 12 km", DeliveryFees.feePaise(new Delivery.Express(12, true)), 18_000);
check("pickup", DeliveryFees.feePaise(new Delivery.Pickup("MG Road")), 0);
System.out.println(passed + " of " + run + " checks passed");
}
} from session import session, version
# The sample checks against the starter code, whose fee is still 0 for every delivery.
version()
session("java delivery/Checks.java") Output
# openjdk version "25.0.4.1" LTS $ java delivery/Checks.java FAIL standard, 3 km got 0, expected 3000 FAIL standard, 5 km got 0, expected 3000 FAIL standard, 12 km got 0, expected 6500 FAIL express by day, 3 km got 0, expected 6000 FAIL express at night, 12 km got 0, expected 18000 ok pickup got 0, expected 0 1 of 6 checks passed
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py
- Write
feePaise()as one switch expression overDeliverywith a case for each record and nodefault. A standard delivery costs 30.00 for the first 5 km and 5.00 for each further kilometre; express costs twice the standard fee for the same distance, plus 50.00 at night; pickup costs nothing. Record patterns such ascase Delivery.Express(int km, boolean night)give you the components directly. - Run
java delivery/Checks.javauntil it prints6 of 6 checks passed. - Add a fourth record,
Scheduled(int km, String slot), for deliveries in a chosen two-hour slot. Before you change the switch, compile again and read the error javac gives you. Then give scheduled deliveries the standard fee plus 20.00.
Interview questions
Fresher · What is a sealed class in Java, and what must the classes it permits declare?
A sealed class or interface lists, in a permits clause, the only classes allowed to extend or implement it directly; the compiler refuses any other, and the JVM refuses to load one. Each permitted subclass must declare how the hierarchy continues: final, so nothing extends it; sealed, with a permits list of its own; or non-sealed, which opens that branch to any subclass. Records are implicitly final and enums implicitly final or sealed, so both can be permitted as they are. The permitted classes must be in the same module, or in the same package when there are no modules. The main benefit is that the compiler knows every subtype, so a switch over a sealed type that handles each one needs no default branch and fails to compile when a new subtype is added. Sealed classes are a standard feature since Java 17.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
Partners add export formats to your reporting product through plugins, and the core code switches over the format type in five places. A teammate proposes making Format a sealed interface so that the compiler checks those switches. When is that right, when is it wrong, and what would you design instead?
-
Mid to senior
Your library's public API has sealed interface Event permits Created, Paid, Cancelled, and client teams switch over it without default branches. Product needs a new Refunded event. What happens to clients that recompile against the new version, to clients that do not, and how would you release the change?
Key takeaways
sealedwithpermitscloses the set of direct subtypes; each permitted type must befinal,sealedornon-sealed, and records and enums qualify as they are.- Permitted types live in the same module, or in the same package without modules, and may leave out
permitswhen they share the sealed type’s file. - A switch over a sealed type with a case for every permitted type needs no
default, and adding a type makes javac list each switch that must handle it. - A
defaultbranch or anif-elsechain compiles silently after such a change and gives the new type an answer nobody chose. non-sealedreopens one branch for extension while the top of the hierarchy stays closed.- Use an enum for values of one shape, a sealed interface of records for alternatives with different data, and an open interface where other code must add alternatives.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- JEP 409: Sealed Classes (OpenJDK)
- The Java Language Specification, Java SE 25: 8.1.1.2 sealed, non-sealed, and final Classes (Oracle)
- The Java Language Specification, Java SE 25: 8.1.6 Permitted Direct Subclasses (Oracle)
- The Java Language Specification, Java SE 25: 9.1.4 Permitted Direct Subclasses and Subinterfaces (Oracle)
- The Java Language Specification, Java SE 25: 9.8 Functional Interfaces (Oracle)
- The Java Language Specification, Java SE 25: 14.11.1 Switch Blocks (Oracle)
- JEP 441: Pattern Matching for switch (OpenJDK)
- JEP 440: Record Patterns (OpenJDK)
- JEP 456: Unnamed Variables & Patterns (OpenJDK)
- JEP 458: Launch Multi-File Source-Code Programs (OpenJDK)
- Java SE 25 API: Class (isSealed and getPermittedSubclasses) (Oracle)
- Java Language Updates, Java SE 25: Sealed Classes and Interfaces (Oracle)
Report a problem with this lesson
Kept only in this browser. Your Learn progress