How do I use enums and associate data or behavior in Kotlin?

In Kotlin, enum class is used for a fixed set of constants. Enums can have:

  • simple constants
  • constructor parameters / associated data
  • properties
  • functions
  • overridden behavior per constant
  • companion object utilities
  • implemented interfaces

Basic enum

enum class Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
}

Usage:

val direction = Direction.NORTH

when (direction) {
    Direction.NORTH -> println("Going up")
    Direction.SOUTH -> println("Going down")
    Direction.EAST -> println("Going right")
    Direction.WEST -> println("Going left")
}

Kotlin when is exhaustive for enums if you cover all constants, so you often do not need an else.


Enum with associated data

Enums can have a constructor.

enum class HttpStatus(val code: Int, val reason: String) {
    OK(200, "OK"),
    CREATED(201, "Created"),
    BAD_REQUEST(400, "Bad Request"),
    NOT_FOUND(404, "Not Found"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error")
}

Usage:

val status = HttpStatus.NOT_FOUND

println(status.code)   // 404
println(status.reason) // Not Found

Important syntax rule: if an enum has members after the constants, the constant list must end with a semicolon.

enum class HttpStatus(val code: Int) {
    OK(200),
    NOT_FOUND(404);

    fun isSuccess(): Boolean = code in 200..299
}

Enum with shared behavior

You can define functions inside the enum class.

enum class Planet(val mass: Double, val radius: Double) {
    EARTH(5.972e24, 6.371e6),
    MARS(6.39e23, 3.389e6),
    JUPITER(1.898e27, 6.9911e7);

    fun surfaceGravity(): Double {
        val gravitationalConstant = 6.67430e-11
        return gravitationalConstant * mass / (radius * radius)
    }
}

Usage:

println(Planet.EARTH.surfaceGravity())

Enum constants with different behavior

Each enum constant can override functions.

enum class Operation {
    PLUS {
        override fun apply(a: Int, b: Int): Int = a + b
    },
    MINUS {
        override fun apply(a: Int, b: Int): Int = a - b
    },
    TIMES {
        override fun apply(a: Int, b: Int): Int = a * b
    },
    DIVIDE {
        override fun apply(a: Int, b: Int): Int = a / b
    };

    abstract fun apply(a: Int, b: Int): Int
}

Usage:

val result = Operation.TIMES.apply(6, 7)
println(result) // 42

This pattern is useful when the enum represents a strategy or command.


Enum implementing an interface

Enums can implement interfaces.

interface Printable {
    fun label(): String
}

enum class Priority : Printable {
    LOW {
        override fun label(): String = "Low priority"
    },
    MEDIUM {
        override fun label(): String = "Medium priority"
    },
    HIGH {
        override fun label(): String = "High priority"
    }
}

Usage:

val priority: Printable = Priority.HIGH
println(priority.label())

You can also combine constructor data with an interface:

interface HasCode {
    val code: Int
}

enum class ErrorType(
    override val code: Int,
    val message: String
) : HasCode {
    VALIDATION(100, "Validation failed"),
    AUTHENTICATION(200, "Authentication failed"),
    NOT_FOUND(300, "Resource not found")
}

Companion object lookup helpers

A common pattern is looking up enum values by associated data.

enum class HttpStatus(val code: Int) {
    OK(200),
    CREATED(201),
    BAD_REQUEST(400),
    NOT_FOUND(404);

    companion object {
        fun fromCode(code: Int): HttpStatus? {
            return entries.find { it.code == code }
        }
    }
}

Usage:

val status = HttpStatus.fromCode(404)
println(status) // NOT_FOUND

In modern Kotlin, prefer entries over values():

HttpStatus.entries

instead of:

HttpStatus.values()

Built-in enum properties and functions

Every enum constant has:

val name: String
val ordinal: Int

Example:

enum class Color {
    RED,
    GREEN,
    BLUE
}

println(Color.RED.name)    // RED
println(Color.RED.ordinal) // 0

You can parse by name:

val color = enumValueOf<Color>("RED")
println(color) // RED

Or safely:

val color = Color.entries.find { it.name == "RED" }

Enum with custom display names

Avoid relying on name for user-facing text. Use a property instead.

enum class UserRole(val displayName: String) {
    ADMIN("Administrator"),
    EDITOR("Editor"),
    VIEWER("Viewer")
}

Usage:

println(UserRole.ADMIN.displayName) // Administrator

Enum with properties and computed values

enum class FileType(val extension: String) {
    TEXT("txt"),
    JSON("json"),
    CSV("csv");

    val mimeType: String
        get() = when (this) {
            TEXT -> "text/plain"
            JSON -> "application/json"
            CSV -> "text/csv"
        }
}

Usage:

println(FileType.JSON.extension) // json
println(FileType.JSON.mimeType)  // application/json

When to use enums

Use an enum when:

  • the set of values is fixed
  • each value is a singleton
  • you need exhaustive when handling
  • the values are known at compile time

Good examples:

enum class LogLevel {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR
}
enum class PaymentStatus {
    PENDING,
    PAID,
    FAILED,
    REFUNDED
}

When not to use enums

If each variant needs different state shapes, consider a sealed class or sealed interface.

For example, this is better as a sealed type:

sealed interface UiState {
    data object Loading : UiState
    data class Success(val data: String) : UiState
    data class Error(val message: String) : UiState
}

Because Success and Error need per-instance data, not fixed singleton enum constants.


Quick summary

enum class Status(val code: Int) {
    ACTIVE(1),
    DISABLED(2),
    DELETED(3);

    fun isVisible(): Boolean = this != DELETED

    companion object {
        fun fromCode(code: Int): Status? =
            entries.find { it.code == code }
    }
}

Usage:

val status = Status.fromCode(1)

if (status?.isVisible() == true) {
    println("Show item")
}

Kotlin enums are best for fixed named values, and they can carry data and behavior just like small classes.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.