A Scala value class is a lightweight wrapper around one value, declared by extending AnyVal. It gives a value a distinct type—such as Meter instead of Double—while allowing the compiler to use the underlying value directly in eligible cases. That can avoid creating a wrapper object, but it is not an unconditional allocation-free guarantee.
How a Scala value class works
User-defined value classes were introduced in Scala 2.10.0. A basic declaration looks like this:
class Meter(val value: Double) extends AnyVal
In source code, Meter is a type distinct from Double, so APIs can distinguish a distance from an arbitrary number. The wrapped Double is the class’s underlying value. In eligible uses, the compiler can represent a Meter as that primitive rather than allocate a separate wrapper.
For example, adding two distances can be written using Meter values while the computation operates on their underlying primitive values. The official Scala 2 guide to value classes and universal traits describes this benefit, while also documenting contexts where an instance must be created.
When a value class may allocate
The JVM does not have a native representation for Scala value classes. The compiler can avoid a wrapper in certain statically typed uses, but it must provide an actual instance when code needs to treat the value class as an object or another type. The Scala guide identifies these common allocation cases:
- Used as another type: For example, a value class used through a universal trait or as a generic type argument needs an instance. A generic call such as
identity[T](Meter(5.0))is a representative case. - Stored in an array: An array of a value class contains instances rather than a flat array of the underlying primitive.
- Used in a runtime type test: Pattern matching or another runtime type test on the value class requires an instance.
Therefore, “value class” describes a compiler-supported wrapper abstraction, not a promise that every use has zero allocation. Whether the wrapper is erased depends on how the value is used.
Rank #2
What you can declare in a value class
Value classes have tighter rules than ordinary classes. Under the Scala 2 guide, a value class must have exactly one primary-constructor value parameter. From Scala 2.11 onward, that parameter must be non-public. It may define methods, but cannot carry ordinary extra state.
- It cannot be extended or subclassed.
- It cannot have additional fields; its members are limited to
defmethods. - It cannot define concrete
equalsorhashCodemethods. - It cannot contain nested or local classes, traits, or objects.
- It cannot use
@specializedtype parameters, and its underlying parameter cannot itself be a user-defined value class. - It must be declared at the top level or as a member of a statically accessible object.
A value class may extend a universal trait, but calling a trait method can require allocation. See the Scala guide for the complete Scala 2 restrictions and examples.
Recommended Free Tools
Value classes and extension methods
In Scala 2, an implicit value class was also a way to add extension-style syntax. For example, a RichInt wrapper could provide a toHexString method for Int. In ordinary eligible calls, the compiler can route the call through an extension method without constructing a RichInt object.
Scala 3 has dedicated extension-method syntax, so a value class is not needed just to add a method to an existing type. Its syntax is built around extension, as shown in the Scala 3 Book’s extension methods guide.
Rank #4
Scala 2 value classes versus Scala 3 opaque types
Scala 3 retains value classes for compatibility, but the Scala documentation recommends opaque types for a similar type-abstraction goal. An opaque type hides its underlying representation outside the scope where it is defined. Unlike a value class, it is an alias with controlled visibility rather than a class that wraps one constructor value.
| Question | Scala 2 value class | Scala 3 opaque type or extension method |
|---|---|---|
| Type abstraction | An AnyVal subclass wrapping one value |
An opaque alias, such as opaque type UserId = Long, whose representation is hidden outside its defining scope |
| Adding methods | Often an implicit class combined with AnyVal |
Use extension (x: T) syntax for extension methods |
| Runtime representation | The compiler may avoid wrapper allocation in eligible uses; generic, array, and runtime type-test contexts can require instances | The Scala 3 Book describes opaque types as providing abstraction without overhead in its illustrated case; representation and use still matter |
| Version availability | Introduced in Scala 2.10.0 and retained in Scala 3 for compatibility | Opaque types and direct extension-method syntax are Scala 3 features |
The Scala 3 Book’s opaque types guide explains its claim that opaque aliases provide abstraction without overhead in the context of the examples there; it should not be read as a universal benchmark or performance guarantee for every program.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Which should you use?
- Maintaining Scala 2 code: A value class can provide a distinct domain type with a simple underlying representation, provided its declaration constraints and allocation-triggering uses fit your design.
- Writing Scala 3 code: Consider an opaque type when you want a distinct type while hiding a representation such as
Long. Use extension methods directly when the goal is simply to add convenient operations. - Making performance decisions: Do not infer allocation behavior from the declaration alone. Check the actual use context and compiler version, especially for generics, arrays, runtime tests, or trait-based calls.
The Scala AnyVal API reference documents the current API type; the Scala guide remains the clearest source for the Scala 2 value-class examples and their restrictions.
Quick Recap
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.

