Composition, Aggregation and Association
Introduction
In real life and in programming objects have relationships between each other. Below we will discuss three of them which are quite often mixed up: composition, aggregation, and association.
Composition:
It is a relationship that is often described in words ‘has-a’, ‘belongs-to’ or ‘part-of’. It means that one of the objects is a logical structure which has another object. In other words, one is a part of the other object.
For example class belongs to school or in other words school has a class. So as you see, whether we call it “belongs-to” or “has-a” is only a matter of point of view.
Composition is a restricted form of “has-a” relationship because the containing object owns it. The members or parts that ‘belongs-to’ could not exist without the owning object that has them. So class could not exist without a school.
UML
Code
1
2
3
4
5
public class Room {}
public class School {
private List<Room> rooms;
}
Aggregation:
It is also a ‘has-a’ relationship, but contrary to composition, the fact is that it doesn’t involve owning, so it is a weaker relationship.
For example, imagine a car and its wheels. We can take off the wheels, and they will still exist, independently of the car; moreover they may even be installed in another car.
It is used to assemble the parts to a bigger construct, which is capable of more things than its parts.
UML
Code
1
2
3
4
5
public class Wheel {}
public class Car {
private List<Wheel> wheels;
}
Association:
It is the weakest of those three relationships; none of the objects belong to another, so it isn’t a “has-a” relationship.
Association only means that the objects “know” each other. For example, a mother and her child.
UML
Code
1
2
3
4
5
public class Child {}
public class Mother {
private List<Child> children;
}
A Note on Code Similarities
Looking at the three code examples above, you will notice they all look structurally identical – a class holding a List<> of another class. In Java (and most OOP languages), there is no syntax-level keyword that distinguishes composition from aggregation from association. The difference is semantic, expressed through lifecycle management and intent:
- Composition – the owning class creates and destroys its parts. If
Schoolis deleted, itsRoomobjects cease to exist. In practice, the rooms are typically instantiated inside the constructor or a factory method ofSchool, and there is no public setter that allows external code to swap them out. - Aggregation – the container references parts that can exist independently. A
Carholds references toWheelobjects, but those wheels can be removed and attached to a different car. The wheels are usually passed in from outside (constructor injection or setters). - Association – neither object owns or manages the other’s lifecycle. A
Motherknows about herChildobjects, and those children can also hold a reference back to the mother. Both exist independently.
The way I enforce these semantics in code is through constructor design, visibility of setters, and documentation (or UML). The language itself won’t stop you from violating the contract, so understanding the intent behind each relationship is essential.
Bidirectional vs Unidirectional Association
Associations can be unidirectional or bidirectional:
- Unidirectional – only one class knows about the other. For example, a
Studentmay hold a reference to aUniversity, but theUniversityclass does not store references to individual students. - Bidirectional – both classes reference each other. A
Motherknows about herChild, and theChildalso holds a reference to theMother.
In practice I default to unidirectional associations whenever possible, because bidirectional ones introduce tighter coupling and make it harder to reason about object graphs (and can complicate serialization/ORM mappings).
Dependency – the Weakest Link
There is actually a relationship even weaker than association: dependency. A dependency exists when one class uses another, but only temporarily – for example, as a method parameter or a local variable. The dependent class does not hold a long-lived reference to the other.
1
2
3
4
5
public class ReportGenerator {
public void generate(Printer printer) { // dependency: used, not stored
printer.print("Report content");
}
}
In UML, dependency is drawn as a dashed arrow. It signals the weakest possible coupling: ReportGenerator knows about Printer only for the duration of the generate call.
The full spectrum from weakest to strongest: Dependency < Association < Aggregation < Composition.


