Low Level Design

Null Object Pattern — Eliminate Null Checks in Java

How the Null Object pattern replaces null references with do-nothing objects that implement the expected interface, removing defensive null checks throughout your codebase.

August 26, 2026·8 min read

What is the Null Object Pattern?#

The Null Object is a behavioral design pattern that provides a default object with neutral (do-nothing) behavior to avoid null reference checks.

Instead of returning null, return an object that implements the expected interface but performs no operations (or returns safe defaults).

You reach for Null Object when:

  • You want to eliminate null checks scattered throughout your code
  • You need a default behavior for missing or optional objects
  • You want to prevent NullPointerException at runtime
  • You have optional dependencies that might not be configured

Real-World Analogy#

Think of a TV remote with dead batteries. Instead of having no remote (null), you have a dummy remote that looks and feels the same — you can still press buttons without the remote breaking, but nothing happens on the TV.

This prevents you from checking "do I have a working remote?" before every button press. The interface is identical; the behavior is neutral.


Class Diagram#


Violation Code — The Problem#

java
class User {
    private String name;
    private String email;

    User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    String getName()  { return name;  }
    String getEmail() { return email; }
}

class UserService {
    User findUserById(int id) {
        if (id == 1) return new User("Alice", "alice@example.com");
        return null; // ❌ null as sentinel
    }
}

// Client — null checks pollute business logic
public class Main {
    public static void main(String[] args) {
        UserService service = new UserService();

        User user = service.findUserById(999);

        if (user != null) {         // ❌ check 1
            System.out.println(user.getName());
        }
        if (user != null) {         // ❌ check 2
            System.out.println(user.getEmail());
        }
        if (user != null) {         // ❌ check 3
            processUser(user);
        }
    }

    static void processUser(User user) {
        if (user != null) {         // ❌ check 4 — can't trust callers
            System.out.println("Processing: " + user.getName());
        }
    }
}

Issues:

  1. Repetitive null checks — every method call needs guarding
  2. NullPointerException risk — miss one check and the app crashes
  3. Polluted business logic — null checks are mixed with real logic
  4. Maintenance burden — adding new methods means updating all null checks
  5. Inconsistent behavior — different parts of the code may handle null differently

Enhanced Code — Null Object Pattern#

java
// Common interface
public interface User {
    String getName();
    String getEmail();
    void displayInfo();
    boolean isNull();
}

// Real object — actual user with data
public class RealUser implements User {
    private final String name;
    private final String email;

    public RealUser(String name, String email) {
        this.name = name;
        this.email = email;
    }

    @Override public String getName()   { return name; }
    @Override public String getEmail()  { return email; }
    @Override public boolean isNull()   { return false; }

    @Override
    public void displayInfo() {
        System.out.println("User: " + name + " <" + email + ">");
    }
}

// Null object — safe do-nothing implementation
public class NullUser implements User {
    @Override public String getName()   { return "Guest"; }
    @Override public String getEmail()  { return "N/A"; }
    @Override public boolean isNull()   { return true; }

    @Override
    public void displayInfo() {
        System.out.println("No user found — using guest defaults.");
    }
}

// Service — never returns null
public class UserService {
    private static final User NULL_USER = new NullUser();

    public User findUserById(int id) {
        if (id == 1) return new RealUser("Alice", "alice@example.com");
        return NULL_USER; // ✅ always returns a valid object
    }
}

// Client — no null checks needed
public class Main {
    public static void main(String[] args) {
        UserService service = new UserService();

        User user = service.findUserById(999);
        user.displayInfo();           // ✅ safe — NullUser prints guest message
        System.out.println(user.getName()); // ✅ returns "Guest"

        processUser(user);            // ✅ no guard needed
    }

    static void processUser(User user) {
        System.out.println("Processing: " + user.getName()); // always safe ✅
    }
}

Common LLD Problems Using Null Object Pattern#

1. Logger System#

  • Null Object: NullLogger
  • Context: When no logger is configured, NullLogger silently ignores all log calls instead of throwing NullPointerException.

2. User Profile / Authentication#

  • Null Object: GuestUser, NullUser
  • Context: When no user is logged in, use GuestUser that returns safe defaults for name, role, and permissions.

3. Payment System#

  • Null Object: NoPaymentMethod
  • Context: When no payment method is configured, avoid runtime exceptions with a dummy that returns empty results.

4. Notification System#

  • Null Object: NoOpNotifier
  • Context: When notifications are disabled, use a null notifier to avoid conditional logic everywhere.

5. Discount Calculation#

  • Null Object: NoDiscount
  • Context: When no discount applies, NoDiscount.apply(price) returns the original price — no null check needed.

6. Analytics / Tracking#

  • Null Object: NullTracker
  • Context: In test environments or when tracking is opted out, use a no-op tracker.

Null Object vs Optional#

Both address null-safety, but differ in approach:

Null ObjectOptional (Java)
TypeFull polymorphic objectContainer/wrapper
UsageWorks anywhere the interface is usedExplicit .get() / .orElse() calls
BehaviorHas its own (neutral) behaviorJust signals presence/absence
Polymorphism✅ Full❌ None
Best forBehavioral defaultsSimple value absence

ReferencesLinks
Article ReferenceRefactoring Guru — Null Object