How do I model absence and presence clearly with Optional in domain models?

When using Optional in domain models, especially within the context of Java, it’s important to model the absence and presence of values in a way that conveys clear intent—making your code expressive, safe, and unambiguous. Below are the best practices to model absence and presence with Optional in domain models effectively:


When to Use Optional in Domain Models

  1. Expressing Optionality of Values
    Use Optional to indicate that a field or method may or may not have a value. This is particularly helpful for nullable fields like a middleName in a Person or an optionalDiscount in a pricing domain.

  2. Optional Return Values
    Use Optional in method return types where a value might not always be available. For instance, a repository method fetching a single record could return Optional<User> instead of null.

  3. Indicating Partial Data
    In domain models (e.g., DDD aggregates), Optional can signal that some pieces of the model might not be fully filled or initialized yet.


Best Practices for Modeling Optional

1. Avoid Optional in Constructors / Fields

Do not use Optional as a field type in your entities or value objects. Instead:

  • Use it for method return types and method arguments.
  • If an optional piece of data exists within a domain model, you can use default values or null-checks in fields.

❌ Avoid this:

public class Customer {
   private Optional<String> middleName = Optional.empty();
}

✔️ Prefer this:

public class Customer {
   private final String middleName; // nullable internally

   public Customer(String middleName) {
       this.middleName = middleName; // Can be null
   }

   public Optional<String> getMiddleName() {
       return Optional.ofNullable(middleName); // Provide Optional as accessor
   }
}

2. Use Optional Only for Return Values

Optional is designed to be used in method return types to avoid returning null. By doing so, the caller must explicitly handle the presence or absence of a result, which makes the intent clearer. For example:

public class CustomerRepository {
    public Optional<Customer> findById(String id) {
        // Return Optional to avoid null checks
        return Optional.empty(); // or Optional.of(customer)
    }
}

3. Avoid Optional in Method Parameters

Using Optional as a method parameter is usually discouraged, as it introduces unnecessary complexity. Instead, rely on overloading, separate methods, or nullable parameters:

❌ Avoid this:

public void updateCustomer(Optional<Address> address) {
    if (address.isPresent()) {
        // Logic when address is present
    }
}

✔️ Use this:

public void updateCustomer(Address address) {
    if (address != null) {
        // Logic when address is provided
    }
}

4. Do Not Serialize Fields with Optional

If your domain models are serialized (e.g., with JSON, XML, etc.), avoid including Optional as part of the serialized structure. Serialization libraries do not typically handle Optional well (or consistently across tools).

Instead, model an absent value using nullable fields, and use Optional only for internal application logic or method contracts.


Example: Domain Model with Optional for Absence & Presence

Use Case: Online Store – Customer Preferences

You want to model a Customer and handle their optional second email or preferences clearly.

package org.kodejava.util;

import java.util.Optional;

public class Customer {
   private final String id;
   private final String name;
   private final String email;
   private final String secondEmail; // Optional here is unnecessary for field

   public Customer(String id, String name, String email, String secondEmail) {
      this.id = id;
      this.name = name;
      this.email = email;
      this.secondEmail = secondEmail;
   }

   public String getId() {
      return id;
   }

   public String getName() {
      return name;
   }

   public String getEmail() {
      return email;
   }

   // Use Optional as a getter to convey optionality
   public Optional<String> getSecondEmail() {
      return Optional.ofNullable(secondEmail);
   }

   // Example: Searching for a customer preference (optional behavior)
   public Optional<String> findPreferenceByKey(String key) {
      // Fetched preferences could return an Optional value
      if ("newsletter".equals(key)) {
         return Optional.of("subscribed");
      }
      return Optional.empty();
   }
}

How to Use It

Customer customer = new Customer("1", "John Doe", "[email protected]", null);

// Accessing optional data
customer.getSecondEmail()
        .ifPresentOrElse(
                email -> System.out.println("Second email: " + email),
                () -> System.out.println("No second email provided.")
        );

// Using optional preferences
Optional<String> newsletterPref = customer.findPreferenceByKey("newsletter");
newsletterPref.ifPresent(pref -> System.out.println("Preferences: " + pref));

Summary Guidelines

  1. Use Optional in return types of methods to clearly represent absence/presence.
  2. Avoid Optional as a field type; use it in accessors/getters instead.
  3. Don’t use null to represent absence in methods returning Optional.
  4. Avoid using Optional in method arguments; use overloads or alternative patterns.
  5. Do not include Optional types in serialized domain models.

By adhering to these practices, you make your domain model more expressive, avoid unexpected nulls, and maintain a clean, clear separation between absence/presence of a value and the core logic of your application.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.