Association, Aggregation and Composition in Java (with UML)

Association, aggregation and composition in Java with UML notation, code that enforces ownership, a comparison table, JPA cascade mapping and FAQs.

UML class diagram of a music app with an association between Listener and Artist, an aggregation between Playlist and Song, and a composition between Playlist and Entry

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.

UML class diagram of a music app with an association between Listener and Artist, an aggregation between Playlist and Song, and a composition between Playlist and Entry
A plain line is an association, a hollow diamond marks aggregation, and a filled diamond marks composition at the owner’s end

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.

PointAssociationAggregationComposition
MeaningUses or knowsHas a, groups partsOwns, is made of
OwnershipNoneWeak, parts can be sharedStrong, one owner per part
Part lifetimeIndependentIndependent of the wholeBound to the whole
Who creates the partAnyoneAnyone, often outside the wholeThe whole
UML notationPlain line, optional arrowHollow diamond at the wholeFilled diamond at the whole
Music app exampleListener follows ArtistPlaylist groups SongPlaylist 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

Happy Learning !!

Source Code on Github

Leave a Comment

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

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

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.