Compose objects into tree structures to represent part-whole hierarchies — allowing clients to treat individual objects and compositions of objects uniformly.
The Composite pattern lets you compose objects into tree structures and then treat individual objects (leaves) and groups of objects (composites) through the same interface. The client doesn't know or care whether it's working with a single item or a whole subtree.
Think of a file system: you can call getSize() on a single file or on a whole folder — the folder recursively sums up all its children. You don't need different code for each case.
Or an organisation chart: getTotalSalary() on one developer returns their salary. On a manager, it returns their salary plus all their reports recursively.
Key Idea: Leaf and Composite share the same interface. Operations on a Composite automatically recurse through the tree. Client code never checks whether it has a leaf or a branch.
The common interface for both leaves and composites. Declares operations that make sense for both — like getSize(), display(), calculate(). This is what clients program against.
A basic element with no children. Implements the Component interface directly. Does the actual work — File, Developer, TextNode, Product.
A container that holds other components (leaves or other composites). Implements Component by delegating operations to its children and optionally combining their results.
Works only through the Component interface. Never checks whether it has a leaf or a composite — the polymorphism handles the recursion automatically.
Without Composite, every operation needs branching logic to handle leaves vs containers separately. This spreads across the entire codebase and breaks whenever you add a new type.
// Without Composite — manual type checking everywhere
long getSize(Object item) {
if (item instanceof File) {
return ((File) item).getSize();
} else if (item instanceof Directory) {
long total = 0;
for (Object child : ((Directory) item).getChildren()) {
total += getSize(child); // recursive — manually written
}
return total;
}
throw new IllegalArgumentException("Unknown type");
}
// With Composite — one line
long size = item.getSize(); // works for File or Directory// Composite Pattern — File System Tree
// ─── Component interface ──────────────────────────────────────
interface FileSystemItem {
String getName();
long getSize();
void display(String indent);
}
// ─── Leaf ─────────────────────────────────────────────────────
class File implements FileSystemItem {
private String name;
private long size;
public File(String name, long size) { this.name = name; this.size = size; }
public String getName() { return name; }
public long getSize() { return size; }
public void display(String indent){ System.out.println(indent + "📄 " + name + " (" + size + "KB)"); }
}
// ─── Composite ────────────────────────────────────────────────
class Directory implements FileSystemItem {
private String name;
private java.util.List<FileSystemItem> children = new java.util.ArrayList<>();
public Directory(String name) { this.name = name; }
public void add(FileSystemItem item) { children.add(item); }
public void remove(FileSystemItem item) { children.remove(item); }
public String getName() { return name; }
// Size = sum of all children — works recursively
public long getSize() {
return children.stream().mapToLong(FileSystemItem::getSize).sum();
}
public void display(String indent) {
System.out.println(indent + "📁 " + name + " (" + getSize() + "KB total)");
for (FileSystemItem child : children) {
child.display(indent + " "); // recurse
}
}
}
// ─── Client — treats files and directories uniformly ──────────
public class App {
public static void main(String[] args) {
// Build tree
Directory root = new Directory("root");
Directory src = new Directory("src");
src.add(new File("Main.java", 12));
src.add(new File("App.java", 8));
src.add(new File("Config.java", 5));
Directory test = new Directory("test");
test.add(new File("AppTest.java", 6));
Directory resources = new Directory("resources");
resources.add(new File("app.properties", 2));
resources.add(new File("logo.png", 45));
root.add(src);
root.add(test);
root.add(resources);
root.add(new File("pom.xml", 3));
// Uniform operation on the whole tree
root.display("");
System.out.println("\nTotal project size: " + root.getSize() + "KB");
// Same interface — client doesn't care if it's a file or folder
FileSystemItem item = root;
System.out.println("Root name: " + item.getName());
}
}Whenever your data is naturally hierarchical — file systems, org charts, menus, DOM, category trees, bill-of-materials.
FileSystem, OrgChart, CategoryTreeWhen clients should be able to call the same operations on a single item or an entire subtree without knowing the difference.
getSize(), render(), calculate(), print()When a value at a node is computed by aggregating the same value from all children — totals, sums, counts, depths.
TotalCost, TeamSalary, TreeDepthWhen building UI frameworks where containers (panels, groups, layouts) and leaves (buttons, labels) share a common Component interface.
React VDOM, Swing Components, HTML DOMEvery HTML element is a Composite node. A <div> can contain other <div>s or leaf <img> elements. Operations like querySelectorAll() and innerHTML work uniformly on any node.
React's entire rendering model is Composite — functional components return trees of components. React traverses and renders them without knowing if a node is a leaf (DOM element) or a composite (custom component).
JPanel (composite) contains JButton, JLabel (leaves) or other JPanels. All extend JComponent, which provides uniform paint(), validate(), and event-handling methods.
Build tools compose tasks into task groups. A target may contain individual tasks (compile, copy) or composite task groups. Both are executed with the same execute() interface.