Composite Design Pattern in Java
The Composite Design Pattern is a structural pattern that organizes objects into tree structures, allowing clients to treat individual objects and groups of objects uniformly.
As described by the Gang of four, "Compose objects into tree structure to represent part-whole hierarchies. Composite lets client treat individual objects and compositions of objects uniformly".

The key concept is that you can manipulate a single instance of the object just as you would manipulate a group of them. The operations you can perform on all the composite objects often have the least common denominator relationship.
Table of Content
Components of Composite Design Pattern
Let's understand this with the help of Diagram:

Here These are introductions for the Components of the Composite Design:
- Component – Defines a common interface that both leaf objects and composite objects implement.
- Leaf – Represents simple, indivisible objects that perform operations directly.
- Composite – Represents complex objects that can contain children (leaves or other composites) and delegate operations to them.
- Client – Works with all objects through the Component interface, treating individual and composite objects uniformly.
Composite Design Pattern example in Java
Imagine you are building a project management system where tasks can be either simple tasks or a collection of tasks (subtasks) forming a larger task.

1. Task (Component)
- Represents the common interface for both simple tasks and task lists.
- Defines methods such as
getTitle(),setTitle(), anddisplay().
#include <string>
#include <iostream>
class Task {
public:
virtual std::string getTitle() const = 0;
virtual void setTitle(const std::string& title) = 0;
virtual void display() const = 0;
};
// Component
public interface Task {
String getTitle();
void setTitle(String title);
void display();
}
from abc import ABC, abstractmethod
class Task(ABC):
@abstractmethod
def get_title(self):
pass
@abstractmethod
def set_title(self, title):
pass
@abstractmethod
def display(self):
pass
class Task {
constructor() {
if (new.target === Task) {
throw new TypeError('Cannot construct Task instances directly');
}
}
getTitle() {
throw new Error('Method getTitle() must be implemented.');
}
setTitle(title) {
throw new Error('Method setTitle() must be implemented.');
}
display() {
throw new Error('Method display() must be implemented.');
}
}
2. SimpleTask (Leaf)
- Represents an individual task with a title.
- Implements the
Taskinterface.
// Leaf
#include <iostream>
#include <string>
using namespace std;
class Task {
public:
virtual string getTitle() = 0;
virtual void setTitle(string title) = 0;
virtual void display() = 0;
};
class SimpleTask : public Task {
private:
string title;
public:
SimpleTask(string title) : title(title) {}
string getTitle() override {
return title;
}
void setTitle(string title) override {
this->title = title;
}
void display() override {
cout << "Simple Task: " << title << endl;
}
};
// Leaf
public class SimpleTask implements Task {
private String title;
public SimpleTask(String title) {
this.title = title;
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
@Override
public void display() {
System.out.println("Simple Task: " + title);
}
}
# Leaf
class Task:
def get_title(self):
pass
def set_title(self, title):
pass
def display(self):
pass
class SimpleTask(Task):
def __init__(self, title):
self.title = title
def get_title(self):
return self.title
def set_title(self, title):
self.title = title
def display(self):
print(f'Simple Task: {self.title}')
// Leaf
class SimpleTask {
constructor(title) {
this.title = title;
}
getTitle() {
return this.title;
}
setTitle(title) {
this.title = title;
}
display() {
console.log(`Simple Task: ${this.title}`);
}
}
3. TaskList (Composite)
- Represents a collection of tasks, which can include both simple tasks and other task lists.
- Implements the
Taskinterface but also has a list of tasks (List<Task>). - Defines methods to add, remove, and display tasks.
#include <iostream>
#include <vector>
#include <string>
class Task {
public:
virtual std::string getTitle() = 0;
virtual void setTitle(std::string title) = 0;
virtual void display() = 0;
};
class TaskList : public Task {
private:
std::string title;
std::vector<Task*> tasks;
public:
TaskList(std::string title) : title(title) {}
std::string getTitle() override {
return title;
}
void setTitle(std::string title) override {
this->title = title;
}
void addTask(Task* task) {
tasks.push_back(task);
}
void removeTask(Task* task) {
tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end());
}
void display() override {
std::cout << "Task List: " << title << std::endl;
for (Task* task : tasks) {
task->display();
}
}
};
import java.util.ArrayList;
import java.util.List;
// Composite
public class TaskList implements Task {
private String title;
private List<Task> tasks;
public TaskList(String title) {
this.title = title;
this.tasks = new ArrayList<>();
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
public void addTask(Task task) {
tasks.add(task);
}
public void removeTask(Task task) {
tasks.remove(task);
}
@Override
public void display() {
System.out.println("Task List: " + title);
for (Task task : tasks) {
task.display();
}
}
}
class TaskList:
def __init__(self, title):
self.title = title
self.tasks = []
def get_title(self):
return self.title
def set_title(self, title):
self.title = title
def add_task(self, task):
self.tasks.append(task)
def remove_task(self, task):
self.tasks.remove(task)
def display(self):
print(f'Task List: {self.title}')
for task in self.tasks:
task.display()
class TaskList {
constructor(title) {
this.title = title;
this.tasks = [];
}
getTitle() {
return this.title;
}
setTitle(title) {
this.title = title;
}
addTask(task) {
this.tasks.push(task);
}
removeTask(task) {
this.tasks = this.tasks.filter(t => t !== task);
}
display() {
console.log(`Task List: ${this.title}`);
this.tasks.forEach(task => task.display());
}
}
4. TaskManagementApp (Client)
- Represents the application that uses the Composite Design Pattern to manage tasks.
- It creates a mix of simple tasks and task lists, showcasing how the Composite pattern allows treating both individual tasks and task collections uniformly.
- The created tasks are displayed in a hierarchical structure to illustrate the pattern's flexibility and uniform handling of different task types.
#include <iostream>
#include <vector>
#include <string>
class Task {
public:
virtual void display() const = 0;
};
class SimpleTask : public Task {
private:
std::string description;
public:
SimpleTask(const std::string& desc) : description(desc) {}
void display() const override {
std::cout << "- " << description << std::endl;
}
};
class TaskList : public Task {
private:
std::string name;
std::vector<Task*> tasks;
public:
TaskList(const std::string& n) : name(n) {}
void addTask(Task* task) {
tasks.push_back(task);
}
void display() const override {
std::cout << name << std::endl;
for (const auto& task : tasks) {
task->display();
}
}
};
int main() {
// Creating simple tasks
Task* simpleTask1 = new SimpleTask("Complete Coding");
Task* simpleTask2 = new SimpleTask("Write Documentation");
// Creating a task list
TaskList* projectTasks = new TaskList("Project Tasks");
projectTasks->addTask(simpleTask1);
projectTasks->addTask(simpleTask2);
// Nested task list
TaskList* phase1Tasks = new TaskList("Phase 1 Tasks");
phase1Tasks->addTask(new SimpleTask("Design"));
phase1Tasks->addTask(new SimpleTask("Implementation"));
projectTasks->addTask(phase1Tasks);
// Displaying tasks
projectTasks->display();
delete simpleTask1;
delete simpleTask2;
delete projectTasks;
delete phase1Tasks;
return 0;
}
// Client
public class TaskManagementApp {
public static void main(String[] args) {
// Creating simple tasks
Task simpleTask1 = new SimpleTask("Complete Coding");
Task simpleTask2 = new SimpleTask("Write Documentation");
// Creating a task list
TaskList projectTasks = new TaskList("Project Tasks");
projectTasks.addTask(simpleTask1);
projectTasks.addTask(simpleTask2);
// Nested task list
TaskList phase1Tasks = new TaskList("Phase 1 Tasks");
phase1Tasks.addTask(new SimpleTask("Design"));
phase1Tasks.addTask(new SimpleTask("Implementation"));
projectTasks.addTask(phase1Tasks);
// Displaying tasks
projectTasks.display();
}
}
from abc import ABC, abstractmethod
class Task(ABC):
@abstractmethod
def display(self):
pass
class SimpleTask(Task):
def __init__(self, description):
self.description = description
def display(self):
print(f'- {self.description}')
class TaskList(Task):
def __init__(self, name):
self.name = name
self.tasks = []
def addTask(self, task):
self.tasks.append(task)
def display(self):
print(self.name)
for task in self.tasks:
task.display()
# Client
def main():
# Creating simple tasks
simpleTask1 = SimpleTask('Complete Coding')
simpleTask2 = SimpleTask('Write Documentation')
# Creating a task list
projectTasks = TaskList('Project Tasks')
projectTasks.addTask(simpleTask1)
projectTasks.addTask(simpleTask2)
# Nested task list
phase1Tasks = TaskList('Phase 1 Tasks')
phase1Tasks.addTask(SimpleTask('Design'))
phase1Tasks.addTask(SimpleTask('Implementation'))
projectTasks.addTask(phase1Tasks)
# Displaying tasks
projectTasks.display()
if __name__ == '__main__':
main()
class Task {
display() {
throw new Error('Method not implemented.');
}
}
class SimpleTask extends Task {
constructor(description) {
super();
this.description = description;
}
display() {
console.log(`- ${this.description}`);
}
}
class TaskList extends Task {
constructor(name) {
super();
this.name = name;
this.tasks = [];
}
addTask(task) {
this.tasks.push(task);
}
display() {
console.log(this.name);
this.tasks.forEach(task => task.display());
}
}
// Client
const main = () => {
// Creating simple tasks
const simpleTask1 = new SimpleTask('Complete Coding');
const simpleTask2 = new SimpleTask('Write Documentation');
// Creating a task list
const projectTasks = new TaskList('Project Tasks');
projectTasks.addTask(simpleTask1);
projectTasks.addTask(simpleTask2);
// Nested task list
const phase1Tasks = new TaskList('Phase 1 Tasks');
phase1Tasks.addTask(new SimpleTask('Design'));
phase1Tasks.addTask(new SimpleTask('Implementation'));
projectTasks.addTask(phase1Tasks);
// Displaying tasks
projectTasks.display();
};
main();
Complete code for the above example:
This code includes the Task, SimpleTask, TaskList, and TaskManagementApp classes. It demonstrates the Composite Design Pattern for organizing tasks in a project management system.
#include <iostream>
#include <vector>
#include <string>
// Component
class Task {
public:
virtual std::string getTitle() const = 0;
virtual void setTitle(const std::string& title) = 0;
virtual void display() const = 0;
};
// Leaf
class SimpleTask : public Task {
private:
std::string title;
public:
SimpleTask(const std::string& title) : title(title) {}
std::string getTitle() const override { return title; }
void setTitle(const std::string& title) override { this->title = title; }
void display() const override { std::cout << "Simple Task: " << title << std::endl; }
};
// Composite
class TaskList : public Task {
private:
std::string title;
std::vector<Task*> tasks;
public:
TaskList(const std::string& title) : title(title) {}
std::string getTitle() const override { return title; }
void setTitle(const std::string& title) override { this->title = title; }
void addTask(Task* task) { tasks.push_back(task); }
void removeTask(Task* task) { tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end()); }
void display() const override {
std::cout << "Task List: " << title << std::endl;
for (const auto& task : tasks) {
task->display();
}
}
};
// Client
int main() {
// Creating simple tasks
Task* simpleTask1 = new SimpleTask("Complete Coding");
Task* simpleTask2 = new SimpleTask("Write Documentation");
// Creating a task list
Task* projectTasks = new TaskList("Project Tasks");
projectTasks->addTask(simpleTask1);
projectTasks->addTask(simpleTask2);
// Nested task list
Task* phase1Tasks = new TaskList("Phase 1 Tasks");
phase1Tasks->addTask(new SimpleTask("Design"));
phase1Tasks->addTask(new SimpleTask("Implementation"));
projectTasks->addTask(phase1Tasks);
// Displaying tasks
projectTasks->display();
// Clean up
delete simpleTask1;
delete simpleTask2;
delete phase1Tasks;
delete projectTasks;
return 0;
}
import java.util.ArrayList;
import java.util.List;
// Component
interface Task {
String getTitle();
void setTitle(String title);
void display();
}
// Leaf
class SimpleTask implements Task {
private String title;
public SimpleTask(String title) {
this.title = title;
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
@Override
public void display() {
System.out.println("Simple Task: " + title);
}
}
// Composite
class TaskList implements Task {
private String title;
private List<Task> tasks;
public TaskList(String title) {
this.title = title;
this.tasks = new ArrayList<>();
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
public void addTask(Task task) {
tasks.add(task);
}
public void removeTask(Task task) {
tasks.remove(task);
}
@Override
public void display() {
System.out.println("Task List: " + title);
for (Task task : tasks) {
task.display();
}
}
}
// Client
public class TaskManagementApp {
public static void main(String[] args) {
// Creating simple tasks
Task simpleTask1 = new SimpleTask("Complete Coding");
Task simpleTask2 = new SimpleTask("Write Documentation");
// Creating a task list
TaskList projectTasks = new TaskList("Project Tasks");
projectTasks.addTask(simpleTask1);
projectTasks.addTask(simpleTask2);
// Nested task list
TaskList phase1Tasks = new TaskList("Phase 1 Tasks");
phase1Tasks.addTask(new SimpleTask("Design"));
phase1Tasks.addTask(new SimpleTask("Implementation"));
projectTasks.addTask(phase1Tasks);
// Displaying tasks
projectTasks.display();
}
}
# Component
from abc import ABC, abstractmethod
class Task(ABC):
@abstractmethod
def get_title(self):
pass
@abstractmethod
def set_title(self, title):
pass
@abstractmethod
def display(self):
pass
# Leaf
class SimpleTask(Task):
def __init__(self, title):
self._title = title
def get_title(self):
return self._title
def set_title(self, title):
self._title = title
def display(self):
print(f'Simple Task: {self._title}')
# Composite
class TaskList(Task):
def __init__(self, title):
self._title = title
self._tasks = []
def get_title(self):
return self._title
def set_title(self, title):
self._title = title
def add_task(self, task):
self._tasks.append(task)
def remove_task(self, task):
self._tasks.remove(task)
def display(self):
print(f'Task List: {self._title}')
for task in self._tasks:
task.display()
# Client
if __name__ == '__main__':
# Creating simple tasks
simple_task1 = SimpleTask('Complete Coding')
simple_task2 = SimpleTask('Write Documentation')
# Creating a task list
project_tasks = TaskList('Project Tasks')
project_tasks.add_task(simple_task1)
project_tasks.add_task(simple_task2)
# Nested task list
phase1_tasks = TaskList('Phase 1 Tasks')
phase1_tasks.add_task(SimpleTask('Design'))
phase1_tasks.add_task(SimpleTask('Implementation'))
project_tasks.add_task(phase1_tasks)
# Displaying tasks
project_tasks.display()
// Component
class Task {
constructor(title) {
if (this.constructor === Task) {
throw new TypeError('Abstract class "Task" cannot be instantiated directly.');
}
this._title = title;
}
getTitle() {
return this._title;
}
setTitle(title) {
this._title = title;
}
display() {
throw new TypeError('Method "display" must be implemented.');
}
}
// Leaf
class SimpleTask extends Task {
constructor(title) {
super(title);
}
display() {
console.log(`Simple Task: ${this._title}`);
}
}
// Composite
class TaskList extends Task {
constructor(title) {
super(title);
this._tasks = [];
}
addTask(task) {
this._tasks.push(task);
}
removeTask(task) {
this._tasks = this._tasks.filter(t => t !== task);
}
display() {
console.log(`Task List: ${this._title}`);
this._tasks.forEach(task => task.display());
}
}
// Client
(() => {
// Creating simple tasks
const simpleTask1 = new SimpleTask('Complete Coding');
const simpleTask2 = new SimpleTask('Write Documentation');
// Creating a task list
const projectTasks = new TaskList('Project Tasks');
projectTasks.addTask(simpleTask1);
projectTasks.addTask(simpleTask2);
// Nested task list
const phase1Tasks = new TaskList('Phase 1 Tasks');
phase1Tasks.addTask(new SimpleTask('Design'));
phase1Tasks.addTask(new SimpleTask('Implementation'));
projectTasks.addTask(phase1Tasks);
// Displaying tasks
projectTasks.display();
})();
Output
Task List: Project Tasks Simple Task: Complete Coding Simple Task: Write Documentation Task List: Phase 1 Tasks Simple Task: Design Simple Task: Implementation
Why do we need Composite Design Pattern?
The Composite Design Pattern was created to address specific challenges related to the representation and manipulation of hierarchical structures in a uniform way.
Here are some points that highlight the need for the Composite Design Pattern:
- Uniform Interface – Treats individual and composite objects the same, simplifying client code.
- Hierarchical Structures – Ideal for tree-like part-whole relationships.
- Flexibility & Scalability – Allows dynamic composition, making structures easy to extend or modify.
- Common Operations – Defines shared operations at the component level, reducing duplication.
- Client Simplification – Provides a unified way to handle complex structures efficiently.
Use of Composite Design Pattern
The Composite Pattern allows uniform treatment of single components and groups, simplifying design and improving efficiency.
- Enables clients to work with individual components and collections in the same way.
- Reduces duplicate logic in handling primitives vs composites.
- Lowers resource usage by minimizing unnecessary objects.
- Supports resource sharing for better scalability and performance.
When not to use Composite Design Pattern?
Composite Design Pattern makes it harder to restrict the type of components of a composite. So it should not be used when you don't want to represent a full or partial hierarchy of objects.
- Composite Design Pattern can make the design overly general.
- It makes harder to restrict the components of a composite.
- Sometimes you want a composite to have only certain components. With Composite, you can't rely on the type system to enforce those constraints for you.
- Instead you'll have to use run-time checks.