Composite Design Pattern
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Real-World Analogy
Think of a File System
A folder can contain files and other folders. A file is a leaf — it has no children. A folder is a composite — it can contain both files and other folders. When you calculate the total size, you call getSize() on the root folder, and it recursively sums up all nested files and folders. You treat a single file and an entire folder tree the same way.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
Root{{"📁 Root"}} -->|"contains"| Src{{"📁 src/"}}
Root -->|"contains"| F1(["📄 readme.md"])
Src -->|"contains"| S1(["📄 App.java"])
Src -->|"contains"| Sub{{"📁 models/"}}
Sub -->|"contains"| M1(["📄 User.java"])
style Root fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
style Src fill:#FFF3E0,stroke:#E65100,color:#000
style Sub fill:#FFF3E0,stroke:#E65100,color:#000
style F1 fill:#E8F5E9,stroke:#2E7D32,color:#000
style S1 fill:#E8F5E9,stroke:#2E7D32,color:#000
style M1 fill:#E8F5E9,stroke:#2E7D32,color:#000
Pattern Structure
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
Client["🖥️ Client"] -->|"uses"| Component[["🎯 Component"]]
Leaf(["🍃 Leaf"]) -->|"implements"| Component
Composite{{"🌳 Composite"}} -->|"implements"| Component
Composite -.->|"contains 0..n"| Component
style Client fill:#E8F5E9,stroke:#2E7D32,color:#000
style Component fill:#FFF3E0,stroke:#E65100,color:#000
style Leaf fill:#E3F2FD,stroke:#1565C0,color:#000
style Composite fill:#E3F2FD,stroke:#1565C0,color:#000 UML Class Diagram
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
class FileSystemComponent {
<<interface>>
+getName() String
+getSize() long
+display(indent) void
}
class File {
-name: String
-size: long
+getName() String
+getSize() long
+display(indent) void
}
class Directory {
-name: String
-children: List~FileSystemComponent~
+add(component) void
+remove(component) void
+getName() String
+getSize() long
+display(indent) void
}
File ..|> FileSystemComponent : implements
Directory ..|> FileSystemComponent : implements
Directory o-- FileSystemComponent : contains 0..* Example Tree Structure
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
CEO{{"👔 CEO"}} -->|"manages"| VP1{{"👔 VP Engineering"}}
CEO -->|"manages"| VP2{{"👔 VP Sales"}}
VP1 --> Dev1(["👨💻 Developer 1"])
VP1 --> Dev2(["👨💻 Developer 2"])
VP2 --> Sales1(["💼 Sales Rep"])
style CEO fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
style VP1 fill:#FFF3E0,stroke:#E65100,color:#000
style VP2 fill:#FFF3E0,stroke:#E65100,color:#000
style Dev1 fill:#E3F2FD,stroke:#1565C0,color:#000
style Dev2 fill:#E3F2FD,stroke:#1565C0,color:#000
style Sales1 fill:#E3F2FD,stroke:#1565C0,color:#000
The Problem
You're building a graphics editor. Shapes can be simple (circles, rectangles) or complex (groups of shapes). Users want to:
- Move a single shape
- Move a group of shapes as one unit
- Nest groups inside groups
Without Composite, you'd need separate code paths for "single shape" and "group of shapes", and deeply nested groups would require recursive special-casing everywhere.
Without This Pattern
// BAD: Client must distinguish between single shapes and groups everywhere
public class GraphicsEditor {
public void moveShape(Shape shape, int dx, int dy) {
shape.setX(shape.getX() + dx);
shape.setY(shape.getY() + dy);
}
// Completely separate logic for groups — duplicated and fragile
public void moveGroup(List<Shape> group, int dx, int dy) {
for (Shape shape : group) {
shape.setX(shape.getX() + dx);
shape.setY(shape.getY() + dy);
}
}
// What about nested groups? Even more special-casing!
public void moveNestedGroup(List<Object> items, int dx, int dy) {
for (Object item : items) {
if (item instanceof Shape s) {
s.setX(s.getX() + dx);
s.setY(s.getY() + dy);
} else if (item instanceof List<?> subGroup) {
moveNestedGroup((List<Object>) subGroup, dx, dy); // unsafe cast
}
}
}
}
Problems:
- Type-checking everywhere: Client code is littered with
instanceofchecks and unsafe casts to distinguish leaves from groups - Violates Open/Closed Principle: Adding a new operation (resize, delete, serialize) requires duplicating the single-vs-group logic in every new method
- Fragile recursive handling: Nested groups require manual recursive traversal with raw
List<Object>— no type safety, easy to introduce bugs - Cannot treat objects uniformly: The fundamental operation "move this thing" has 3 different method signatures depending on structure depth
- Pain point: Every time you add nesting depth or a new operation, you must write and maintain another recursive method with type-checking logic — the codebase becomes a maze of
if/else instanceofblocks
The Solution
The Composite pattern defines a common interface for both simple (leaf) and complex (composite) elements. The composite element stores children and delegates operations to them recursively. Clients interact with the interface without knowing if they're dealing with a leaf or an entire subtree.
Four participants:
| Role | Responsibility |
|---|---|
| Component | Common interface for leaf and composite |
| Leaf | Represents end objects with no children |
| Composite | Stores children; delegates operations recursively |
| Client | Interacts uniformly via the Component interface |
Implementation
// Component
public interface FileSystemComponent {
String getName();
long getSize();
void display(String indent);
}
// Leaf
public class File implements FileSystemComponent {
private final String name;
private final long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
@Override
public String getName() { return name; }
@Override
public long getSize() { return size; }
@Override
public void display(String indent) {
System.out.println(indent + "📄 " + name + " (" + size + " bytes)");
}
}
// Composite
public class Directory implements FileSystemComponent {
private final String name;
private final List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
children.add(component);
}
public void remove(FileSystemComponent component) {
children.remove(component);
}
@Override
public String getName() { return name; }
@Override
public long getSize() {
// Recursively calculates total size
return children.stream()
.mapToLong(FileSystemComponent::getSize)
.sum();
}
@Override
public void display(String indent) {
System.out.println(indent + "📁 " + name + " (" + getSize() + " bytes)");
for (FileSystemComponent child : children) {
child.display(indent + " ");
}
}
}
// Client
public class FileExplorer {
public static void main(String[] args) {
Directory root = new Directory("root");
Directory src = new Directory("src");
Directory tests = new Directory("tests");
src.add(new File("Main.java", 2048));
src.add(new File("Utils.java", 1024));
tests.add(new File("MainTest.java", 1536));
root.add(src);
root.add(tests);
root.add(new File("README.md", 512));
// Treat entire tree uniformly
root.display("");
System.out.println("Total size: " + root.getSize() + " bytes");
}
}
Output:
// Component
public interface Employee {
String getName();
double getSalary();
void displayHierarchy(String indent);
}
// Leaf — individual contributor
public class Developer implements Employee {
private final String name;
private final double salary;
public Developer(String name, double salary) {
this.name = name;
this.salary = salary;
}
@Override
public String getName() { return name; }
@Override
public double getSalary() { return salary; }
@Override
public void displayHierarchy(String indent) {
System.out.println(indent + "👨💻 " + name + " ($" + salary + ")");
}
}
// Composite — manager with reports
public class Manager implements Employee {
private final String name;
private final double salary;
private final List<Employee> reports = new ArrayList<>();
public Manager(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void addReport(Employee employee) {
reports.add(employee);
}
public void removeReport(Employee employee) {
reports.remove(employee);
}
@Override
public String getName() { return name; }
@Override
public double getSalary() { return salary; }
public double getTeamCost() {
return salary + reports.stream()
.mapToDouble(e -> e instanceof Manager m ? m.getTeamCost() : e.getSalary())
.sum();
}
@Override
public void displayHierarchy(String indent) {
System.out.println(indent + "👔 " + name + " ($" + salary + ") [Team Cost: $" + getTeamCost() + "]");
for (Employee report : reports) {
report.displayHierarchy(indent + " ");
}
}
}
// Usage
public class OrgChart {
public static void main(String[] args) {
Manager ceo = new Manager("Alice (CEO)", 300000);
Manager vpEng = new Manager("Bob (VP Eng)", 200000);
vpEng.addReport(new Developer("Charlie", 150000));
vpEng.addReport(new Developer("Diana", 140000));
Manager vpSales = new Manager("Eve (VP Sales)", 180000);
vpSales.addReport(new Developer("Frank", 120000));
ceo.addReport(vpEng);
ceo.addReport(vpSales);
ceo.displayHierarchy("");
}
}
// Component — UI element
public interface UIComponent {
void render();
void resize(int width, int height);
}
// Leaf — simple UI element
public class Button implements UIComponent {
private final String label;
public Button(String label) { this.label = label; }
@Override
public void render() {
System.out.println(" [Button: " + label + "]");
}
@Override
public void resize(int width, int height) {
System.out.println(" Resizing button '" + label + "' to " + width + "x" + height);
}
}
public class TextField implements UIComponent {
private final String placeholder;
public TextField(String placeholder) { this.placeholder = placeholder; }
@Override
public void render() {
System.out.println(" [TextField: " + placeholder + "]");
}
@Override
public void resize(int width, int height) {
System.out.println(" Resizing textfield to " + width + "x" + height);
}
}
// Composite — container of UI elements
public class Panel implements UIComponent {
private final String name;
private final List<UIComponent> children = new ArrayList<>();
public Panel(String name) { this.name = name; }
public void add(UIComponent component) { children.add(component); }
@Override
public void render() {
System.out.println("[Panel: " + name + "]");
children.forEach(UIComponent::render);
}
@Override
public void resize(int width, int height) {
System.out.println("Resizing panel '" + name + "' and all children:");
children.forEach(c -> c.resize(width, height));
}
}
// Usage
Panel form = new Panel("Login Form");
form.add(new TextField("Username"));
form.add(new TextField("Password"));
form.add(new Button("Submit"));
Panel page = new Panel("Main Page");
page.add(form);
page.add(new Button("Help"));
page.render(); // Renders entire tree uniformly
When to Use
- You want to represent part-whole hierarchies of objects (trees)
- You want clients to treat individual objects and compositions uniformly
- The structure is recursive — composites contain both leaves and other composites
- You're working with nested menus, file systems, org charts, or UI component trees
- You want to run operations recursively across the entire tree
Real-World Examples
| Where | Example |
|---|---|
| JDK | java.awt.Container — contains Component objects (which can be containers) |
| JDK | javax.swing.JPanel — can hold other panels and widgets |
| JSF | UIComponent tree — the entire JSF component hierarchy |
| Spring | BeanDefinition with nested beans |
| XML/DOM | org.w3c.dom.Node — elements contain other elements |
| React/Angular | Component trees where parent components render child components |
| Build tools | Maven modules — a parent POM contains child modules |
Pitfalls
Common Mistakes
- Overly general Component interface: Leaf nodes shouldn't expose
add()/remove()— it violates ISP (prefer making child management composite-only) - Circular references: A composite accidentally added as its own descendant causes infinite recursion
- Difficulty restricting composition: It's hard to constrain which types can be children (e.g., "only files in this folder")
- Performance on deep trees: Recursive operations on deeply nested structures can be slow — consider caching computed results
- Thread safety: Modifying the tree while iterating it causes
ConcurrentModificationException
Key Takeaways
Summary
| Aspect | Detail |
|---|---|
| Intent | Treat individual objects and groups uniformly via tree structure |
| Mechanism | Common interface; composites delegate to children recursively |
| Key Benefit | Simplifies client code — no need to distinguish leaf vs. composite |
| Key Principle | Open/Closed — add new component types without changing client |
| When to use | Any recursive tree structure (files, UI, org charts, menus) |
| Interview Tip | "Java's AWT/Swing uses Composite — a JPanel is a Component that contains other Components" |