| benf.org : other : cfr : How is switch-on-enum implemented? (Java 21 edition) |
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+
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.
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 |