Back to Design Pattern Course

Design Pattern Course

Composite is a Structural Design Pattern.

There are times when you need to represent a tree structure in your code — for example, an organizational hierarchy, a file system (files inside folders inside folders), a UI component tree, or a bill-of-materials. What makes Composite valuable is that it lets clients treat individual leaf objects and compositions of objects (branches/nodes) uniformly through the same interface — so a single print(), cost(), or draw() call on the root of the tree automatically cascades down to every child, whether that child is a leaf or itself contains more children.

The pattern consists of three participants:

  • Component (interface/abstract class): The common interface shared by both leaf and composite objects. Client code only ever interacts with this type.
  • Leaf: A class that has no children. It represents the primitive, individual elements of the tree. It implements the Component interface but does not manage child components.
  • Composite (node): A class that can have children — other leaves or other composites. It implements the Component interface and manages a collection of child Components, usually by delegating operations to its children.
package com.praveen.designpatterns.structural.composite; /** * Component — Employee is the shared interface for both leaf (Developer) and * composite (Manager) participants. */ public interface Employee { void add(Employee emp); void remove(Employee emp); Employee getChild(int i); String getName(); double getSalary(); void print(); }
package com.praveen.designpatterns.structural.composite; import java.util.ArrayList; import java.util.List; /** * Composite — Manager can have child employees (leaves or other managers). */ public class Manager implements Employee { private final String name; private final double salary; private final List<Employee> employees = new ArrayList<>(); public Manager(String name, double salary) { this.name = name; this.salary = salary; } @Override public void add(Employee emp) { employees.add(emp); } @Override public void remove(Employee emp) { employees.remove(emp); } @Override public Employee getChild(int i) { return employees.get(i); } @Override public String getName() { return name; } @Override public double getSalary() { return salary; } @Override public void print() { System.out.println("--------------"); System.out.println("Name: " + getName()); System.out.println("Salary: " + getSalary()); System.out.println("--------------"); for (Employee emp : employees) { emp.print(); } } }
package com.praveen.designpatterns.structural.composite; /** * Leaf — Developer has no children. * Note: add/remove/getChild are unsupported operations for a leaf. */ 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 void add(Employee emp) { throw new UnsupportedOperationException("Leaf node does not support add()"); } @Override public void remove(Employee emp) { throw new UnsupportedOperationException("Leaf node does not support remove()"); } @Override public Employee getChild(int i) { throw new UnsupportedOperationException("Leaf node does not support getChild()"); } @Override public String getName() { return name; } @Override public double getSalary() { return salary; } @Override public void print() { System.out.println("--------------"); System.out.println("Name: " + getName()); System.out.println("Salary: " + getSalary()); System.out.println("--------------"); } }
package com.praveen.designpatterns.structural.composite; public class CompositePatternTest { public static void main(String[] args) { Employee emp1 = new Developer("Kiran", 80000); Employee emp2 = new Developer("Avinash", 50000); Employee manager1 = new Manager("Praveen", 300000); manager1.add(emp1); manager1.add(emp2); Employee emp3 = new Developer("Ravi", 60000); Employee generalManager = new Manager("Pradeep", 500000); generalManager.add(emp3); generalManager.add(manager1); generalManager.print(); } }

Output

--------------
Name: Pradeep
Salary: 500000.0
--------------
--------------
Name: Ravi
Salary: 60000.0
--------------
--------------
Name: Praveen
Salary: 300000.0
--------------
--------------
Name: Kiran
Salary: 80000.0
--------------
--------------
Name: Avinash
Salary: 50000.0
--------------

One important correction from the original: the leaf class (Developer) originally had empty, silently no-op add(), remove(), and getChild() implementations. Silently swallowing unsupported operations is dangerous — a caller that mistakenly tries to add a child to a Developer will get no error and no feedback, making bugs very hard to trace. The corrected version above throws UnsupportedOperationException instead, making the misuse immediately visible. This is actually one of the known trade-offs of the "transparent" Composite design (keeping add/remove on the Component interface so clients treat everything uniformly) vs. the "safe" design (only putting child-management methods on the Composite class, not the Component). Both approaches have valid uses — transparency wins when client code truly shouldn't need to know leaf from composite; safety wins when you want compile-time guarantees that leaves can't be misused.

When to Use Composite Pattern

  • When you need to represent part-whole hierarchies of objects.
  • When you want client code to treat individual objects and compositions of objects uniformly, so it doesn't need to distinguish between a leaf and a composite.

Composite Design Pattern Examples in the JDK

  • java.awt.Container#add(Component) — every AWT/Swing component can be a container holding child components, which are themselves potentially containers. Classic use of Composite.
  • javax.faces.component.UIComponent — JSF UI component tree is a Composite structure.