JB Header

Java Switch Expressions: Arrow Labels and yield

Introduction The classic switch statement has two well-known problems - forgetting a break causes fall-through, and getting a value out of a switch needs a variable assigned in every case. Switch expressions, standard since Java 14, fix both. This tutorial explains Java switch expressions with arrow labels and yield. We will start with arrow labels, which never fall through. We will then use switch as an expression which produces a value, and use yield to return a value from a block. Next we will see how the compiler checks that a switch expression covers every possible value. Lastly, we will take a first look at handling null and at pattern matching in switch, added in Java 21.

(Note - This tutorial builds on the classic switch statementClick to read tutorial: Java switch Statement Tutorial with Examples.) Arrow labels - no more fall-through Instead of case value:, write case value ->. With an arrow label, only the code to the right of the arrow runs - there is no fall-through, and no break is needed. The right-hand side can be a single expression, a block in braces, or a throw statement. Several values can share one label, separated by commas.
A switch statement with arrow labels
//ArrowSwitch.java
public class ArrowSwitch {
  public static void main(String[] args) {
    int level = 2;
    switch (level) {
      case 1 -> System.out.println("Level 1: Beginner");
      case 2 -> System.out.println("Level 2: Intermediate");
      case 3 -> System.out.println("Level 3: Advanced");
      default -> System.out.println("Unknown level");
    }

    String day = "SUN";
    switch (day) {
      case "SAT", "SUN" -> System.out.println(day + ": weekend - lie in!");
      case "MON", "TUE", "WED", "THU", "FRI" -> {
        System.out.println(day + ": weekday");
        System.out.println("Alarm set for 7:00");
      }
      default -> System.out.println("Not a day: " + day);
    }
  }
}
 OUTPUT of the above code
Level 2: Intermediate
SUN: weekend - lie in!
Compare the first switch with the fall-through bug in the switch statement tutorialClick to read tutorial: Java switch Statement Tutorial with Examples: here only case 2 ran. A single switch must use either arrow labels or colon labels throughout - they cannot be mixed. Switch as an expression A switch can also be an expression which produces a value. The value of the matching case becomes the value of the whole switch, which can be assigned to a variable, returned, or passed to a method. Note the semicolon after the closing brace when it is part of a statement.
A switch expression that returns a value
//SwitchExpression.java
public class SwitchExpression {
  static int daysIn(int month, boolean leapYear) {
    return switch (month) {
      case 4, 6, 9, 11 -> 30;
      case 2 -> leapYear ? 29 : 28;
      case 1, 3, 5, 7, 8, 10, 12 -> 31;
      default -> throw new IllegalArgumentException("Invalid month: " + month);
    };
  }

  public static void main(String[] args) {
    String size = "M";
    double price = switch (size) {
      case "S" -> 9.99;
      case "M" -> 12.99;
      case "L", "XL" -> 14.99;
      default -> 0;
    };
    System.out.println("Price of size " + size + ": " + price);
    System.out.println("Days in February 2028: " + daysIn(2, true));
    System.out.println("Days in month 13: " + daysIn(13, false));
  }
}
 OUTPUT of the above code
Price of size M: 12.99
Days in February 2028: 29
Exception in thread "main" java.lang.IllegalArgumentException: Invalid month: 13
	at SwitchExpression.daysIn(SwitchExpression.java:8)
	at SwitchExpression.main(SwitchExpression.java:22)
Explanation of the code
  • Compare daysIn() with the same method in the switch statement tutorial - no days variable, no breaks, and the whole method is a single return.
  • A throw is allowed on the right of an arrow, which is a clean way to reject invalid values. Here it throws an IllegalArgumentException for month 13.
  • The default -> 0 case returns an int, which is promoted to double along with the other values.
yield - returning a value from a block When a case needs several statements before it can produce its value, use a block, and give the value with yield. yield is to a switch expression what return is to a method - it ends the case and supplies the switch's value.
Using yield in a switch expression
//YieldDemo.java
public class YieldDemo {
  static double shippingCost(String zone, double weightKg) {
    return switch (zone) {
      case "LOCAL" -> 2.50;
      case "NATIONAL" -> {
        double cost = 4.00 + weightKg * 0.50;
        yield Math.min(cost, 15.00);           // capped at 15
      }
      case "INTERNATIONAL" -> {
        if (weightKg > 20) {
          yield -1;                            // too heavy to ship
        }
        yield 12.00 + weightKg * 2.00;
      }
      default -> throw new IllegalArgumentException("Unknown zone: " + zone);
    };
  }

  public static void main(String[] args) {
    System.out.println("LOCAL, 3 kg          : " + shippingCost("LOCAL", 3));
    System.out.println("NATIONAL, 3 kg       : " + shippingCost("NATIONAL", 3));
    System.out.println("NATIONAL, 40 kg      : " + shippingCost("NATIONAL", 40));
    System.out.println("INTERNATIONAL, 3 kg  : " + shippingCost("INTERNATIONAL", 3));
    System.out.println("INTERNATIONAL, 25 kg : " + shippingCost("INTERNATIONAL", 25));
  }
}
 OUTPUT of the above code
LOCAL, 3 kg          : 2.5
NATIONAL, 3 kg       : 5.5
NATIONAL, 40 kg      : 15.0
INTERNATIONAL, 3 kg  : 18.0
INTERNATIONAL, 25 kg : -1.0
Note - yield also works in switch expressions which use the old colon labels - case 1: yield "one"; - but arrow labels are clearer and are preferred. Exhaustiveness - covering every value A switch expression must always produce a value, so the compiler checks that it is exhaustive - that every possible input is handled. For an int or String, that means a default is required. For an enum, listing every constant is enough, and no default is needed. If a constant is missing, the code does not compile -
A switch expression that misses an enum constant
//Seasons.java
public class Seasons {
  enum Season { WINTER, SPRING, SUMMER, AUTUMN }

  static String describe(Season season) {
    return switch (season) {
      case WINTER -> "cold";
      case SPRING -> "mild";
      case SUMMER -> "hot";
    };
  }
}
 OUTPUT on compiling the above code
Seasons.java:6: error: the switch expression does not cover all possible input values
    return switch (season) {
           ^
1 error
This is a big advantage over the classic switch statement. If someone later adds a constant to an enum, every switch expression which does not handle it fails to compile, instead of silently doing nothing at runtime. Leave out default when switching on an enum, so that you get this check.
Handling null and type patterns (Java 21) Java 21 made switch even more powerful -
  • case null: A switch normally throws a NullPointerException when the value is null. Adding a case null handles it instead.
  • Type patterns: A case can test the type of an object and give it a name in one step - case Integer i -> matches any Integer, and makes it available as i.
  • Guards: when adds an extra condition to a pattern - case Integer i when i < 0 ->.
case null and type patterns in a switch (Java 21+)
//PatternSwitch.java
public class PatternSwitch {
  static String describe(Object value) {
    return switch (value) {
      case null -> "nothing at all";
      case Integer i when i < 0 -> "a negative number: " + i;
      case Integer i -> "a number: " + i;
      case String s when s.isBlank() -> "a blank string";
      case String s -> "a string of length " + s.length();
      default -> "something else: " + value.getClass().getSimpleName();
    };
  }

  public static void main(String[] args) {
    System.out.println(describe(42));
    System.out.println(describe(-7));
    System.out.println(describe("Java"));
    System.out.println(describe("   "));
    System.out.println(describe(3.14));
    System.out.println(describe(null));
  }
}
 OUTPUT of the above code
a number: 42
a negative number: -7
a string of length 4
a blank string
something else: Double
nothing at all
Explanation of the code
  • Cases are checked from top to bottom, so the guarded case Integer i when i < 0 must come before the plain Integer i case. The compiler reports an error if a case can never be reached.
  • Inside each case, i and s already have the right type - no cast is needed.
  • Pattern matching becomes really useful with classes, records and sealed types, which are covered later in this series. For now, remember that switch can do much more than compare constants.
Summary In this tutorial we first used arrow labels, which never fall through and can list several values per case. We then used switch as an expression which produces a value, including a throw for invalid input. Next we used yield to produce a value from a block of statements. We then saw that switch expressions must be exhaustive, and that the compiler checks enum switches for missing constants. Lastly, we handled null and matched on types with patterns and guards, available since Java 21. In the next tutorial, we move on to loops, starting with the for loopClick to read tutorial: Java for Loop Tutorial with Examples.

Java Switch Expressions: Arrow Labels and yield - JavaBrahman