Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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 def methods.
  • It cannot define concrete equals or hashCode methods.
  • It cannot contain nested or local classes, traits, or objects.
  • It cannot use @specialized type 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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.