Association, aggregation and composition are the three kinds of has-a relationship between classes. Association means one object knows another, aggregation is an association where a whole groups parts that also live on their own, and composition is an aggregation where the whole owns its parts, so the parts cannot exist without it.
We use the three terms to decide who creates an object, who may share it, and what happens to it when its container goes away. The answers drive the Java code and the database mapping. The following example is a playlist class from a music streaming app that shows all three relationships.
class Playlist {
private final String name;
private final Listener owner; // association: knows its listener
private final List<Entry> entries = new ArrayList<>(); // composition: owns its entries
Playlist(String name, Listener owner) {
this.name = name;
this.owner = owner;
}
void add(Song song) { // aggregation: songs come from the catalog
entries.add(new Entry(song, entries.size() + 1));
}
List<Song> songs() {
return entries.stream().map(Entry::song).toList();
}
private record Entry(Song song, int position) { } // created and kept only by Playlist
}
The playlist creates every Entry itself and never hands one out, while it only refers to songs and to a listener, which exist anyway. Each relationship gets a section with code, and after them we compare the UML notation, map the classes to JPA and weigh composition against inheritance.
1. Association
An association is any lasting link where one object holds a reference to another and uses it. Neither object owns the other, and each one can be created and removed on its own. An association has a direction (unidirectional when only one side holds a reference, bidirectional when both do) and a multiplicity, such as one-to-one, one-to-many, many-to-one or many-to-many.

The following example has two associations. Every Song refers to its Artist (many-to-one, unidirectional), and a Listener follows any number of artists, while an artist has many followers (many-to-many).
record Artist(String name) { }
record Song(String title, Artist artist) { }
class Listener {
private final String name;
private final Set<Artist> following = new LinkedHashSet<>();
Listener(String name) { this.name = name; }
void follow(Artist artist) { following.add(artist); }
void unfollow(Artist artist) { following.remove(artist); }
int followingCount() { return following.size(); }
}
Artist nora = new Artist("Nora");
Listener mia = new Listener("Mia");
mia.follow(nora);
mia.unfollow(nora);
int followed = mia.followingCount(); // 0
String stillThere = nora.name(); // "Nora", unfollowing removes only the link
Unfollowing removes the link and leaves both objects untouched. That independence is the defining feature of a plain association, and it is the right model whenever two things meet but neither is part of the other.
2. Aggregation
Aggregation is an association with a whole-part meaning. The whole groups the parts, but the parts are created elsewhere, can belong to several wholes at once, and outlive the whole. UML calls it shared aggregation.
A playlist and its songs fit this description. The songs live in the app’s catalog, the same song appears in thousands of playlists, and deleting a playlist must not delete a single song.
Listener mia = new Listener("Mia");
Song blue = new Song("Blue", new Artist("Nora"));
Playlist roadTrip = new Playlist("Road trip", mia);
Playlist gym = new Playlist("Gym", mia);
roadTrip.add(blue);
gym.add(blue);
boolean sameSong = roadTrip.songs().get(0) == gym.songs().get(0); // true, one shared Song object
Both playlists point to the same Song object. When the app drops gym, the garbage collector can reclaim the playlist, but blue stays reachable through roadTrip and, in the real app, through the catalog. In code, aggregation and association look the same, a field that holds a reference, so the difference lies in the meaning we document and enforce.
3. Composition
Composition is the strongest form of aggregation, where each part belongs to a single whole, the whole controls the part’s lifetime, and the part has no meaning outside it. UML calls it composite aggregation.
A playlist entry, the record of which song sits at which position, means nothing without its playlist. The Playlist class from the intro enforces composition with three rules, which keep the entries out of reach of other code.
- The whole creates its parts. Callers pass a Song, and add() builds the Entry internally.
- The part type is private, so no other class can create, hold or share an Entry.
- The whole never returns its internal list. The method songs() returns a new unmodifiable list built with Stream.toList().
Playlist chill = new Playlist("Chill", new Listener("Ana"));
chill.add(new Song("Rain", new Artist("Leo")));
List<Song> tracks = chill.songs();
int trackCount = tracks.size(); // 1
boolean added = tracks.add(new Song("Sun", new Artist("Leo"))); // UnsupportedOperationException
Java has no delete operator, so the entries are not destroyed when the playlist is. Once nothing references the playlist, its entries become unreachable too, and the garbage collector reclaims both. Composition in Java is therefore a design rule that encapsulation enforces, and a public getter that returns the internal entries list would turn composition into sharing without any compiler warning.
4. Comparing the Three Relationships
The three relationships form a hierarchy. Every composition is an aggregation, and every aggregation is an association, so the question to ask is how strong the ownership is.
| Point | Association | Aggregation | Composition |
|---|---|---|---|
| Meaning | Uses or knows | Has a, groups parts | Owns, is made of |
| Ownership | None | Weak, parts can be shared | Strong, one owner per part |
| Part lifetime | Independent | Independent of the whole | Bound to the whole |
| Who creates the part | Anyone | Anyone, often outside the whole | The whole |
| UML notation | Plain line, optional arrow | Hollow diamond at the whole | Filled diamond at the whole |
| Music app example | Listener follows Artist | Playlist groups Song | Playlist owns Entry |
| Typical JPA mapping | @ManyToOne or @ManyToMany, no cascade | @ManyToMany, no cascade remove | @OneToMany with cascade = ALL and orphanRemoval = true |
The three relationships are part of the design side of object-oriented programming. UML 2.5.1 names the three kinds none, shared and composite aggregation, and only the last two get a diamond.
5. Mapping the Relationships With JPA
The difference becomes concrete when the objects live in a database, because deleting a row is a real delete. With Jakarta Persistence 3.2 and Hibernate, composition maps to @OneToMany with cascading and orphan removal, so the entries are saved and deleted with their playlist. Aggregation maps to a relationship without cascading removal, so deleting a playlist deletes only the rows of the join table.
@Entity
class PlaylistEntity {
@Id @GeneratedValue
Long id;
@ManyToOne // association: owner is not deleted with the playlist
ListenerEntity owner;
@ManyToMany // aggregation: songs stay in the catalog
List<SongEntity> songs = new ArrayList<>();
@OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL, orphanRemoval = true)
List<EntryEntity> entries = new ArrayList<>(); // composition: entries live and die with the playlist
}
@Entity
class EntryEntity {
@Id @GeneratedValue
Long id;
@ManyToOne
PlaylistEntity playlist;
int position;
}
@Entity
class SongEntity {
@Id @GeneratedValue
Long id;
String title;
}
@Entity
class ListenerEntity {
@Id @GeneratedValue
Long id;
String name;
}
A cascade remove on songs would be a serious bug, because deleting one playlist would delete songs that other playlists still use. The articles on cascade types and orphan removal cover the options in detail.
6. Composition vs Inheritance
Composition also has a second, wider meaning in Java design, building a class from other objects instead of extending a class. The advice to favor composition over inheritance comes from the fact that a subclass inherits every public method of its parent, including the ones that break its rules.
Say we made Playlist extend ArrayList<Song> to save some code. Every caller could call clear(), set() or removeIf() and skip the position numbering. By holding a private list instead, the class exposes only add() and songs(), and it can switch to another collection later without breaking callers. Inheritance stays the right choice for a real is-a relationship, such as a PremiumListener that is a Listener with extra features.
7. Association, Aggregation and Composition FAQs
Design reviews and interviews keep returning to the line between aggregation and composition.
7.1. What is the main difference between aggregation and composition?
Ownership of the part’s lifetime. In aggregation, the part exists independently and can be shared, like a song in many playlists. In composition, the part belongs to one whole and goes away with it, like a playlist entry.
7.2. Is aggregation a has-a relationship?
Yes, and so is composition. Both describe a whole that has parts. Plain association is often described as uses-a, because neither object is part of the other.
7.3. How do we implement composition in Java?
Create the parts inside the whole, keep them in private fields, and never return the internal objects or collections. Return copies or unmodifiable views instead, such as List.copyOf() or Stream.toList().
7.4. Can an aggregated object exist without its container?
Yes. That is the defining property of aggregation. A song exists in the catalog before any playlist adds it, and after every playlist removes it.
7.5. Is composition the same as a nested class?
No. A record or class nested in the whole, as Entry is here, helps hide the part, but composition is about ownership. A top-level class can be a composed part as long as only one whole creates and holds each instance.
8. Conclusion
Association is a link between independent objects, aggregation adds a whole-part meaning with shareable parts, and composition adds exclusive ownership, so the part’s lifetime follows the whole. UML marks them with a plain line, a hollow diamond and a filled diamond.
In Java, the field declarations often look the same for all three, so the relationship lives in how the class creates, hides and exposes its parts. In a database, the same choice decides whether a delete cascades, which makes it worth writing down for every relationship in the model.
9. References
- OMG Unified Modeling Language 2.5.1
- Jakarta Persistence 3.2, OneToMany
- Jakarta Persistence 3.2, CascadeType
- Stream.toList() Javadoc
- Oracle Java Tutorial, Classes
Happy Learning !!
Hi, please write an article about inheritance vs composition vs aggregation and which one is better to use.
Good explanation, but if it were some examples in the form of the code, it would be great.
I have one question – i.e. can we say that tight coupling exist for Composition? because if we delete the parent object then its collabrated objects gets deleted also so can we say that it is tight coupling?
One more – > Car has seats . Is this composition or Aggregation?
One more – > Car has seats . Is this composition or Aggregation?
To ans this question -> this is composition because without car seats can’t be exist. In simple term car contains seats so no car no seats. cheers. :)
But if the Car is spoiled we can delete the car and shift the seats to a new car . So it is Aggregation . I think Engine and Car is more of Composition
yup right
Actually No. It is a matter of implementation in code, rather than concept
If Engine is created by third party code and passed into Car, then both lifecycles are independent, and it is a aggregation ( whole part relationship )
If Engine is created by Car class directly, then Engine’s lifecycle is dependent on Car, and it is a composition (full ownership)