iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Scala 3 enums and sealed algebraic data types (ADTs) let you describe a finite set of alternatives, then ask the compiler to flag matches that leave a known case unhandled. This can make selected invalid combinations unrepresentable in your domain model—but it does not validate arbitrary input or enforce every business rule. Also, an exhaustivity diagnostic is a warning unless your build is configured to treat warnings as errors.
What enums and ADTs let you express
An algebraic data type combines alternatives into one type. A sum type says a value is one alternative from a finite set; each alternative can carry its own data. Scala 3 enums provide concise syntax for this kind of model.
enum Color:
case Red, Green, Blue
Color has three named alternatives. A parameterized enum can attach data to a particular alternative:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →enum PaymentStatus:
case Pending
case Paid(receiptId: String)
case Declined(reason: String)
Unlike a broad record with a status string and nullable receipt and failure fields, this model associates each piece of data with the case where it applies. A Paid value carries a receipt ID; a Declined value carries a reason. The design makes combinations such as “pending with a failure reason” harder to represent through this type.
#1 Best Overall
This is the central benefit, not a claim that every invalid state has disappeared. The type does not prove that a receipt ID is genuine, that a decline reason meets a business policy, or that external data is well-formed.
How to handle every known case
Pattern matching lets an operation describe what to do for each alternative. For the examples below, assume Scala 3.3 or later syntax and a project compiler configuration that reports exhaustivity warnings; warning behavior and pattern-binding checks can vary with the compiler version and settings.
Rank #2
def describe(status: PaymentStatus): String =
status match
case PaymentStatus.Pending => "Awaiting payment"
case PaymentStatus.Paid(receiptId) => s"Paid: $receiptId"
case PaymentStatus.Declined(reason) => s"Declined: $reason"
If a later change adds another enum case, such as Refunded, the compiler can report that this match does not cover it. Scala’s E029 exhaustivity diagnostic describes this as a warning. To make that warning fail a build, configure the project’s compiler warning policy accordingly; do not assume that omission automatically produces a hard compilation error.
When each case has meaningful, distinct behavior, listing cases explicitly makes the code’s intent visible and gives future changes a chance to prompt review. A wildcard can handle all remaining cases:
Rank #3
status match
case PaymentStatus.Paid(receiptId) => processReceipt(receiptId)
case _ => recordOtherStatus()
That fallback is appropriate when all other alternatives truly share behavior. But it can also hide the need to decide what a newly added case should do, because the wildcard already matches it.
When to use an enum or a sealed trait
Choose based on the shape and intended openness of the model:
| Need | Suitable design | Why |
|---|---|---|
| A small fixed set of named values | Simple enum | Communicates the finite choices directly. |
| Alternatives with different associated fields | Parameterized enum | Keeps each alternative’s data with its case and supports constructor-based matching. |
| A closed family expressed as separate case classes or objects | Sealed trait with cases | Creates a closed set the compiler can use for exhaustivity analysis. |
| External code must add direct subtypes | An open hierarchy rather than a sealed base or enum | Sealed bases and enums are closed to external extension. |
A sealed-trait model can look like this:
sealed trait PaymentStatus
case object Pending extends PaymentStatus
final case class Paid(receiptId: String) extends PaymentStatus
final case class Declined(reason: String) extends PaymentStatus
Direct subtypes of a sealed trait must be declared in the same source file as the base trait. This restriction gives the compiler a closed family to analyze. If the model must permit direct subtypes in other files, a sealed hierarchy is not the right fit. Scala’s Pattern Matching tour explains the relationship between sealed bases and exhaustive matching.
What the compiler guarantee does—and does not—cover
It can reveal an omitted case in a closed model
Exhaustivity checking applies to the compiler’s understanding of the match scrutinee’s type and the patterns in that match. For a closed enum or sealed family, it can identify known alternatives that are not covered. Scala documents this as an exhaustivity warning, not an unconditional compile-time failure; the project’s warning settings determine whether the build stops.
Pattern-binding behavior has version-specific details. The Scala documentation discusses Scala 3.2 and -source future in its pattern-bindings reference. The runtimeChecked mechanism explicitly exempts an expression from certain static checks, including exhaustivity checking, so use it only when that trade-off is intended.
It does not validate data at system boundaries
A value read from a database, received over a network, or parsed from a file may not satisfy the assumptions of your trusted domain model. Validate and convert such input before constructing domain values. Enums and ADTs constrain values represented by their types; they do not automatically inspect or validate arbitrary external data.
It does not enforce every invariant
Types can prevent particular combinations from being expressed, but other rules may depend on runtime facts or relationships among values. For example, choosing a Paid(receiptId) case does not establish that the receipt exists. Put such checks in the appropriate validation or domain logic rather than treating a closed type as proof of every business condition.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Scala 3 enum details when they matter
Enums are language-level syntax, not a separate runtime magic type. Scala translates them into ordinary Scala constructs, including a sealed abstract class and companion object; cases are represented according to whether they are singleton or parameterized. Most application code can reason directly in terms of the enum declaration and its cases. If interoperability or case-declaration constraints matter, consult Scala’s enum and ADT translation reference.
Quick Recap
A practical design checklist
- Use a simple enum when the alternatives are just named values.
- Use parameterized cases when alternatives carry different relevant data.
- Choose a sealed trait when a closed family of explicit case classes or objects suits the code better.
- Keep sealed-trait children in the same source file as their base.
- Match explicitly when each case needs deliberate handling; use a wildcard only when shared fallback behavior is intentional.
- Pin the Scala compiler version and warning settings in your project, and decide whether exhaustivity warnings should fail the build.
- Validate external input before turning it into trusted domain values.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

