Compose objects into tree structures to represent part-whole hierarchies — treat individual objects and compositions uniformly.
Composite lets clients treat individual objects (leaves) and compositions of objects (composites) through the same interface. The client never needs to distinguish between a single item and a group of items — both respond to the same operations.
It builds tree structures where every node (composite) can contain children that are either leaves or other composites, allowing recursive composition of arbitrary depth.
getSalary(). Calling it on a department sums up the salaries of everyone inside it recursively.Consider a file system. You have File objects and Folder objects. Folders can contain Files or other Folders. To calculate total size, you'd have to write one code path for files and a different recursive one for folders — and repeat this every time you need a new operation.
Composite makes both File and Folder implement the same FileSystem interface. getSize() on a File returns its own size; on a Folder it sums children recursively. Client calls the same method on both.
// Component — uniform interface public interface FileSystemItem { String getName(); long getSize(); void print(String indent); } // Leaf — no children public 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 print(String indent) { System.out.println(indent + "📄 " + name + " (" + size + "KB)"); } } // Composite — contains children public class Folder implements FileSystemItem { private String name; private List<FileSystemItem> children = new ArrayList<>(); public Folder(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; } // Composite operation: aggregates from all children recursively public long getSize() { return children.stream() .mapToLong(FileSystemItem::getSize) .sum(); } public void print(String indent) { System.out.println(indent + "📁 " + name); children.forEach(c -> c.print(indent + " ")); // recursive! } } // Client — never distinguishes File from Folder public class Main { public static void main(String[] args) { Folder root = new Folder("root"); Folder src = new Folder("src"); src.add(new File("Main.java", 12)); src.add(new File("Utils.java", 8)); Folder test = new Folder("test"); test.add(new File("MainTest.java", 5)); root.add(src); root.add(test); root.add(new File("README.md", 2)); root.print(""); // 📁 root // 📁 src // 📄 Main.java (12KB) // 📄 Utils.java (8KB) // 📁 test // 📄 MainTest.java (5KB) // 📄 README.md (2KB) System.out.println("Total: " + root.getSize() + "KB"); // 27KB } }
JPanel (composite) contains JButton, JLabel (leaves) all as Componentdiv can contain other divs or text nodes; all are NodeAuthorizationManager composites chain multiple authorization checksjava.awt.Container extends java.awt.Component — textbook Composite in the JDKTask interfacegetSize() loop infinitely causing a StackOverflowErrorfolder.add(folder) — simplest form of circular reference, crashes on any recursive callinstanceof to distinguish Leaf from Composite inside operations — defeats the whole point of the uniform interfaceadd(), explicitly check if (item == this) throw new IllegalArgumentException("Cannot add to itself")Composite sub-interface that adds add()/remove() — Leaf never implements these, catching misuse at compile timeDeque) to avoid StackOverflowError on very deep hierarchies// ❌ BREAKING — circular reference → StackOverflow Folder a = new Folder("a"); Folder b = new Folder("b"); a.add(b); b.add(a); // ❌ cycle! getSize() loops forever // ✅ FIX — ancestor check in add() public void add(FileSystemItem item) { if (item == this) throw new IllegalArgumentException("Cannot add folder to itself"); if (isAncestor(item)) throw new IllegalArgumentException("Circular reference detected"); children.add(item); } private boolean isAncestor(FileSystemItem candidate) { if (!(candidate instanceof Folder)) return false; Folder f = (Folder) candidate; for (FileSystemItem child : f.children) { if (child == this || isAncestor(child)) return true; } return false; } // ❌ BREAKING — client uses instanceof, defeats pattern void process(FileSystemItem item) { if (item instanceof Folder) { /* folder logic */ } else { /* file logic */ } } // ✅ FIX — add method to the interface, let each type handle itself public interface FileSystemItem { void process(); // File and Folder implement differently } // ✅ FIX — iterative traversal for deep trees public long getSizeIterative(Folder root) { long total = 0; Deque<FileSystemItem> stack = new ArrayDeque<>(); stack.push(root); while (!stack.isEmpty()) { FileSystemItem item = stack.pop(); total += item.getSize(); if (item instanceof Folder) ((Folder) item).children.forEach(stack::push); } return total; }
operation(). Leaf implements it directly. Composite stores List<Component> and loops children in its operation(). Both look identical to the client.add() with ancestor checks.