DEV Community

DevCorner2
DevCorner2

Posted on

🚫 Null Object Pattern – LLD with Java Implementation & UML

Avoid null checks in your code by substituting real objects with a benign default β€” the "null object" β€” that does nothing but conforms to expected behavior.


🧠 What is the Null Object Pattern?

The Null Object Pattern is a behavioral design pattern that provides a special object representing a "do nothing" or "no-op" version of another object. Instead of returning null, you return a concrete implementation that adheres to the interface but does nothing.


πŸ’‘ Why Use It?

Problem Solution
Excessive null checks Eliminate them with a default object
Risk of NullPointerException Avoid by returning safe defaults
Simplifies client code Always work with a valid object
Promotes polymorphism All subclasses behave consistently

🎯 Real-World Use Cases

Use Case Description
🧾 Logging If logger isn’t configured, use NullLogger
πŸ§ͺ Unit testing Return no-op mocks instead of null
πŸ§‘β€πŸ’Ό User profile Return GuestUser instead of null
🌐 API fallback Serve dummy handler if main is unavailable

🧱 LLD Components

Component Role
AbstractService (interface) Declares common behavior
RealService Concrete implementation
NullService Empty, no-op implementation
ServiceFactory Returns either a real or null object
Client Uses AbstractService without null checks

πŸ“ UML Class Diagram

         +---------------------+
         |  AbstractService    |  <<interface>>
         +---------------------+
         | +execute()          |
         +---------------------+
            β–²           β–²
            |           |
+----------------+   +----------------+
|   RealService   |   |   NullService   |
+----------------+   +----------------+
| +execute()     |   | +execute()     |
+----------------+   +----------------+

        +------------------+
        |  ServiceFactory   |
        +------------------+
        | +getService(id)   |
        +------------------+
Enter fullscreen mode Exit fullscreen mode

πŸ’» Java Code Implementation

βœ… AbstractService.java

public interface AbstractService {
    void execute();
}
Enter fullscreen mode Exit fullscreen mode

βœ… RealService.java

public class RealService implements AbstractService {
    private final String serviceName;

    public RealService(String serviceName) {
        this.serviceName = serviceName;
    }

    @Override
    public void execute() {
        System.out.println("βœ… Executing service: " + serviceName);
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… NullService.java

public class NullService implements AbstractService {
    @Override
    public void execute() {
        // Do nothing β€” silently ignore
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… ServiceFactory.java

public class ServiceFactory {
    private static final Map<String, AbstractService> registry = new HashMap<>();

    static {
        registry.put("email", new RealService("EmailService"));
        registry.put("sms", new RealService("SMSService"));
    }

    public static AbstractService getService(String key) {
        return registry.getOrDefault(key, new NullService());
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Client.java

public class Client {
    public static void main(String[] args) {
        AbstractService service1 = ServiceFactory.getService("email");
        service1.execute();  // βœ… Works

        AbstractService service2 = ServiceFactory.getService("push");
        service2.execute();  // πŸ‘» Null Object handles gracefully
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Output

βœ… Executing service: EmailService
// No output or error for 'push' service (handled by NullService)
Enter fullscreen mode Exit fullscreen mode

✨ Benefits

  • βœ… No null checks required
  • βœ… No NPEs at runtime
  • βœ… Improves code readability and client simplicity
  • βœ… Promotes interface-based development

⚠️ When NOT to Use

  • When the caller must know that an object is invalid/missing
  • When "doing nothing" has side effects (e.g., security, compliance)
  • If null has domain-specific semantics (e.g., "order not found")

πŸ” Summary

Feature Value
Pattern Type Behavioral
Primary Use Avoid null checks
Java Strategy Polymorphic no-op object
Best Practice Replace null with NullObject for safer behavior

βœ… Final Thoughts

The Null Object Pattern is a clean way to enforce safe defaults and reduce code complexity β€” especially in large-scale systems where defensive programming against null is messy and error-prone.

Top comments (0)