{"id":"sealed-and-patterns-switch","slug":"switch-on-types","title":"switch on sealed types","text":"In module 57, a switch expression over an enum needed no `default` when it had a case for every constant. Since Java 21, a switch can also choose by an object's **type**, and a sealed interface's permitted types work like an enum's constants: when every one has a case, the switch is complete.\n\n### Type patterns in case labels\n\nThe café's payments, now as records:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(receipt(new Cash(450)));\n        System.out.println(receipt(new BankCard(\"4921\", 1250)));\n        System.out.println(receipt(new Voucher(\"SPRING10\", 1000)));\n    }\n\n    public static String receipt(Payment payment) {\n        return switch (payment) {\n            case Cash cash -> \"Cash: \" + cash.cents() + \" cents\";\n            case BankCard card -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents\";\n            case Voucher voucher -> \"Voucher \" + voucher.code() + \": \" + voucher.cents() + \" cents\";\n        };\n    }\n}\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard, Voucher {\n}\n// ==== Cash.java ====\npublic record Cash(int cents) implements Payment {\n}\n// ==== BankCard.java ====\npublic record BankCard(String lastDigits, int cents) implements Payment {\n}\n// ==== Voucher.java ====\npublic record Voucher(String code, int cents) implements Payment {\n}\n```\n\n```output\nCash: 450 cents\nCard ending 4921: 1250 cents\nVoucher SPRING10: 1000 cents\n```\n\n- `case Cash cash ->` has a type pattern, like the ones `instanceof` takes. The case matches when the payment is a Cash, and then `cash` is the payment as a Cash, for that case only.\n- Java tries the cases from the top, and the first one that matches gives the value.\n- There is no `default`, and none is needed: Payment is sealed, so javac knows that every payment is a Cash, a BankCard or a Voucher. A switch that covers every possible value is **exhaustive**.\n\nThe same choice with `instanceof` needs a last `return` for \"none of these\", which can never happen. The switch says exactly what the program means, and javac checks it.\n\n### javac finds the switches to update\n\nThe café starts taking mobile payments. Someone adds `MobilePay` to the permits list and writes the record, but forgets the switch:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(receipt(new MobilePay(\"040 123 4567\", 300)));\n    }\n\n    public static String receipt(Payment payment) {\n        return switch (payment) {\n            case Cash cash -> \"Cash: \" + cash.cents() + \" cents\";\n            case BankCard card -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents\";\n            case Voucher voucher -> \"Voucher \" + voucher.code() + \": \" + voucher.cents() + \" cents\";\n        };\n    }\n}\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard, Voucher, MobilePay {\n}\n// ==== Cash.java ====\npublic record Cash(int cents) implements Payment {\n}\n// ==== BankCard.java ====\npublic record BankCard(String lastDigits, int cents) implements Payment {\n}\n// ==== Voucher.java ====\npublic record Voucher(String code, int cents) implements Payment {\n}\n// ==== MobilePay.java ====\npublic record MobilePay(String phone, int cents) implements Payment {\n}\n```\n\n```javac\nMain.java:7: error: the switch expression does not cover all possible input values\n        return switch (payment) {\n               ^\n1 error\n```\n\nIn a large program, javac points at every switch that needs a case for the new type. With a `default` in them, the program would compile, and every mobile payment would quietly get whatever the default gives. So with sealed types, leave `default` out.\n\nA switch *statement* (one that gives no value) with type patterns must cover every type too. And as with every switch, a `null` payment throws a NullPointerException; since Java 21 a switch can have `case null ->` for it, but it's usually better not to pass `null` around at all.\n\n### Guards: case ... when\n\nA case can have a condition after the word `when`. The case matches only when the type matches and the condition is true. A card payment over 50 euros needs the card's PIN:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(receipt(new BankCard(\"4921\", 1250)));\n        System.out.println(receipt(new BankCard(\"4921\", 8900)));\n        System.out.println(receipt(new Cash(8900)));\n    }\n\n    public static String receipt(Payment payment) {\n        return switch (payment) {\n            case Cash cash -> \"Cash: \" + cash.cents() + \" cents\";\n            case BankCard card when card.cents() > 5000 -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents, PIN needed\";\n            case BankCard card -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents\";\n            case Voucher voucher -> \"Voucher \" + voucher.code() + \": \" + voucher.cents() + \" cents\";\n        };\n    }\n}\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard, Voucher {\n}\n// ==== Cash.java ====\npublic record Cash(int cents) implements Payment {\n}\n// ==== BankCard.java ====\npublic record BankCard(String lastDigits, int cents) implements Payment {\n}\n// ==== Voucher.java ====\npublic record Voucher(String code, int cents) implements Payment {\n}\n```\n\n```output\nCard ending 4921: 1250 cents\nCard ending 4921: 8900 cents, PIN needed\nCash: 8900 cents\n```\n\nA case with `when` doesn't count toward covering BankCard, since its condition can be false; the plain `case BankCard card` after it does. The order matters: the guarded case must come first. In the other order, the plain case would match every card, and the guarded one could never be reached. javac doesn't allow that:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(receipt(new BankCard(\"4921\", 8900)));\n    }\n\n    public static String receipt(Payment payment) {\n        return switch (payment) {\n            case Cash cash -> \"Cash: \" + cash.cents() + \" cents\";\n            case BankCard card -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents\";\n            case BankCard card when card.cents() > 5000 -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents, PIN needed\";\n            case Voucher voucher -> \"Voucher \" + voucher.code() + \": \" + voucher.cents() + \" cents\";\n        };\n    }\n}\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard, Voucher {\n}\n// ==== Cash.java ====\npublic record Cash(int cents) implements Payment {\n}\n// ==== BankCard.java ====\npublic record BankCard(String lastDigits, int cents) implements Payment {\n}\n// ==== Voucher.java ====\npublic record Voucher(String code, int cents) implements Payment {\n}\n```\n\n```javac\nMain.java:10: error: this case label is dominated by a preceding case label\n            case BankCard card when card.cents() > 5000 -> \"Card ending \" + card.lastDigits() + \": \" + card.cents() + \" cents, PIN needed\";\n                 ^\n1 error\n```\n","task":"Back to the floor plan. This time the shapes are records with no methods of their own, and `Main.java` works out the areas. Write `area(Shape shape)` with a switch expression on `shape`: a rectangle's area is its width times its depth, and a circle's is `Math.PI` times the radius times the radius. Don't use `default` or `instanceof`. The program prints the area of each room and then the total:\n\n```\nLiving room: 22.5\nBedroom: 12.0\nTower: 7.0685834705770345\nTotal: 41.56858347057703\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Shape living = new Rectangle(5.0, 4.5);\n        Shape bedroom = new Rectangle(4.0, 3.0);\n        Shape tower = new Circle(1.5);\n        System.out.println(\"Living room: \" + area(living));\n        System.out.println(\"Bedroom: \" + area(bedroom));\n        System.out.println(\"Tower: \" + area(tower));\n        System.out.println(\"Total: \" + (area(living) + area(bedroom) + area(tower)));\n    }\n\n    public static double area(Shape shape) {\n        return 0.0;\n    }\n}\n\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n}\n\n// ==== Rectangle.java ====\npublic record Rectangle(double width, double depth) implements Shape {\n}\n\n// ==== Circle.java ====\npublic record Circle(double radius) implements Shape {\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Shape living = new Rectangle(5.0, 4.5);\n        Shape bedroom = new Rectangle(4.0, 3.0);\n        Shape tower = new Circle(1.5);\n        System.out.println(\"Living room: \" + area(living));\n        System.out.println(\"Bedroom: \" + area(bedroom));\n        System.out.println(\"Tower: \" + area(tower));\n        System.out.println(\"Total: \" + (area(living) + area(bedroom) + area(tower)));\n    }\n\n    public static double area(Shape shape) {\n        return switch (shape) {\n            case Rectangle rectangle -> rectangle.width() * rectangle.depth();\n            case Circle circle -> Math.PI * circle.radius() * circle.radius();\n        };\n    }\n}\n\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n}\n\n// ==== Rectangle.java ====\npublic record Rectangle(double width, double depth) implements Shape {\n}\n\n// ==== Circle.java ====\npublic record Circle(double radius) implements Shape {\n}\n","hints":["`return switch (shape) { ... };` with one case for each permitted type: `case Rectangle rectangle -> ...` and `case Circle circle -> ...`.","In a case, read the values with the record's accessors: `rectangle.width() * rectangle.depth()`, `Math.PI * circle.radius() * circle.radius()`."],"tests":[{"name":"Test 1","stdin":"","expect":"Living room: 22.5\nBedroom: 12.0\nTower: 7.0685834705770345\nTotal: 41.56858347057703","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(area(new Rectangle(2.5, 2.0)));\nSystem.out.println(area(new Circle(1.0)));\nSystem.out.println(area(new Circle(0.0)) + area(new Rectangle(0.5, 0.5)));\n","expect":"5.0\n3.141592653589793\n0.25","hidden":true}],"require":[{"pattern":"\\bswitch\\s*\\(\\s*shape\\s*\\)","message":"Choose by the shape's type with `switch (shape)`."},{"pattern":"\\bcase\\s+Rectangle\\b","message":"Give rectangles a case with a type pattern, such as `case Rectangle rectangle ->`."}],"forbid":[{"pattern":"\\bdefault\\b","message":"Leave out `default`: Shape is sealed, so a case for each permitted type is enough."},{"pattern":"\\binstanceof\\b","message":"Use the switch's case labels instead of `instanceof`."}],"more":[{"task":"The floor plan app gets a third shape, `Triangle(double base, double height)`, for attic rooms. It's already in `Triangle.java` and in Shape's permits list, but now `Main.java` doesn't compile: press Run, and javac names both switch expressions. Add a case for triangles to each: a triangle's area is base times height divided by 2, and its label is `triangle`, the base, ` x `, and the height, like the other labels. Don't add a `default`. The program prints:\n\n```\nrectangle 5.0 x 4.5: 22.5\ncircle radius 1.5: 7.0685834705770345\ntriangle 4.0 x 2.5: 5.0\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Shape[] rooms = {new Rectangle(5.0, 4.5), new Circle(1.5), new Triangle(4.0, 2.5)};\n        for (Shape room : rooms) {\n            System.out.println(label(room) + \": \" + area(room));\n        }\n    }\n\n    public static double area(Shape shape) {\n        return switch (shape) {\n            case Rectangle rectangle -> rectangle.width() * rectangle.depth();\n            case Circle circle -> Math.PI * circle.radius() * circle.radius();\n        };\n    }\n\n    public static String label(Shape shape) {\n        return switch (shape) {\n            case Rectangle rectangle -> \"rectangle \" + rectangle.width() + \" x \" + rectangle.depth();\n            case Circle circle -> \"circle radius \" + circle.radius();\n        };\n    }\n}\n\n// ==== Triangle.java ====\npublic record Triangle(double base, double height) implements Shape {\n}\n\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle, Triangle {\n}\n\n// ==== Rectangle.java ====\npublic record Rectangle(double width, double depth) implements Shape {\n}\n\n// ==== Circle.java ====\npublic record Circle(double radius) implements Shape {\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Shape[] rooms = {new Rectangle(5.0, 4.5), new Circle(1.5), new Triangle(4.0, 2.5)};\n        for (Shape room : rooms) {\n            System.out.println(label(room) + \": \" + area(room));\n        }\n    }\n\n    public static double area(Shape shape) {\n        return switch (shape) {\n            case Rectangle rectangle -> rectangle.width() * rectangle.depth();\n            case Circle circle -> Math.PI * circle.radius() * circle.radius();\n            case Triangle triangle -> triangle.base() * triangle.height() / 2;\n        };\n    }\n\n    public static String label(Shape shape) {\n        return switch (shape) {\n            case Rectangle rectangle -> \"rectangle \" + rectangle.width() + \" x \" + rectangle.depth();\n            case Circle circle -> \"circle radius \" + circle.radius();\n            case Triangle triangle -> \"triangle \" + triangle.base() + \" x \" + triangle.height();\n        };\n    }\n}\n\n// ==== Triangle.java ====\npublic record Triangle(double base, double height) implements Shape {\n}\n\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle, Triangle {\n}\n\n// ==== Rectangle.java ====\npublic record Rectangle(double width, double depth) implements Shape {\n}\n\n// ==== Circle.java ====\npublic record Circle(double radius) implements Shape {\n}\n","hints":["javac's two errors point at the two `switch` lines: each needs a `case Triangle triangle -> ...`.","The area is `triangle.base() * triangle.height() / 2`, and the label `\"triangle \" + triangle.base() + \" x \" + triangle.height()`."],"tests":[{"name":"Test 1","stdin":"","expect":"rectangle 5.0 x 4.5: 22.5\ncircle radius 1.5: 7.0685834705770345\ntriangle 4.0 x 2.5: 5.0","hidden":false},{"name":"Test 2","stdin":"","call":"Shape attic = new Triangle(3.0, 1.0);\nSystem.out.println(label(attic) + \" \" + area(attic));\nSystem.out.println(label(new Rectangle(1.0, 2.0)) + \" \" + area(new Triangle(0.5, 8.0)));\n","expect":"triangle 3.0 x 1.0 1.5\nrectangle 1.0 x 2.0 2.0","hidden":true}],"require":[{"pattern":"\\bcase\\s+Triangle\\b","min":2,"message":"Give triangles a case in both switches, such as `case Triangle triangle ->`."}],"forbid":[{"pattern":"\\bdefault\\b","message":"Don't add `default`: a case for Triangle makes each switch complete again."},{"pattern":"\\binstanceof\\b","message":"Use the switches' case labels instead of `instanceof`."}]},{"task":"Back in the chat app, ChatMessage is now sealed, with three records. Write `preview(ChatMessage message)` with a switch expression, using `when` for the special cases:\n\n- a text message longer than 20 characters: the sender, `: `, its first 20 characters and `...`;\n- any other text message: the sender, `: ` and the text;\n- a photo over 1000 kB: the sender and `: [large photo, ` and the size and ` kB]`;\n- any other photo: the sender and `: [photo]`;\n- a sticker: the sender and `: [sticker ` and the sticker's name and `]`.\n\nDon't use `default`. The check runs:\n\n```java\nSystem.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\nSystem.out.println(preview(new TextMessage(\"Ben\", \"Running late, the bus never came\")));\nSystem.out.println(preview(new PhotoMessage(\"Chen\", \"menu.jpg\", 840)));\nSystem.out.println(preview(new PhotoMessage(\"Chen\", \"view.png\", 4200)));\nSystem.out.println(preview(new StickerMessage(\"Dora\", \"thumbs up\")));\n```\n\nand expects:\n\n```\nAino: Lunch at noon?\nBen: Running late, the bu...\nChen: [photo]\nChen: [large photo, 4200 kB]\nDora: [sticker thumbs up]\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\n        System.out.println(preview(new TextMessage(\"Ben\", \"Running late, the bus never came\")));\n        System.out.println(preview(new PhotoMessage(\"Chen\", \"menu.jpg\", 840)));\n        System.out.println(preview(new PhotoMessage(\"Chen\", \"view.png\", 4200)));\n        System.out.println(preview(new StickerMessage(\"Dora\", \"thumbs up\")));\n    }\n\n    public static String preview(ChatMessage message) {\n        return \"\";\n    }\n}\n\n// ==== ChatMessage.java ====\npublic sealed interface ChatMessage permits TextMessage, PhotoMessage, StickerMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n\n// ==== StickerMessage.java ====\npublic record StickerMessage(String sender, String name) implements ChatMessage {\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\n        System.out.println(preview(new TextMessage(\"Ben\", \"Running late, the bus never came\")));\n        System.out.println(preview(new PhotoMessage(\"Chen\", \"menu.jpg\", 840)));\n        System.out.println(preview(new PhotoMessage(\"Chen\", \"view.png\", 4200)));\n        System.out.println(preview(new StickerMessage(\"Dora\", \"thumbs up\")));\n    }\n\n    public static String preview(ChatMessage message) {\n        return switch (message) {\n            case TextMessage typed when typed.text().length() > 20 -> typed.sender() + \": \" + typed.text().substring(0, 20) + \"...\";\n            case TextMessage typed -> typed.sender() + \": \" + typed.text();\n            case PhotoMessage photo when photo.kilobytes() > 1000 -> photo.sender() + \": [large photo, \" + photo.kilobytes() + \" kB]\";\n            case PhotoMessage photo -> photo.sender() + \": [photo]\";\n            case StickerMessage sticker -> sticker.sender() + \": [sticker \" + sticker.name() + \"]\";\n        };\n    }\n}\n\n// ==== ChatMessage.java ====\npublic sealed interface ChatMessage permits TextMessage, PhotoMessage, StickerMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n\n// ==== StickerMessage.java ====\npublic record StickerMessage(String sender, String name) implements ChatMessage {\n}\n","hints":["Put each guarded case before the plain case of the same type: `case TextMessage typed when typed.text().length() > 20 -> ...` and then `case TextMessage typed -> ...`.","`typed.text().substring(0, 20)` gives the first 20 characters. A photo is large when `photo.kilobytes() > 1000`."],"tests":[{"name":"Test 1","stdin":"","call":"System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\nSystem.out.println(preview(new TextMessage(\"Ben\", \"Running late, the bus never came\")));\nSystem.out.println(preview(new PhotoMessage(\"Chen\", \"menu.jpg\", 840)));\nSystem.out.println(preview(new PhotoMessage(\"Chen\", \"view.png\", 4200)));\nSystem.out.println(preview(new StickerMessage(\"Dora\", \"thumbs up\")));\n","expect":"Aino: Lunch at noon?\nBen: Running late, the bu...\nChen: [photo]\nChen: [large photo, 4200 kB]\nDora: [sticker thumbs up]","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(preview(new TextMessage(\"Eli\", \"Exactly twenty chars\")));\nSystem.out.println(preview(new TextMessage(\"Eli\", \"Twenty-one characters\")));\nSystem.out.println(preview(new PhotoMessage(\"Fay\", \"a.png\", 1000)));\nSystem.out.println(preview(new PhotoMessage(\"Fay\", \"b.png\", 1001)));\nSystem.out.println(preview(new StickerMessage(\"Gus\", \"party hat\")));\n","expect":"Eli: Exactly twenty chars\nEli: Twenty-one character...\nFay: [photo]\nFay: [large photo, 1001 kB]\nGus: [sticker party hat]","hidden":true}],"require":[{"pattern":"\\bswitch\\s*\\(\\s*message\\s*\\)","message":"Choose with `switch (message)`."},{"pattern":"\\bwhen\\b","min":2,"message":"Use `when` for the two special cases, long texts and large photos."}],"forbid":[{"pattern":"\\bdefault\\b","message":"Leave out `default`: ChatMessage is sealed, so a case for each type is enough."},{"pattern":"\\binstanceof\\b","message":"Use the switch's case labels instead of `instanceof`."}]}]}