Back to Design Patterns
Structural Pattern

Composite Pattern

Compose objects into tree structures to represent part-whole hierarchies — allowing clients to treat individual objects and compositions of objects uniformly.

Intermediate10 min read

What is the Composite Pattern?

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.

Core Participants

Component

Component Interface

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.

Leaf

Leaf

A basic element with no children. Implements the Component interface directly. Does the actual work — File, Developer, TextNode, Product.

Composite

Composite

A container that holds other components (leaves or other composites). Implements Component by delegating operations to its children and optionally combining their results.

Client

Client

Works only through the Component interface. Never checks whether it has a leaf or a composite — the polymorphism handles the recursion automatically.

!Problem It Solves

Type-Checking Explosion

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

Implementation

// 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());
    }
}

When to Use Composite

Tree-Structured Data

Whenever your data is naturally hierarchical — file systems, org charts, menus, DOM, category trees, bill-of-materials.

FileSystem, OrgChart, CategoryTree

Uniform Treatment of Items and Groups

When 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()

Recursive Computations

When a value at a node is computed by aggregating the same value from all children — totals, sums, counts, depths.

TotalCost, TeamSalary, TreeDepth

UI Component Trees

When building UI frameworks where containers (panels, groups, layouts) and leaves (buttons, labels) share a common Component interface.

React VDOM, Swing Components, HTML DOM

Pros & Cons

Advantages

  • Uniform client code — no type-checking required
  • Easy to add new component types — Open/Closed Principle
  • Recursion is implicit in the tree structure
  • Works naturally for any hierarchical problem
  • Adding composite nodes doesn't break existing operations

Disadvantages

  • Hard to restrict what types can be children of a composite
  • Deep trees can cause deep recursion and stack overflow
  • Interface may force leaves to implement irrelevant methods
  • Removing nodes from deep trees requires traversal

Real-World Examples

HTML DOM

Every 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 Component Tree

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).

Java Swing Component Hierarchy

JPanel (composite) contains JButton, JLabel (leaves) or other JPanels. All extend JComponent, which provides uniform paint(), validate(), and event-handling methods.

Ant / Maven Build Tasks

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.

What's Next?

Explore related Structural patterns: