Using Java Switch with Enum: Syntax and Patterns
Learn how to use switch with enums in Java. Covers the classic switch statement, switch expressions, exhaustiveness, pattern matching, null handling, and performance tradeoffs.
When a Java developer needs to map an enum constant to a specific behavior, the switch statement is often the first tool that comes to mind. Switching on an enum is straightforward, but modern Java offers several syntax options that change how you write and maintain that logic. This article covers the classic switch statement, switch expressions, exhaustiveness, pattern matching, and the edge cases you should watch for.
Why Switch on Enum Is Common in Java
Enums represent a fixed set of constants, and switch statements provide a natural way to branch on each constant. Unlike if-else chains, a switch on an enum is readable and centralizes the mapping logic. The compiler also gives you some help: if you use a switch expression, its exhaustiveness checking can detect when you miss a constant. This makes the combination of enums and switch a core tool for implementing state machines, command dispatch, and policy selection.
Classic Switch Statement with Enum
The traditional switch statement uses colon syntax and requires a break for each case. Here is a simple example with an enum representing days of the week:
public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } public String classify(Day day) { String type; switch (day) { case MONDAY: case TUESDAY: case WEDNESDAY: case THURSDAY: case FRIDAY: type = "weekday"; break; case SATURDAY: case SUNDAY: type = "weekend"; break; default: throw new IllegalArgumentException("Unexpected day: " + day); } return type; }
This works, but the break statements are easy to forget, and the variable assignment pattern is verbose. The compiler does not enforce that every enum constant is handled, so a default is often useful to make an unhandled constant fail explicitly instead of silently doing nothing.
Switch Expressions and Arrow Labels
Java 14 introduced switch expressions, which allow you to use the result of a switch directly as an expression. With arrow labels, you can write each case as a single expression, and no break is needed. The same logic becomes:
public String classify(Day day) { return switch (day) { case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "weekday"; case SATURDAY, SUNDAY -> "weekend"; }; }
This version is more concise and less error-prone. The arrow labels also prevent fallthrough, and the switch expression must yield a value or throw an exception. If you omit the default and the switch is not exhaustive, the compiler will report an error. This is a significant improvement over the classic statement.
Handling Exhaustiveness and Default
When you use a switch expression on an enum, the compiler requires that all possible values are covered. If you add a new constant to the enum later, the switch expression will fail to compile until you handle it. This is a powerful maintainability benefit. For example, if you add HOLIDAY to the Day enum, the switch expression above will no longer compile because HOLIDAY is not handled. You must update the switch.
You can also provide a default case to cover values outside the listed cases, but it will not handle null: the switch evaluates the selector before dispatch and throws NullPointerException first. With an enum selector, the compiler already knows the full set of non-null values, so a default is mainly useful when the selector is an interface type that could have implementations other than your enums. For a pure enum switch, relying on exhaustiveness is cleaner and makes the compiler your ally.
Pattern Matching for Switch with Enum (Java 21+)
Java 21 finalized pattern matching for switch. This feature allows you to match on type patterns and use guards, which is useful when you have an enum that implements an interface or when you need to combine enum matching with additional conditions. For example, consider a sealed interface Operation with two enum implementations:
sealed interface Operation permits Add, Multiply { int apply(int a, int b); } enum Add implements Operation { INSTANCE; public int apply(int a, int b) { return a + b; } } enum Multiply implements Operation { INSTANCE; public int apply(int a, int b) { return a * b; } }
With pattern matching, you can switch on the enum constant and also access its methods:
public int execute(Operation op, int a, int b) { return switch (op) { case Add add -> add.apply(a, b); case Multiply mul -> mul.apply(a, b); }; }
Here, Operation is sealed, so those two patterns are exhaustive and no default is required. This is more flexible than a simple constant switch because it binds the enum constant to a variable. You can also use guards to refine the match. Here, the first Add case applies only when verbose is true; if the guard is false, matching continues to the next compatible Add case:
public String describe(Operation op, boolean verbose) { return switch (op) { case Add add when verbose -> "addition (verbose)"; case Add add -> "addition"; case Multiply mul -> "multiplication"; }; }
Pattern matching for switch works with any reference type, but it is especially useful when your enum carries state or implements behavior.
Null Handling and Edge Cases
A switch on an enum throws NullPointerException if the enum value is null. This is true for both the classic statement and switch expressions. If null is a valid input in your domain, you must handle it explicitly before the switch. A default case cannot catch null, because the switch throws before any case label is considered. The safest pattern is to guard against null at the method entry:
public String classify(Day day) { if (day == null) { return "unknown"; } return switch (day) { case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "weekday"; case SATURDAY, SUNDAY -> "weekend"; }; }
Another edge case is when you have an enum with a large number of constants. A switch with dozens of cases can become unwieldy. In that situation, consider using a Map<Enum, Function> or a method on the enum itself. The switch is still efficient, but readability suffers.
Performance and Maintainability Considerations
Switch on enum is typically compiled to an efficient lookup using the enum's ordinal, so there is no performance reason to avoid it. The main tradeoff is maintainability. A switch statement forces you to handle each constant in one place, which is good for centralizing logic but can become a maintenance burden if the enum changes frequently. Switch expressions with exhaustiveness checks mitigate this by moving errors to compile time. Pattern matching adds flexibility but can make the code harder to follow if overused. For most cases, a simple switch expression is the right balance of clarity and safety.
When you need to add a new enum constant and you rely on exhaustiveness, the compiler will guide you through every switch expression that must be updated. This is a strong argument for preferring switch expressions over the classic statement in new code. If you are working with a legacy codebase that uses classic switch statements, consider migrating them incrementally to switch expressions to gain compile-time exhaustiveness checks.