skip to content

Explain exactly what the Kotlin compiler generates for an enum that has per-constant anonymous-class bodies, and why a semicolon after the constant list is mandatory.

level: middleimportance: should knowfreq 35%

answer

  1. body constant -> synthetic subclass
  2. javaClass differs per body constant
  3. enum is abstract if any member abstract
  4. ; required once any member follows constants
  5. more constants = more generated classes

basics

~10 s

Each constant with its own body becomes a separate hidden subclass of the enum. The semicolon is needed to mark where the constant list ends and member declarations begin.

solid answer

~40 s

When a constant has an anonymous-class body, the compiler emits a synthetic subclass of the enum type for that constant (e.g. `Operation$PLUS`), and the `PLUS` field holds an instance of that subclass. Constants without a body are instances of the enum class itself. Because these subclasses are anonymous and final, you cannot reference them by name or subclass them further. The terminating semicolon is mandatory whenever the enum body contains anything beyond the constant list — abstract members, regular methods, properties, companion objects — because Kotlin needs an unambiguous boundary between the comma-separated constant list and subsequent member declarations. Each per-constant override participates in normal virtual dispatch, so the synthetic subclass's method is invoked.

code

kotlin · 11 lines
kotlin
enum class Op {
    ADD { override fun apply(a: Int, b: Int) = a + b },
    SUB { override fun apply(a: Int, b: Int) = a - b };
    abstract fun apply(a: Int, b: Int): Int
}

fun main() {
    // Each bodied constant is its own synthetic subclass
    println(Op.ADD.javaClass == Op.SUB.javaClass) // false
    println(Op.ADD.name)                          // ADD (still normal)
}

go deeper

for a junior

Knows the semicolon is needed and that each constant can override the abstract member.

for a middle

Explains synthetic per-constant subclasses, the abstract enum type, and the exact semicolon rule.

for a senior

Reasons about reflection/javaClass differences and bytecode/class-count cost at scale.

for a principal

Weighs generated-class bloat and method-count limits when deciding per-constant bodies vs a single dispatch method on large enums.

## What gets generated A Kotlin `enum class` compiles to a JVM class extending `java.lang.Enum`. Each constant is a `public static final` field holding one instance. When a constant has a **per-constant body** (an anonymous-class body), that constant is *not* a plain instance of the enum class. Instead the compiler generates a **synthetic anonymous subclass** of the enum and instantiates it for that field. ```kotlin enum class Op { ADD { override fun apply(a: Int, b: Int) = a + b }, // -> subclass Op$1 (synthetic) SUB { override fun apply(a: Int, b: Int) = a - b }; // -> subclass Op$2 (synthetic) abstract fun apply(a: Int, b: Int): Int } ``` Decompiled, you roughly get an abstract `Op` class with `abstract apply`, plus inner synthetic subclasses (`Op$1`, `Op$2`) each overriding `apply`, and the static fields `ADD`/`SUB` pointing at those subclass instances. ## Consequences of the codegen - **More classes**: N constants with bodies = N extra synthetic classes. For huge enums this is real bytecode/method-count cost. - **Enum type is abstract**: if *any* member is abstract, the enum class itself is effectively abstract; only the synthetic subclasses are concrete. - **`javaClass` differs per constant**: `Op.ADD.javaClass != Op.SUB.javaClass`. Reflection sees distinct classes. `Op.ADD::class` reflects the synthetic subclass, while `Op.ADD.ordinal`/`name` still work normally. - **Cannot be subclassed by you**: the synthetic classes are final and inaccessible. ## The semicolon rule The enum constant list is comma-separated. As soon as you add **any** member after it — an abstract function, a normal method, a property, an `init` block, a `companion object` — Kotlin requires a `;` to terminate the constant list so the parser knows the constants ended and declarations begin. ```kotlin enum class Direction { NORTH, SOUTH; fun opposite() = ... } // ; required enum class Color { RED, GREEN, BLUE } // no member -> no ; needed ``` ## Dispatch reminder Calling `Op.ADD.apply(...)` resolves via the runtime type — the synthetic `Op$1` — so its override runs. This is ordinary polymorphism over a closed set.

  • Does a constant WITHOUT a body get its own synthetic subclass?
    No. Bodyless constants are direct instances of the enum class; only constants with an anonymous body get a synthetic subclass.
  • Why might Op.ADD::class surprise someone using reflection?
    It reflects the synthetic subclass, not Op itself, so per-constant javaClass differs — code comparing classes across constants can break.

saying these in an interview costs you the question

  • Claiming all constants share the same class even with bodies
  • Saying the semicolon is optional when members follow
  • Ignoring the extra generated classes / method-count cost
  • Thinking you can name or extend the synthetic subclass
  • Confusing per-constant body with a companion object

context