Enums

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, released 2026-10-12) and every example marked as run was compiled and run with Scala 3.10.0 through Scala CLI on JDK 26 (checked on 2026-10-15); the one Scala 2 example was run with Scala 2.13.18. The enum construct has been part of Scala 3 since 3.0, so everything here also holds on the 3.9 LTS line and on the 3.3 LTS line; no feature on this page is experimental or in preview. Statements about 3.9 and 3.3 come from the documentation and release notes, not from a run.

An enum defines a type together with a closed list of values or shapes. It is Scala’s single construct for what other languages split into several: a Java enum for a fixed set of constants and a Kotlin sealed class for a closed set of shapes. A Scala 3 enum covers both, so a simple list of names, a list of constants with fields and a full sum type whose cases carry data all use the same keyword. The page covers the enum as a list of constants and the members that come with it. How an enum becomes an algebraic data type is the subject of Algebraic Data Types & GADTs. The examples use the trailhead scenario, a toolkit that plans hikes on a network of mountain trails. For the other closed-hierarchy construct see Case Classes and Inheritance: Abstract, Sealed, Open & Final; for matching on enum cases see Pattern Matching.

Simple enums

An enum with only singleton cases is a list of constants. All cases of a simple enum are objects, each case is a val of the enum’s companion with the enum as its type, and the compiler generates the members values, valueOf, fromOrdinal and ordinal for it.

//> using scala 3.10.0

package trailhead

enum Season:
  case Spring, Summer, Autumn, Winter

@main def run(): Unit =
  println(Season.values.mkString(", "))
  println(Season.Summer.ordinal)
  println(Season.valueOf("Autumn"))
  println(Season.fromOrdinal(3))
  println(Season.Winter.toString)
  println(Season.Spring.ordinal < Season.Autumn.ordinal)
  try Season.valueOf("Monsoon")
  catch case e: IllegalArgumentException => println(e.getMessage)
  try Season.fromOrdinal(9)
  catch case e: NoSuchElementException => println(e.getMessage)
Spring, Summer, Autumn, Winter
1
Autumn
Winter
Winter
true
enum trailhead.Season has no case with name: Monsoon
enum trailhead.Season has no case with ordinal: 9

The members of the generated companion are:

Member Meaning

values

An Array[Season] with all cases in declaration order. Use values.toList to hold it as an immutable list.

valueOf(name)

The case whose name is name. A name that matches no case throws IllegalArgumentException.

fromOrdinal(n)

The case at position n. An index that matches no case throws NoSuchElementException.

ordinal

An Int member of each case, its position in values, counting from 0.

Both lookups throw instead of returning an Option, as the output shows. For a lookup that can fail on user input, wrap the lookup in values.find(...) as the companion example below does, or in Try (see Either & Try). The lookup is also case sensitive: Season.valueOf("spring") throws, because the case is called Spring.

Facts to know about the cases of a simple enum:

  • The enum class is abstract (new Season fails with error E042, "Season is abstract; it cannot be instantiated"), so the cases listed in the body are the only instances.

  • The cases are compared by identity: Season.valueOf("Spring") eq Season.Spring is true, and so is ==.

  • An enum extending another enum is rejected by the compiler ("extending enums is prohibited").

  • An enum is not Comparable and not Ordered. Order by ordinal, as the last line of the example does, or provide an Ordering in the companion (see below).

  • Every enum extends the trait scala.reflect.Enum, which declares ordinal. The cases are also Product and Serializable (both isInstanceOf tests are true on 3.10.0), which is why a case with fields has a case-class-like toString.

Parameterized enums and members

An enum can take constructor parameters, and then each case passes arguments to the enum’s constructor with extends. The enum may also define fields and methods that are shared by all cases.

//> using scala 3.10.0

package trailhead

enum Surface(val label: String, val speedFactor: Double):
  case Paved extends Surface("paved", 1.0)
  case Gravel extends Surface("gravel", 0.8)
  case Scree extends Surface("scree", 0.4)

  def isLoose: Boolean = speedFactor < 0.5

object Surface:
  def fromLabel(label: String): Option[Surface] = values.find(_.label == label)

@main def run(): Unit =
  println(Surface.Scree.isLoose)
  println(Surface.Gravel.label)
  println(Surface.fromLabel("paved"))
  println(Surface.valueOf("Scree").speedFactor)
true
gravel
Some(Paved)
0.4

Points about this form:

  • The parameters of the enum are visible as members only when declared val (or var, private val, and so on), as for any class. Here label and speedFactor are val`s, so `Surface.Gravel.label works.

  • A case with no arguments that does not call a constructor (case Spring) is allowed only when the enum has no constructor parameters. In an enum with parameters every case must supply arguments, either with extends Surface(...) or because the case has its own parameter list (see the next section). The compiler stops at the first case that omits them:

    -- [E171] Type Error: k.scala:12:2
    12 |  case Plain
       |  ^
       |missing argument for parameter mass of constructor Planet in class Planet: (mass: Double): trailhead.Planet

    This message was produced by an enum Planet(val mass: Double) containing a bare case Plain.

  • Members such as isLoose are defined in the enum body after the cases. They are normal members and are inherited by every case.

  • An enum may extend a trait or a class, as in enum Surface(val label: String) extends Describable. The members of the trait are then available on every case, and a case may override toString or other members through the enum body.

  • The enum body can be given a companion object Surface, which can hold helpers such as fromLabel. The companion coexists with the compiler-generated members values, valueOf and fromOrdinal.

Companion objects, givens and derivation

Because an enum has a companion object, the companion is the natural home for given instances, parsing helpers and constants that belong to the type. The generated members and the user-written ones are merged into the same object.

//> using scala 3.10.0

package trailhead

enum Season derives CanEqual:
  case Spring, Summer, Autumn

object Season:
  given Ordering[Season] = Ordering.by(_.ordinal)
  def parse(text: String): Option[Season] = values.find(_.toString.equalsIgnoreCase(text))
  val warm: Set[Season] = Set(Summer)

enum Surface(val label: String) extends Describable:
  case Paved extends Surface("paved")
  case Scree extends Surface("scree")
  def describe: String = s"$label surface"
  override def toString: String = label.toUpperCase

trait Describable:
  def describe: String

@main def run(): Unit =
  println(List(Season.Autumn, Season.Spring).sorted)
  println(Season.parse("AUTUMN"))
  println(Season.warm)
  println(Surface.Scree.describe)
  println(Surface.values.toList)
List(Spring, Autumn)
Some(Autumn)
Set(Summer)
scree surface
List(PAVED, SCREE)

Three things are worth noting.

  • derives CanEqual makes the enum usable under -language:strictEquality (see Multiversal Equality). Enums derive other type classes in the same way (see Type Class Derivation).

  • Overriding toString in the enum body changes the printed form of every case, because the cases inherit it. The example prints PAVED for Surface.Paved, not Paved, and valueOf still takes the case name "Paved".

  • Inside the companion, the cases are in scope unqualified (Summer in warm), and values can be called without a prefix.

Enums with non-singleton cases

A case with a parameter list is a class, not an object, and the enum then behaves as a sum type: a closed set of alternatives where each alternative may carry its own data.

//> using scala 3.10.0

package trailhead

enum Difficulty:
  case Easy, Moderate, Hard
  case Alpine(grade: Int)

enum Pass(val name: String):
  case Day extends Pass("day")
  case Season extends Pass("season")
  case Custom(days: Int) extends Pass(s"custom-$days")

@main def run(): Unit =
  println(Difficulty.Easy)
  println(Difficulty.Alpine(4))
  println(Difficulty.Alpine(4) == Difficulty.Alpine(4))
  println(Difficulty.Alpine(4).copy(grade = 5))
  println(Pass.Custom(3).name)
  println(Pass.Day.ordinal)
  println(Pass.Custom(3).ordinal)
  println(Difficulty.Alpine(2).productPrefix)
Easy
Alpine(4)
true
Alpine(5)
custom-3
0
2
Alpine

A parameterized case is a case class: it gets structural equals and hashCode, toString, copy, an apply and an unapply for pattern matching. The ordinal numbering still counts every case in declaration order, so Pass.Custom(3).ordinal is 2.

The cost is that the enum no longer has a complete list of values. The compiler tells you so when you ask:

-- [E008] Not Found Error: b.scala:19:21
19 |  println(Difficulty.values.toList)
   |          ^^^^^^^^^^^^^^^^^
   |       value values is not a member of object trailhead.Difficulty.
   |       Although class Difficulty is an enum, it has non-singleton cases,
   |       meaning a values array is not defined

The same rule applies to valueOf: an enum with a non-singleton case has no values and no valueOf (the compiler reports "meaning a valueOf lookup method is not defined"). ordinal is still available on every value. fromOrdinal still exists: on a Mode with Walk, Run and Ski(fast: Boolean), Mode.fromOrdinal(1) returned Run and Mode.fromOrdinal(2) threw NoSuchElementException, because Ski is not a singleton. To list the singleton cases of a mixed enum, write the list by hand in the companion.

Further points on mixed enums:

  • A singleton case assigned to a val without an annotation is widened to the enum type. On 3.10.0 val w = Mode.Walk has the type Mode, and List(Mode.Walk, Mode.Ski(true)) has the element type Mode. A parameterized case such as Mode.Ski(true) is also typed as Mode.

  • The qualified name Mode.Walk.type exists, but a value typed as the enum is not a Mode.Walk.type. The compiler reports error E007 (type mismatch) with Found: Mode and Required: Mode.Walk.

  • Singleton cases print as their name, parameterized cases as Name(args).

  • An import import Mode.* brings every case into scope unqualified, and the enum cases can then be used as Walk or Ski(true). The case constructors can also be called without new, because the enum generates apply methods.

  • The cases form a closed set, so a match on the enum is checked for exhaustivity:

    //> using scala 3.10.0
    
    package trailhead
    
    enum Mode:
      case Walk, Run
      case Ski(fast: Boolean)
    
    def pace(m: Mode): Int = m match
      case Mode.Walk => 5
      case Mode.Run => 10
    
    @main def run(): Unit =
      println(pace(Mode.Walk))
    -- [E029] Pattern Match Exhaustivity Warning: e.scala:9:25
    9 |def pace(m: Mode): Int = m match
      |                         ^
      |                      match may not be exhaustive.
      |
      |                      It would fail on pattern case: trailhead.Mode.Ski(_)
      |
      | longer explanation available when compiling with `-explain`
    1 warning found
    5

    The program still compiles and prints 5, and the warning names the missing case. See Pattern Matching for the exhaustivity rules and @unchecked.

Restrictions and desugaring

The cases of an enum are the only subtypes it has. The following were checked on 3.10.0:

  • An enum is abstract: new Season fails with error E042, "Season is abstract; it cannot be instantiated".

  • An enum cannot be extended by another enum: the compiler reports "extending enums is prohibited".

  • In an enum with constructor parameters, every case must supply arguments (E171, shown above).

  • An enum that extends java.lang.Enum cannot have parameterized cases (shown below).

The reference also lists restrictions on what a case may refer to from its own body and type parameters; see the page "Scala 3 Reference: Enums" under restrictions-on-enum-cases in the references.

Internally the compiler turns an enum into an abstract class with a companion that holds one value per singleton case and one case class per parameterized case. Printing the run-time classes of enum Season: case Spring, Summer on 3.10.0, with Season.Summer.getClass.getName, Season.getClass.getName, classOf[Season].getInterfaces and the type of Season.values, gave, one per line:

trailhead.Season$$anon$1
trailhead.Season$
List(scala.reflect.Enum)
Season[]

The singleton case is therefore an anonymous subclass of the enum, so getClass.getName of a case is not stable and should not be compared. The declared methods of Season were fromOrdinal, productElementName, productElementNames, productIterator, productPrefix, valueOf and values. The exact desugaring is in the reference page below and can change between versions. Application code should depend on values, valueOf, fromOrdinal and ordinal only.

Java-compatible enums

A Scala enum can be turned into a real Java enum by having it extend java.lang.Enum. This is useful for libraries that take Enum types: EnumMap, EnumSet, annotations in Java code, and frameworks that use Class.getEnumConstants.

//> using scala 3.10.0

package trailhead

enum Level extends java.lang.Enum[Level]:
  case Low, Mid, High

@main def run(): Unit =
  println(Level.Mid.compareTo(Level.High))
  println(Level.Mid.name)
  println(Level.Mid.getDeclaringClass)
  println(Level.valueOf("Low"))
  println(Level.values.toList)
  val m = new java.util.EnumMap[Level, String](classOf[Level])
  m.put(Level.High, "h")
  m.put(Level.Low, "l")
  println(m)
  println(java.util.EnumSet.allOf(classOf[Level]))
  println(java.util.EnumSet.of(Level.High, Level.Low))
-1
Mid
class trailhead.Level
Low
List(Low, Mid, High)
{Low=l, High=h}
[Low, Mid, High]
[Low, High]

The enum then has the members of java.lang.Enum in addition to the Scala ones: name, compareTo (ordering by ordinal) and getDeclaringClass, as the output shows. The type argument is the enum itself (java.lang.Enum[Level]).

Rules:

  • All cases of an enum that extends java.lang.Enum must be singletons. A parameterized case is rejected:

    -- Error: m.scala:7:7
    7 |  case High(n: Int)
      |  ^^^^^^^^^^^^^^^^^
      |  parameterized case is not allowed in an enum that extends java.lang.Enum
  • The enum can still declare parameters and members, as the simple enums above, as long as the cases are singletons.

  • Java APIs that take an Enum type, such as EnumMap and EnumSet in the example, accept Level. The Java side of this interaction is covered in Calling Scala from Java and the reverse direction, Java enums used from Scala, in Using Java from Scala.

For a Java comparison, a Java enum can also hold fields and methods; a Java enum cannot have per-case data of a different shape, which is the difference that Scala’s mixed enums remove. See also the Java page Records & Sealed Classes and the Kotlin page Sealed Classes & Enums.

scala.Enumeration, the legacy form

Scala 2 had no enum keyword. The library class scala.Enumeration served instead, and it still exists in 2.13 and in Scala 3. Its values are objects of the inner type Value, created by calling Value inside an object that extends Enumeration.

//> using scala 2.13.18

object Surface extends Enumeration {
  type Surface = Value
  val Paved, Gravel, Scree = Value
  val Ice = Value(10, "Ice")
}

object Main extends App {
  println(Surface.values)
  println(Surface.Gravel.id)
  println(Surface.withName("Scree"))
  println(Surface.Ice.id)
  println(Surface(0))
  try Surface.withName("Mud") catch { case e: NoSuchElementException => println(e.getMessage) }
}
Surface.ValueSet(Paved, Gravel, Scree, Ice)
1
Scree
10
Paved
No value found for 'Mud'

An Enumeration also compiles on Scala 3.10.0, with a braceless body and an @main method, including a match over its values:

Surface.ValueSet(Paved, Gravel, Scree)
p
3

The output above is from an object with Paved, Gravel and Scree, a match that covers only Paved and Gravel, and a method taking a List[Surface.Value]. The 3.10.0 compiler printed no warning for the incomplete match.

Compared with the Scala 3 enum:

Aspect scala.Enumeration Scala 3 enum

Declaration

object X extends Enumeration with val A, B = Value

enum X: case A, B

Type of a value

X.Value, a path-dependent type; the alias type X = Value (used above) is the usual way to name it

The enum type X

Exhaustivity of match

Not checked; the compiler cannot see the values as a closed set (no warning in the run above)

Checked: E029 names the missing case

Fields and methods per value

Subclass Val by hand

Constructor parameters on the enum and extends X(...) on the case

Cases with data

No

Yes, parameterized cases

Lookup

withName, apply(id), values (a ValueSet)

valueOf, fromOrdinal, values (an Array)

Java enum compatibility

None

extends java.lang.Enum[E]

Use scala.Enumeration only for code that must compile on Scala 2 and Scala 3 with one source, or to keep an existing code base. In new Scala 3 code use enum. The migration is mechanical for simple lists of names: replace the object by an enum, drop the Value alias and replace withName by valueOf. Scala 2 code that wanted exhaustivity checking and data per case used sealed trait hierarchies with case objects; that pattern is still valid and is described in Algebraic Data Types & GADTs.

Choosing between enum, sealed trait and case class

  • A fixed list of constants, possibly with fields: simple or parameterized enum.

  • A closed set of alternatives where some carry data: a mixed enum or a sealed trait with case classes and case objects.

  • Alternatives that must be extended in other files or modules: a sealed trait cannot (it is limited to its file); use a non-sealed trait or an open class instead.

  • One record type: a case class (see Case Classes).

  • Interop with Java enum APIs (EnumMap, EnumSet, JPA, annotations): extends java.lang.Enum[E] with singleton cases only.

Scala 3.9 LTS versus 3.10

  • The enum construct, values, valueOf, fromOrdinal, ordinal, parameterized and Java-compatible enums, and the compiler messages quoted above come from Scala 3.0 and the 3.3 LTS line, and are the same on the 3.9 LTS and on 3.10.

  • The messages in this page were produced by 3.10.0. The exact wording and the error numbers (E007, E008, E029, E042, E171) may differ slightly between releases.

  • scala.Enumeration is still in the Scala API documentation and compiled unchanged on 3.10.0 in the run above.

Where the books are outdated

  • Books written for Scala 2 teach Enumeration or sealed trait plus case object as the only forms. Both still work, but the enum keyword replaces them in new code.

  • Books that show enums with the values method on a mixed enum are wrong: values and valueOf exist only when all cases are singletons, as the E008 error above shows.

  • The braces around the case lines are optional in Scala 3, and the examples use the indentation form.

  • Scala 2.13 books describe Enumeration#Value ids as the way to order values; use ordinal and an Ordering in the companion for an enum.