Most Kotlin developers write their first enum class in their first week with the language. At first it looks like a Java enum with shorter syntax.
Then you notice the constants can take constructor arguments and override methods. Each constant is a real object, not a label. That’s where most of the useful behavior comes from, and some of the annoying behavior too.
JetBrains maintains enum classes as part of the core Kotlin language specification. Android apps use them heavily, and you’ll find them in most backend and multiplatform code wherever someone needs a closed set of states.
One change is worth knowing up front. Kotlin’s own documentation describes entries as a performant replacement for values(), with lookups like get, contains, and indexOf running in constant time (kotlinlang.org, 2023). If you learned enums before 1.9, this is the habit to update.
What Is a Kotlin Enum Class?

You declare every possible instance inside the class body, by name. No code anywhere else can create another one.
So an enum class is a class type restricted to a fixed, named set of constants. Each constant is a full object, which means it can hold constructor arguments, properties, and its own method bodies.
What you get from this is type safety. Pass something that isn’t one of the declared constants and the compiler rejects it before the code runs. With plain strings or int flags, you’d find out at runtime, probably from a crash report.
Every enum class extends kotlin.Enum under the hood. That abstract base class has been in the standard library since Kotlin 1.0.
A regular class lets you create as many instances as you want. An enum class caps that number at compile time, so the compiler always has the complete list. Almost everything else in this article follows from that one fact, from exhaustive when checks to the persistence problems further down.
Compile-time guarantees like this are a big part of why people pick the Kotlin programming language over Java, and enum class is one of the cleanest examples.
Each constant is a singleton instance of the enum type. Constants can hold state and override behavior, and the enum as a whole can implement interfaces.
The smallest valid version looks like this: enum class Direction { NORTH, SOUTH, EAST, WEST }.
Four constants, no extra members. It compiles.
How Do You Declare a Kotlin Enum Class?

You start with enum class, then the name, then braces with the constants inside.
Constants are separated by commas and written in capitals by convention, same as in Java. Their order isn’t cosmetic. It sets each constant’s ordinal value, counting up from zero.
- Write
enum classfollowed by the class name. If you need a constructor, declare it right after the name, like any other class. - List every constant name, separated by commas.
- Put a semicolon after the last constant, but only if the body defines more members below it.
- Add properties and functions after that semicolon.
The semicolon is the step people forget. Java has the same rule, but Kotlin almost never asks for semicolons anywhere else, so it’s easy to leave off.
For the first few weeks, a kotlin cheat sheet open in another tab will save you from that exact compile error more than once.
On the JVM, kotlinc compiles the declaration into a final class extending java.lang.Enum. Each constant becomes a static field on that generated class. That’s why Java code in the same project reads the constants the same way Kotlin code does.
Enum Constants and Primary Constructor Parameters

Enum Constants as Fixed Instances
Each constant behaves like a singleton. Kotlin’s documentation is explicit about this: a constant’s type is always the enum class itself, never a private subtype, even when that constant has its own anonymous class body.
If you’ve used Kotlin’s singleton classes, it’s the same idea applied to a fixed list instead of a single object.
Constants are created once, when the class first loads. You never call a constructor yourself to get a new one.
Comparing two constants with == is always safe, since only one instance of each ever exists.
Passing Values Through the Primary Constructor
The primary constructor is declared once, on the class. Every constant then passes its own arguments to it at the point where it’s declared.
It accepts as many typed parameters as a regular class constructor would, and RED can pass completely different values than BLUE into the same parameter list. Leave a required argument off even one constant and the build fails, not just that line.
This is the same mechanism as Kotlin constructors anywhere else. It just runs once per constant instead of once per object you create.
Built-in Members of Enum Class: values(), valueOf(), entries, ordinal, and name

Every enum class comes with a handful of members you never have to write.
| Member | Returns | Key trait |
|---|---|---|
entries | Read-only list of constants | Stable since Kotlin 1.9.0, no new allocation per call |
values() | Array of constants | Allocates a new array on every call |
valueOf(name) | Single constant | Throws IllegalArgumentException on a bad name |
ordinal | Int | Position in the declaration, starting at 0 |
name | String | Exact declared identifier, case-sensitive |
values() and valueOf()
values() is the older option. It returns a new array with every constant, in declaration order.
In normal code, a fresh array per call doesn’t matter much. Inside a loop that runs thousands of times, it adds up. It’s the same allocation cost you’d get recreating any other Kotlin arrays on every call.
valueOf(name) goes the other way and turns a string into the matching constant.
Give it a name that doesn’t exist and it throws IllegalArgumentException right away. It won’t return null, which surprises people who expect it to act like a map lookup.
entries, ordinal, and name
entries is a read-only list that gets built once and reused. It isn’t rebuilt every time you access it.
ordinal is a constant’s position in the declaration, starting at 0 for the first one. name returns the exact identifier from the declaration, case-sensitive, with no connection to any toString override you’ve written.
Move a constant up or down and every ordinal after it shifts. That’s why storing ordinal anywhere permanent is risky. It’s an easy way to corrupt a database column without noticing for weeks.
The version history matters, because it decides which one you should reach for:
entriesshipped as an experimental feature in Kotlin 1.8.20 (kotlinlang.org, 2023)entriesbecame Stable in Kotlin 1.9.0, and is now the recommended default overvalues()(kotlinlang.org, 2023)values()allocates a new array on every call, a cost the Kotlin team names directly in its own documentation (kotlinlang.org, 2023)- ordinal starts at 0 for the first constant and increases by one for each constant listed after it
How Does Enum Class Implement an Interface?

Enum classes can implement one or more interfaces, like any other Kotlin class.
They cannot extend another class, though. Every enum already inherits from kotlin.Enum, which the compiler sets up for you, and Kotlin doesn’t allow a second parent class on top of that.
You’ll sometimes see two interfaces on one declaration. The usual example is an arithmetic enum implementing a binary operator interface plus an integer-specific version of it:
enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};
override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}The implementation can sit at the class level, where every constant shares it, or inside one constant’s own anonymous body. Most real code mixes the two.
In the example above, PLUS and TIMES each override apply differently, while applyAsInt is written once on the class and shared.
Per-Constant Bodies and Abstract Method Overrides

Put an abstract method in the enum body and every constant has to override it. Miss one and the whole file won’t compile, not just the constant you forgot.
Regular methods are more relaxed. Constants can override those selectively, or not at all.
Then there are properties declared inside a single constant’s anonymous body. These catch people off guard the first time.
You can’t read that kind of property from outside the constant, because the compile-time type of a constant is always the enum class itself, never its own anonymous subtype.
Try it anyway and the compiler reports an unresolved reference, even though the property is sitting right there in the file. Confusing error message, the first time you see it.
Companion Object Inside a Kotlin Enum Class
A companion object inside an enum class holds members that belong to the type, not to any one constant. You call them on the class name directly, the way you’d call a static method in Java.
It’s the same construct covered in our guide to the kotlin companion object, just declared inside an enum instead of a regular class.
The usual reason to add one is a custom lookup built on valueOf() or entries. Often it searches by something other than the name, like an integer code stored on each constant.
Kotlin 1.6.20 tightened a rule around this pattern.
Reaching into the companion object from inside an enum constant’s initializer now fails to compile, because the companion object isn’t initialized yet while the constants themselves are being built. Earlier Kotlin versions let this slip through inconsistently before the compiler closed the gap (Kotlin issue tracker, KT-49461).
If you run into it, pass the value through the constant’s constructor arguments instead.
Enum Class in When Expressions and Exhaustiveness

Cover every constant in a when expression and you need no else branch.
The compiler already has the full, closed list of constants, so it checks coverage itself.
Kotlin stabilized this for enum, sealed, and Boolean subjects in Kotlin 1.6.0, released November 2021 (kotlinlang.org).
That release made non-exhaustive when statements over those subjects a compiler warning. Starting in Kotlin 1.7.0, they became hard errors (kotlinlang.org).
In practice, if you add a constant and forget to update a when block somewhere, the build stops right at that block. Of everything enums give you, this is the part I’d miss most.
Several constants can share one branch when they map to the same result. You can still write an else branch, and it’s legal, but it quietly turns off the check for any constant added later. I leave it out for that reason.
When Should You Use Enum Class Instead of Sealed Class or Data Class?

Use an enum class when the set is small, fixed, and every value has the same shape. Days of the week. A list of status codes.
Sealed classes come in when each variant needs its own fields. A sealed subclass can also have many instances, each holding different data, which no enum constant can do.
If the values come from runtime data that nobody could list in advance, you want a data class.
| Construct | Instance count | Per-instance state | Typical use case |
|---|---|---|---|
| Enum class | Fixed, one per constant | Shared shape, different values | Status codes, days, directions |
| Sealed class | Fixed subtypes, many instances each | Distinct fields per subtype | Network response states, UI states |
| Data class | Unlimited, created at runtime | Full custom fields | API payloads, user records |
Kotlin’s own sealed class documentation states the core difference plainly: each enum constant exists as a single instance, while a sealed class subclass can have multiple instances (kotlinlang.org).
All three work in a when expression. Only enum and sealed classes get exhaustive checking, though, so with a data class you’ll usually need an else branch.
Network responses show where this decides things. SUCCESS and ERROR as enum constants can’t carry a payload or an error message. Success and Error as sealed subclasses can hold different data on every call.
Some teams write this down as a rule for screen state. Udacity’s engineering blog described modeling ViewModel states as a sealed class specifically to get compiler-enforced exhaustive checks across loading, success, and error cases.
Data classes are the better fit when the values come from outside the app. A Kotlin data class wrapping a user record or an API payload is the typical case, with fields that vary from one instance to the next.
What Are the Pros and Cons of Kotlin Enum Class?
Advantages of Enum Class
The big one is compile-time safety. An invalid value can’t exist, because the compiler only accepts the constants you declared.
- Exhaustive when checking catches missing branches before the app ships
- Constant names document themselves, which beats raw strings and magic numbers
- Per-constant behavior, without writing a separate class for every case
- Built-in members like entries, ordinal, name, and valueOf that you never have to write
That last one is easy to take for granted. Most other ways of representing a fixed set of values make you do the bookkeeping yourself.
Downsides to Weigh
You can’t extend an enum. Adding a constant means recompiling and redeploying every module that reads it.
If the enum is published, adding a constant is both a source and binary breaking change for any exhaustive when block written against the older version. Whoever depends on it finds out when their build breaks.
Persistence is the other weak spot. Ordinal shifts the moment someone reorders the constants, and that’s a known, recurring source of production bugs.
None of this is a reason to avoid enums. It’s a reason to think for a minute before declaring a set of values this way.
When Does a Kotlin Enum Class Not Work?
If your set of values isn’t fixed at compile time, an enum class is the wrong tool.
Options that come from a database table, a remote API response, or something a user typed have no fixed list for the compiler to check against.
It also breaks down in some less obvious cases:
- The options grow or shrink at runtime, driven by a server response or settings an admin can change
- Each variant needs a different shape, not just a different value (a network response that carries a payload on success and an error code on failure, for example)
- The list will keep growing across app releases, while rows already stored in a database still need to resolve correctly
- The code leans on reflection or dynamic class loading, where a closed type set gets in the way
The growing-list case ties back to the breaking-change risk from earlier. A published enum that later needs a new constant is a source and binary breaking change for anyone matching on it exhaustively.
Sealed classes and sealed interfaces handle the shape problem. Kotlin’s own documentation confirms their subclasses can each carry entirely different constructor parameters under one restricted hierarchy (kotlinlang.org).
Renaming a constant fails differently from reordering.
Every row written before the rename still holds the old name, and valueOf throws IllegalArgumentException on each one, even though the stored value itself never changed.
How Do You Use Enum Class in a Real Kotlin Project?

Production enum code rarely looks like the isolated examples in the docs. It usually sits inside a few recurring patterns.
Common Integration Points
You’ll see enums all over Android development, anywhere a screen or a response needs a small, fixed set of states.
- UI state flags in Jetpack Compose or XML-based views, like a loading, content, or empty state
- Room database columns that store a category or status per row
Retrofit response mapping is the other common spot. A fixed set of string or integer codes from an API gets converted into typed constants.
Room ships a default type converter for enums out of the box, so most projects never write one by hand (developer.android.com).
If you’ve written your own converter, it takes precedence over that default. That matters once a database column stops mapping cleanly onto a constant’s name.
Mistakes That Break Enum Class in Production
The first is adding a constant and not updating every when block. The new constant compiles fine on its own, then the build breaks everywhere an exhaustive when over that enum is missing the new branch.
Annoying, but it’s the good kind of failure. It gets caught before the app ships.
The worse one is calling valueOf on external data without handling the failure. Parse a status code from an API response or a user-supplied string with no try block around it, and one unexpected value crashes the app instead of falling back to something sensible.
The persistence problems from earlier (ordinal shifting on reorder, name lookups breaking on a rename) turn up in this same database and API-mapping code.
FAQ on What Is The Kotlin Enum Class
Is a Kotlin enum class the same thing as a Java enum?
Close, but not identical. Both compile to a class extending java.lang.Enum on the JVM, and Java’s enum has supported per-constant method bodies since Java 5.
Kotlin adds the entries property and a shorter constructor syntax with less boilerplate.
Does a Kotlin enum class support generics?
Not on the class itself. An enum class cannot declare its own type parameters, since each constant needs one fixed type at compile time.
Generic functions or properties inside the class body still work.
Can you serialize a Kotlin enum class with kotlinx.serialization?
Yes. Annotate the enum class with @Serializable and kotlinx.serialization encodes each constant by its declared name by default.
Putting @SerialName on a constant changes the name used in the wire format without touching the Kotlin identifier.
Can an enum class be nested inside another class?
Yes. It can be a member type of another class or sit inside a companion object, like any other nested class.
It can’t be local, though. Kotlin’s specification states that enum classes cannot be declared locally, so an enum class inside a function won’t compile.
What Should You Set Up First in What Is The Kotlin Enum Class?

Start with the constants and the constructor signature.
Before any database or API touches the type, pick a stable persistence key, meaning an explicit property that isn’t ordinal or name. Companion lookups and per-constant overrides can wait until that structure is settled.
enum class Status(val code: Int) {
ACTIVE(1),
PAUSED(2),
CLOSED(3);
companion object {
private val byCode = entries.associateBy { it.code }
fun fromCode(code: Int): Status? = byCode[code]
}
}It takes a bit of discipline upfront. A dedicated key property is one more field to maintain, and there’s no automatic mapping back from a stored value to a constant unless you write the lookup yourself.
Once the lookup exists, most projects turn the entries list into a keyed structure, like the associateBy call above. That step is covered in more detail in turning a Kotlin list into a map.
- pip is not recognized in VS Code: Why It Happens and How to Fix It - October 1, 2026
- MySQL Cheat Sheet - September 30, 2026
- How to Remove Duplicate Lines in Notepad++ (Sorted or Unsorted) - September 29, 2026



