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 |
|---|---|
|
An |
|
The case whose name is |
|
The case at position |
|
An |
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 Seasonfails with errorE042, "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.Springis true, and so is==. -
An enum extending another enum is rejected by the compiler ("extending enums is prohibited").
-
An enum is not
Comparableand notOrdered. Order byordinal, as the last line of the example does, or provide anOrderingin the companion (see below). -
Every enum extends the trait
scala.reflect.Enum, which declaresordinal. The cases are alsoProductandSerializable(bothisInstanceOftests are true on 3.10.0), which is why a case with fields has a case-class-liketoString.
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(orvar,private val, and so on), as for any class. HerelabelandspeedFactorareval`s, so `Surface.Gravel.labelworks. -
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 withextends 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.PlanetThis message was produced by an enum
Planet(val mass: Double)containing a barecase Plain. -
Members such as
isLooseare 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 overridetoStringor other members through the enum body. -
The enum body can be given a companion
object Surface, which can hold helpers such asfromLabel. The companion coexists with the compiler-generated membersvalues,valueOfandfromOrdinal.
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 CanEqualmakes the enum usable under-language:strictEquality(see Multiversal Equality). Enums derive other type classes in the same way (see Type Class Derivation). -
Overriding
toStringin the enum body changes the printed form of every case, because the cases inherit it. The example printsPAVEDforSurface.Paved, notPaved, andvalueOfstill takes the case name"Paved". -
Inside the companion, the cases are in scope unqualified (
Summerinwarm), andvaluescan 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
valwithout an annotation is widened to the enum type. On 3.10.0val w = Mode.Walkhas the typeMode, andList(Mode.Walk, Mode.Ski(true))has the element typeMode. A parameterized case such asMode.Ski(true)is also typed asMode. -
The qualified name
Mode.Walk.typeexists, but a value typed as the enum is not aMode.Walk.type. The compiler reports errorE007(type mismatch) withFound: ModeandRequired: 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 asWalkorSki(true). The case constructors can also be called withoutnew, because the enum generatesapplymethods. -
The cases form a closed set, so a
matchon 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 5The 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 Seasonfails with errorE042, "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.Enumcannot 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.Enummust 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
Enumtype, such asEnumMapandEnumSetin the example, acceptLevel. 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'
Source: Scala API: scala.Enumeration
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 |
|
|
Type of a value |
|
The enum type |
Exhaustivity of |
Not checked; the compiler cannot see the values as a closed set (no warning in the run above) |
Checked: |
Fields and methods per value |
Subclass |
Constructor parameters on the enum and |
Cases with data |
No |
Yes, parameterized cases |
Lookup |
|
|
Java enum compatibility |
None |
|
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
enumor asealed traitwith case classes and case objects. -
Alternatives that must be extended in other files or modules: a
sealed traitcannot (it is limited to its file); use a non-sealedtraitor anopenclass 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
enumconstruct,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.Enumerationis 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
Enumerationorsealed traitpluscase objectas the only forms. Both still work, but theenumkeyword replaces them in new code. -
Books that show enums with the
valuesmethod on a mixed enum are wrong:valuesandvalueOfexist only when all cases are singletons, as the E008 error above shows. -
The braces around the
caselines are optional in Scala 3, and the examples use the indentation form. -
Scala 2.13 books describe
Enumeration#Valueids as the way to order values; useordinaland anOrderingin the companion for anenum.