Aggregation: whole-part relationships with independent lifecycles
Learn aggregation in OOP: a has-a relationship where the part can exist independently of the whole, with Java examples and UML notation.
Introduction
Aggregation models a whole that groups parts without owning their lifecycles. The whole can organize or navigate to a part, while the part retains its identity and can continue to exist elsewhere. A Department and its Employee objects are a useful example.
The key boundary is lifecycle semantics: aggregation is stronger than a plain association because it expresses a whole-part grouping, but weaker than composition because the part remains independent. UML represents it with a hollow diamond.
TL;DR / Mental Model
- Mental model: the whole groups a part; another part of the domain manages the part's lifetime.
- Boundary: an aggregated part remains meaningful when the whole is removed and may be shared by multiple wholes.
- UML cue: a hollow diamond
βmarks the whole; a filled diamondβmeans composition. - Trade-off: independent lifecycles make reuse and sharing easier, but references, reassignment, and database integrity must be managed explicitly.
30-Second Explanation
Aggregation is a has-a relationship in which the part can outlive the whole. A department can aggregate employees: dissolving the department does not delete the employees, and an employee can move to another department. If the part has no meaningful life outside the whole, the relationship is composition instead.
5-Minute Explanation
Classify aggregation from domain intent, not from a constructor alone. Ask whether the part has an independent identity, whether it can be transferred or shared, and which object or process creates and retires it. Then model the whole as holding references to externally managed parts, use a hollow UML diamond when the whole-part meaning is useful, and avoid cascade deletion when the part must survive. The constructor and database schema are clues; the lifecycle rule is the contract.
The Problem
Aggregation and composition are often conflated because both use βhas-aβ language and can be drawn with diamonds. The distinction matters because it communicates who controls a contained object's lifecycle. Getting it wrong can leak orphaned records or accidentally destroy shared data.
Aggregation is a "has-a" relationship where the part can outlive the whole. A Department has Employee objects, but dissolving the department does not erase the employees from existence. They can move to another department. That independence separates aggregation from composition.
What Is Aggregation
Aggregation means the whole contains references to its parts, but does not own their lifecycles. The parts are created somewhere else, passed in, and continue to exist if the whole is destroyed.
Think of a university department. The department groups professors together under one roof, but the professors existed before the department was formed and will continue to exist if the department is dissolved. The department is a container, not a factory.
Compare this to a House and its Room objects. Demolish the house, and the rooms are gone. That is composition. The room has no independent existence outside the house.
A concise rule is: aggregation holds references to parts managed elsewhere; composition models exclusive ownership of parts whose lifecycle is tied to the whole.
Aggregation is a specialized association
Every aggregation is an association with added "whole-part" semantics. The part can exist independently, but the whole groups parts into a meaningful collection. Think of it as association plus the sentence "is part of."
UML Notation
Aggregation uses a hollow (open) diamond on the "whole" side of the relationship. The diamond sits on the class that acts as the container. The line points toward the part.
Contrast this with the two other diamond types:
| Symbol | UML Name | Meaning | Diamond |
|---|---|---|---|
o-- | Aggregation | Whole groups parts, parts survive | Hollow (open) |
*-- | Composition | Whole owns parts, parts die with whole | Filled (solid) |
--> | Association | Objects reference each other, no ownership | No diamond |
The hollow diamond is the visual cue for aggregation in UML. A filled diamond communicates that the part is treated as exclusively owned and cannot exist independently, which changes the design semantics.
Notice the pattern: University aggregates Department, Department aggregates Professor. Professors can move between departments. They can even belong to two departments simultaneously (cross-listed appointments). Rooms cannot move between buildings. That is the visual shorthand.
Aggregation vs Composition
The distinction comes down to one question: what happens to the part when the whole is removed?
| Dimension | Aggregation | Composition |
|---|---|---|
| UML diamond | Hollow β | Filled β |
| Lifecycle | Part exists independently | Part dies with the whole |
| Who creates the part? | External code, passed in | The whole creates it internally |
| Shared ownership? | Yes, part can belong to multiple wholes | No, part belongs to exactly one whole |
| Deletion cascade | No cascade | Cascade delete |
| Java signal | Constructor receives the part | Constructor creates the part with new |
| Real example | Team has Players (players exist without team) | Invoice has LineItems (line items are meaningless without invoice) |
A practical test is: can you take the part out of the whole and hand it to someone else? If yes, aggregation. If the part makes no sense outside the whole (what is an OrderLine without an Order?), it is composition.
The constructor test is not absolute
Some code creates parts inside the constructor for convenience but still treats them as independent (e.g., a default empty list). The real test is domain intent: does the part have meaning and identity outside the whole? Code structure is a hint, not a proof.
Implementation
// Employee exists independently. It is not created by Department
// and is not destroyed when Department is removed.
// An employee can move between departments freely.
public class Employee {
private final String id;
private final String name;
private String role;
public Employee(String id, String name, String role) {
this.id = id;
this.name = name;
this.role = role;
}
public String getId() { return id; }
public String getName() { return name; }
public String getRole() { return role; }
@Override
public String toString() {
return name + " (" + role + ")";
}
}The key observation in Main.java: setting engineering = null makes the department eligible for garbage collection if no other references remain, but alice and carol remain fully functional. They are not tied to the department's domain lifecycle. Compare this to composition, where child objects are cleaned up or become invalid when the parent is destroyed, depending on the language and persistence mechanism.
I also want to highlight the Professor shared across departments. In composition, a part belongs to exactly one parent. In aggregation, the same part can appear in multiple wholes. Dr. Smith teaches in both CS and Math. That shared ownership is one of the clearest signals that you are looking at aggregation, not composition.
Implementation heuristic: inspect construction
If the "whole" receives the part as a constructor or method parameter, it may indicate aggregation. If the whole calls new Part() internally, it may indicate composition. This is a useful heuristic, not a proof; domain intent decides the relationship.
When to Use Aggregation
So when does aggregation matter in a design?
The part has independent identity. Employees have IDs, names, and careers that exist outside any department. Songs exist outside any playlist. If the part has a primary key or a meaningful identity of its own, aggregation is your default.
The part can be shared. A professor teaches in multiple departments. A song appears in multiple playlists. Composition models exclusive ownership, so shared parts call for aggregation semantics or a plain association instead.
The part's lifecycle is managed elsewhere. Employees are hired by HR, not by the department. Players are signed by the club, not by the team roster. If some other system or process creates and destroys the part, the whole is just holding a reference.
You want loose coupling. Aggregation means the whole can be tested with mock parts. The department does not need to know how to construct an employee. This makes unit testing simpler and keeps dependencies flowing in one direction.
In many real codebases, the distinction between aggregation and plain association is subtle. The practical difference shows up in database design (cascade delete or not), serialization (deep copy or reference), and garbage collection (preventing memory leaks by clearing references). The boundary between aggregation and composition is usually the more consequential distinction.
Common Mistakes
Mistake 1: Confusing aggregation with composition.
"A Team has Player objects, so it must be composition." No. Players exist independently of any team. They can be free agents, traded, or retired. Dissolving the team does not erase the players. The lifecycle independence makes this aggregation.
Mistake 2: Drawing a filled diamond when you mean hollow.
In UML, β (filled) means composition and β (hollow) means aggregation. A filled diamond communicates that the part is treated as having a lifecycle tied to the whole. If you mean aggregation, draw it hollow or explicitly say "the part exists independently."
Mistake 3: Circular aggregation chains.
If A aggregates B and B aggregates A, you have a circular dependency. Neither can be the "whole." This usually means the relationship is plain association, not aggregation. Aggregation implies a directional whole-part hierarchy.
Mistake 4: Treating aggregation as weak composition. Some developers treat aggregation as "composition but the child might survive." That framing is backwards. Aggregation is not weakened composition. It is strengthened association. The part was always independent; the whole just groups it.
Mistake 5: Not clearing references when the whole is done.
If a Department object is removed but its List<Employee> still holds strong references, those employees cannot be garbage collected even if nothing else references them. In languages with garbage collection, clearing the list in a cleanup method prevents subtle memory leaks.
Shared parts and cascade delete
In database terms, aggregation means you should NOT use ON DELETE CASCADE on the foreign key. If you delete the department row, the employee rows must survive. Using cascade delete on an aggregation relationship silently converts it into composition at the database layer, which contradicts your domain model.
Test Your Understanding
Quick Recap
- Aggregation is a "has-a" relationship where the part exists independently of the whole. The whole groups parts but does not own their lifecycles.
- UML notation: hollow (open) diamond
βon the whole side. Filled diamondβis composition, not aggregation. - The lifecycle test: delete the parent. If the child survives and is still meaningful, it is aggregation. If the child is destroyed or meaningless, it is composition.
- In Java, the signal is that the whole receives parts from outside (constructor parameter, setter) rather than creating them with
new. - Aggregated parts can be shared across multiple wholes. A professor in two departments is aggregation. A room in one building is composition.
- In database design, aggregation means no cascade delete. Use
SET NULLorRESTRICTon the foreign key. - In a design discussion, lead with the lifecycle test. It distinguishes aggregation from composition by semantics rather than syntax.
Related OOP Concepts
- Association - The baseline reference relationship; aggregation adds whole-part grouping without lifecycle ownership.
- Composition vs Inheritance - Compares object composition with subclassing and explains the ownership trade-offs.
- Encapsulation - Helps control how aggregated parts are added, removed, and exposed.
Related Articles
Understand association in OOP: how objects reference each other without ownership, with examples of unidirectional, bidirectional, and multiplicity relationships.
Inheritance models IS-A but couples tightly. Composition models HAS-A and stays flexible. Learn when each is right and how to migrate from one to the other.
Encapsulation bundles data with the behavior that controls it. Private fields help enforce invariants when all mutation paths respect the object's rules.