benf.org :  other :  cfr :  How is switch-on-enum implemented? (Java 21 edition)

Switch on Enum - Java 21

The switch on enum strategy that java has always used, (which I document here) continues to be used for most enum types in java 21+

Why did the switch table exist?

Let's go back and look at the switch table (for more details, refer to this)

static void <clinit>()
{
    SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1 = new int[EnumTest1.values().length];
    try {
        SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1[EnumTest1.FOO.ordinal()] = 1;
    }
    catch (NoSuchFieldError unnamed_local_ex_0) {
        // empty catch block
    }
    try {
        SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1[EnumTest1.BAP.ordinal()] = 2;
    }
    catch (NoSuchFieldError unnamed_local_ex_0) {
        // empty catch block
    }
}

This is a clever way of making us insensitive to layout changes in EnumTest1. If we have code which hardcodes ordinal values, then an attacker (or more likely just a random dev!) can change the ordering of their enum, and completely break our code.

But what if trust isn't an issue?

Consider the case where we're switching on an inner enum

public class PrimitivePatterns9c {

    enum Color { RED, GREEN, BLUE }

    static String enumObjectSwitch(Color obj) {
        return switch (obj) {
            case Color.RED -> "red";
            case Color.GREEN -> "green";
            case Color.BLUE -> "blue";
        };
    }

    public static void main(String[] args) {
        System.out.println(enumObjectSwitch(Color.RED));
        System.out.println(enumObjectSwitch(Color.GREEN));
        System.out.println(enumObjectSwitch(Color.BLUE));
    }
}

In this case, there's no way that our enum is going to get recompiled without our outer class getting recompiled - we can trust the ordinal!

And, indeed, this is what happens if you turn off enum switch desugaring

java -cp target\classes org.benf.cfr.reader.Main c:\code\cfr\decompilation-test\test-data\output\java_25\org\benf\cfr\tests\PrimitivePatterns9c.class --decodeenumswitch false
/*
 * Decompiled with CFR 0.153-SNAPSHOT (0ebf016-dirty).
 */
package org.benf.cfr.tests;

public class PrimitivePatterns9c {
    static String enumObjectSwitch(Color obj) {
        return switch (obj.ordinal()) {
            default -> throw new MatchException(null, null);
            case 0 -> "red";
            case 1 -> "green";
            case 2 -> "blue";
        };
    }

    public static void main(String[] args) {
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.RED));
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.GREEN));
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.BLUE));
    }

    static enum Color {
        RED,
        GREEN,
        BLUE;

    }
}

If you use default options, the switch is sugared as you expect. (though I don't remove the matchException, as it's harmless.)

/*
 * Decompiled with CFR 0.153-SNAPSHOT (0ebf016-dirty).
 *
 * Could not load the following classes:
 *  java.lang.MatchException
 */
package org.benf.cfr.tests;

public class PrimitivePatterns9c {
    static String enumObjectSwitch(Color obj) {
        return switch (obj) {
            default -> throw new MatchException(null, null);
            case RED -> "red";
            case GREEN -> "green";
            case BLUE -> "blue";
        };
    }

    public static void main(String[] args) {
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.RED));
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.GREEN));
        System.out.println(PrimitivePatterns9c.enumObjectSwitch(Color.BLUE));
    }

    static enum Color {
        RED,
        GREEN,
        BLUE;

    }
}

Last updated 03/2026