The Value of Value Objects 💊
Value objects are identified by the immutable value they carry, are always valid, can have logic, and portray domain meaning. Let’s explore those characteristics.

Carry a value
Value objects represent high-level (domain) concepts. They can be categorized as:
- Mathematical values: represent coordinates on a continuous scale. e.g. Percentage, Money, Temperature, Point, Color, Date, Date Range, and Distance.
- Identifiers: discrete values used to identify some entity. e.g. Email, Locale, Currency, Telephone number, IP address, URL, File path, and Product identifier (enums behave as identifiers but they’re limited to a hardcoded set of values).
Usually, a value object holds a single primitive value [🚀], but it can hold more. For example, an RGBA color holds 4 integers; a 3D point holds three numbers.
// The literal value is kept inside the value object
data class Email(val value: String)
fun main() {
val newEmail = Email("[email protected]")
assert("[email protected]" == newEmail.value)
}Value objects don’t make sense on their own. They exist in the context of an entity. You might create a new one to run a findByEmail for example, but other than that, they exist only to serve as entity data.
Same value? Always equal
When you take a pill from a bottle, it doesn’t matter which one you pick. A value object's state matches its identity. Two value objects carrying the same value are identical; both represent the same concept. When compared, they are always equal. Email(“[email protected]”) will always equal Email(“[email protected]”) regardless of their references [🚀]:
// Value objects with the same value are always equal
// (in some languages you may have to define toEquals)
data class Email(val email: String)
fun main() {
assert(Email("[email protected]").equals(Email("[email protected]")))
assert(Email("[email protected]") == Email("[email protected]"))
}Objects that are equal due to the value of their properties […] are called value objects. Value Object (Martin Fowler)
For most purposes, value objects can be seen and treated as primitive values because they behave the same way. They’re all value types in opposition to reference types (you pass by value rather than by reference).
Immutable
While an entity has a long-lived identity granted by its properties, a value object’s identity is solely given by the piece of data it carries. Since it does not evolve, it means it does not have a life of its own. Immutability is what makes value objects what they are.
Immutability is an essential property in functional programming but it should be pursued in objected-oriented as well. It pays to make the objects immutable, namely to prevent the aliasing bug and guarantee thread safety. For example, once an Email is created, you can’t change its value [🚀]:
// Compilation error when reassigning the value of a value object
data class Email(val value: String)
fun main() {
Email("[email protected]").value = "[email protected]" // 🚨 Val cannot be reassigned
}Immutable values can freely be passed around. There is no need to create copies. Even concurrent access is safe without modifications. Testing is simplified to creating different instances via the constructor and then asserting the returned results of properties and methods. Immutable objects also encourage side-effect free functions and those come with their own set of advantages. Kotlin and Domain-Driven Design — Value Objects
Abstract a type
Value objects are the cure to an anti-pattern called primitive obsession — using primitive values (i.e. naked primitives) everywhere.
Value objects enclose their real type as an implementation detail. For example, you might be changing the representation of a price to include the currency, or you may be changing the identifier of the User entity from integer to string — you wouldn’t have to change entities that use it or declarations like this:
findById(userId: UserIdentifier) // all the calls are kept the sameBy using a value object we can ensure that clients won’t rely on raw data, not on the particular primitive type of that data. Advanced Web Application Architecture
For functions with long lists of parameters, most of them the same type, value objects can be of great help, in terms of documentation and error-checking.
Intent revealing
With a value object, you create a new semantic level that lets you express much more clearly and closer to the domain of the app.
Being abstract is something profoundly different from being vague (…) The purpose of abstraction is not to be vague but to create a new semantic level in which one can be absolutely precise. Edsger W. Dijkstra
Value objects are domain-specific types that enrich your type system, promoting domain terminology. They’re one of the building blocks of domain-driven design, playing an essential role in the ubiquitous language. Primitive types don’t convey any meaningful domain knowledge.
They make the domain explicit, e.g. by using Money as a wrapper instead of just two fields of type BigDecimal and String […]. Readers of the code will understand it’s about money and maintainers will be unable to pass something not representing money. In addition, the wrapper can contain validations and thus additional domain knowledge and also increase security. Kotlin and Domain-Driven Design — Value Objects
The self-validation and logic that value objects have within them are also a source of contextually placed know-how about the domain.
Value objects help APIs and entities reveal their intent, acting as code self-documentation.
In entities
Consider the Customer entity where most of its properties can be value objects [🚀]:
import java.util.Date
// Customer entity without value objects
data class CustomerOld(
val id: Int,
val personalEmail: String,
val salutation: String,
val firstName: String,
val lastName: String,
val language: String,
val createdAt: Long
)
🆚
// Customer entity with value objects
data class Email(val email: String)
enum class Salutation { MR, MS }
data class Name(val value: String) {
val first get() = value.split(" ").first()
val last get() = value.split(" ").last()
}
data class Locale(val value: String)
data class Customer(
val id: Customer.Id,
val personalEmail: Email,
val salutation: Salutation,
val name: Name,
val language: Locale,
val createdAt: Date,
) {
data class Id(val value: Int)
}They’re even more important in the functional programming paradigm because you’re mostly passing lambdas around. It also reveals intent when resorting to generics:
val articleMappings: Map<String, String>
🆚
val articleMappings: Map<ArticleCategory, Region>In APIs
Compare these signatures:
fun notifyClient(recipient: String)
🆚
fun notifyClient(recipient: Email)The parameter of the first signature doesn’t tell it’s expecting an email, unlike the second signature.

Compare these example invocations — the second doesn’t allow any mistake regarding the order of the arguments:
sendSms("test123", "text")
🆚
sendSms(UserId("test123"), "text")





