Full Stack • Java • System Design • Cloud • AI Engineering

Decorator Design Pattern in Java

Learn Decorator Design Pattern in Java with real-world examples, Spring Security, Java IO, banking use cases, UML diagrams, code examples, benefits, limitations, and interview questions.

What You Will Learn

In this article, you'll learn:

  • What is Decorator Pattern?
  • Why do we need it?
  • Problems it solves
  • Real-world examples
  • UML diagrams
  • Java implementation
  • Spring Framework examples
  • Banking and Insurance use cases
  • Java IO examples
  • Benefits and limitations
  • Interview questions

Introduction

In enterprise applications, requirements evolve continuously.

Today you may have:

Order Service

Tomorrow the business asks for:

Order Service
+ Logging
+ Security
+ Auditing
+ Metrics
+ Monitoring
+ Tracing

A naive solution would create many subclasses:

OrderService

LoggedOrderService

SecuredOrderService

AuditedOrderService

LoggedSecuredOrderService

LoggedSecuredAuditedOrderService

As features increase, the number of classes grows exponentially.

This problem is called:

Class Explosion

Decorator Pattern solves this problem elegantly.


What is Decorator Pattern?

Decorator is a Structural Design Pattern that allows behavior to be added dynamically to an object without modifying its source code.

Instead of changing the original object:

Modify Existing Class ❌

We wrap the object:

Wrap Existing Object ✅

and add additional behavior.


Purpose of Decorator Pattern

The main purpose is:

Add new functionality to an object dynamically at runtime.

Decorator follows:

Open For Extension

Closed For Modification

which is one of the core SOLID principles.


Real World Analogy

Imagine ordering coffee.

Base Coffee:

Simple Coffee

Optional add-ons:

Milk
Sugar
Chocolate
Caramel

Different customers choose different combinations.

Instead of creating:

MilkCoffee

SugarCoffee

MilkSugarCoffee

ChocolateMilkSugarCoffee

We dynamically add toppings.

Decorator works exactly the same way.


Problem Without Decorator

flowchart TD

A[Coffee]

A --> B[Milk Coffee]

A --> C[Sugar Coffee]

A --> D[Chocolate Coffee]

B --> E[Milk Sugar Coffee]

B --> F[Milk Chocolate Coffee]

C --> G[Sugar Chocolate Coffee]

As features increase:

Classes Increase Rapidly

Solution With Decorator

flowchart LR

A[Simple Coffee]

A --> B[Milk Decorator]

B --> C[Sugar Decorator]

C --> D[Chocolate Decorator]

Each decorator adds behavior independently.


Key Components

Component

Common interface.

Example:

Coffee

Concrete Component

Original implementation.

Example:

SimpleCoffee

Decorator

Base wrapper.

Example:

CoffeeDecorator

Concrete Decorators

Add specific behavior.

Examples:

MilkDecorator

SugarDecorator

ChocolateDecorator

UML Diagram

classDiagram

class Coffee {
    <<interface>>
    +getDescription()
    +getCost()
}

class SimpleCoffee

class CoffeeDecorator

class MilkDecorator

class SugarDecorator

class ChocolateDecorator

Coffee <|.. SimpleCoffee

Coffee <|.. CoffeeDecorator

CoffeeDecorator <|-- MilkDecorator

CoffeeDecorator <|-- SugarDecorator

CoffeeDecorator <|-- ChocolateDecorator

CoffeeDecorator --> Coffee

Coffee Example

Step 1: Component Interface

public interface Coffee {

    String getDescription();

    double getCost();
}

Step 2: Concrete Component

public class SimpleCoffee implements Coffee {

    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double getCost() {
        return 5.0;
    }
}

Step 3: Base Decorator

public abstract class CoffeeDecorator
        implements Coffee {

    protected Coffee coffee;

    public CoffeeDecorator(
            Coffee coffee) {

        this.coffee = coffee;
    }
}

Step 4: Milk Decorator

public class MilkDecorator
        extends CoffeeDecorator {

    public MilkDecorator(
            Coffee coffee) {

        super(coffee);
    }

    @Override
    public String getDescription() {

        return coffee.getDescription()
                + ", Milk";
    }

    @Override
    public double getCost() {

        return coffee.getCost() + 2;
    }
}

Step 5: Sugar Decorator

public class SugarDecorator
        extends CoffeeDecorator {

    public SugarDecorator(
            Coffee coffee) {

        super(coffee);
    }

    @Override
    public String getDescription() {

        return coffee.getDescription()
                + ", Sugar";
    }

    @Override
    public double getCost() {

        return coffee.getCost() + 1;
    }
}

Client Code

public class DecoratorDemo {

    public static void main(String[] args) {

        Coffee coffee =
                new SimpleCoffee();

        coffee =
                new MilkDecorator(coffee);

        coffee =
                new SugarDecorator(coffee);

        System.out.println(
                coffee.getDescription());

        System.out.println(
                coffee.getCost());
    }
}

Output

Simple Coffee, Milk, Sugar

8.0

Execution Flow

sequenceDiagram

participant Client

participant Sugar

participant Milk

participant Coffee

Client->>Sugar:getCost()

Sugar->>Milk:getCost()

Milk->>Coffee:getCost()

Coffee-->>Milk:5

Milk-->>Sugar:7

Sugar-->>Client:8

Banking Example

Money Transfer Processing

Base Functionality:

Transfer Money

Additional Features:

Fraud Detection

Audit Logging

Encryption

Compliance Validation

Decorator allows independent addition of each feature.


Banking Architecture

flowchart LR

A[Money Transfer]

A --> B[Fraud Decorator]

B --> C[Audit Decorator]

C --> D[Encryption Decorator]

D --> E[Compliance Decorator]

Insurance Example

Claim Processing

Core Functionality:

Claim Validation

Additional Processing:

Fraud Analysis

Risk Assessment

Audit Tracking

Notification

Each feature can be implemented as a decorator.


Spring Security Example

One of the best real-world examples.

Incoming Request:

HTTP Request

Processing Chain:

flowchart LR

A[Request]

A --> B[Authentication Filter]

B --> C[Authorization Filter]

C --> D[CSRF Filter]

D --> E[Exception Filter]

E --> F[Application]

Every filter decorates the request.


Servlet Wrapper Example

Java Servlet API heavily uses Decorator.

public class CustomRequestWrapper
        extends HttpServletRequestWrapper {

    public CustomRequestWrapper(
            HttpServletRequest request) {

        super(request);
    }

    @Override
    public String getHeader(String name) {

        return "Modified Header";
    }
}

Java IO Example

Decorator Pattern is everywhere in Java IO.

InputStream input =
        new FileInputStream("orders.txt");

input =
        new BufferedInputStream(input);

input =
        new DataInputStream(input);

Each wrapper adds new functionality.


Logging Decorator Example

public interface OrderService {

    void createOrder();
}

Core Service

public class OrderServiceImpl
        implements OrderService {

    @Override
    public void createOrder() {

        System.out.println(
                "Order Created");
    }
}

Logging Decorator

public class LoggingDecorator
        implements OrderService {

    private final OrderService service;

    public LoggingDecorator(
            OrderService service) {

        this.service = service;
    }

    @Override
    public void createOrder() {

        System.out.println(
                "Logging Started");

        service.createOrder();

        System.out.println(
                "Logging Completed");
    }
}

Output

Logging Started

Order Created

Logging Completed

Microservices Example

API Gateway Request Processing

Request

→ Authentication

→ Authorization

→ Rate Limiting

→ Logging

→ Tracing

→ Service

Each layer acts like a decorator.


Framework Examples

Framework Example
Spring Security Filter Chain
Servlet API Request Wrapper
Java IO BufferedInputStream
Java IO DataInputStream
Kafka Message Interceptors
SLF4J Logging Wrappers

Benefits

✅ Runtime Behavior Addition

✅ Avoids Class Explosion

✅ Composition Over Inheritance

✅ Open Closed Principle

✅ Reusable Components

✅ Easy Feature Addition

✅ Better Maintainability


Limitations

❌ Many Small Classes

❌ Deep Decorator Chains

❌ Harder Debugging

❌ Order Of Decorators Matters


When To Use

Use Decorator when:

  • Features are optional
  • Behavior changes at runtime
  • Inheritance becomes complex
  • Cross-cutting concerns exist

Examples:

  • Logging
  • Security
  • Validation
  • Monitoring
  • Auditing

When Not To Use

Avoid Decorator when:

  • Behavior never changes
  • Simpler inheritance works
  • Extra abstraction is unnecessary

Decorator vs Adapter

Feature Decorator Adapter
Goal Add Behavior Change Interface
Client Interface Same Different
Focus Enhancement Compatibility

Decorator vs Composite

Feature Decorator Composite
Purpose Add Functionality Build Hierarchies
Structure Wrapper Tree
Example Coffee Add-ons File System

Interview Questions

What is Decorator Pattern?

Decorator dynamically adds behavior to an object without modifying the original class.


Why use Decorator instead of inheritance?

To avoid class explosion and support runtime flexibility.


Real World Example?

Coffee with toppings.


Where is Decorator used in Java?

Java IO Streams.


Where is Decorator used in Spring?

Spring Security Filter Chain.


Difference Between Decorator and Adapter?

Decorator adds behavior.

Adapter changes interface.


Key Takeaways

  • Decorator is a Structural Design Pattern.
  • Adds functionality dynamically at runtime.
  • Uses composition instead of inheritance.
  • Prevents class explosion.
  • Widely used in Spring Security, Java IO, API Gateways, and Microservices.
  • Excellent for logging, monitoring, security, auditing, and validation.