Association, aggregation and composition
Association, aggregation and composition defined by lifecycle and ownership, written in Java (Order, Team, Doctor), with multiplicity, navigability, UML diamonds, what the garbage collector sees, and the LLD interview angle.
On this page
🔗 What are association, aggregation and composition?
They are three strengths of the "has-a" relationship between objects. Association means two objects know about each other and work together, like a Doctor and a Patient. Aggregation is a whole that groups parts which can live on without it, like a Team and its Players. Composition is a whole that owns its parts outright, like an Order and its OrderLines: delete order ORD-1001 and its three lines have no reason to exist.
Interviewers ask this in two places. In a core Java round, it's a quick check that you can define the terms precisely and say what decides which one you have (hint: lifecycle and ownership, not the syntax). In a low-level design round, it's everywhere: the moment you draw a class diagram for a parking lot or a food-delivery app, the interviewer reads your arrows and diamonds and asks "who owns this?" and "what happens to these when that's deleted?".
What you’ll learn
- Define association, aggregation and composition by lifecycle and sharing, not by syntax
- Write composition in Java that holds: the whole creates its parts and leaks nothing mutable
- Draw the UML correctly: hollow vs filled diamond at the whole, multiplicity like
1..*, navigability arrows - Keep bidirectional associations in sync, or replace them with an association class like
Appointment - Explain why the GC knows only reachability, so ownership in Java is a discipline you enforce
- Use these words to drive lifecycle questions in an LLD round
For this lesson, we'll focus on what each relationship means in UML and in plain English, how each one looks in Java code, directionality and multiplicity, how Java's garbage collector sees (and doesn't see) ownership, and how to use all of this in an LLD interview.
Below the line (out of scope)
- Is-a vs has-a, and why to prefer composition over inheritance: covered in Inheritance vs composition
- Copying object graphs safely: covered in Deep copy vs shallow copy
- JPA mapping annotations in depth (we mention
cascadeandorphanRemovalonly as a bridge)
The 30-second answer
All three are has-a relationships, ordered by how tightly the lifecycles are bound. Association is the general "uses or knows" link with independent lifecycles: a doctor and a patient. Aggregation is a whole-part link where parts are shared and outlive the whole: a team and its players, drawn with a hollow diamond at the whole. Composition is exclusive ownership where the whole creates, controls and destroys its parts: an order and its lines, drawn with a filled diamond. Java syntax is the same for all three, a field holding a reference. The difference is the contract you enforce: who creates the part, whether references to it escape, and whether it's deleted with the whole.
Interview radar
Asked very often: Expect definitions of association, aggregation and composition, the correct UML diamond, and the lifecycle rule behind each term. Senior rounds probe: Strong rounds test multiplicity, navigability, bidirectional consistency, association classes and the fact that Java fields do not encode ownership. Production angle: This connects to cascade deletes, orphan rows, heap leaks from leaked references and object graphs that serialize or recurse forever.
01Picture it first: a house, a football club and a clinic
A house and its rooms is composition. The rooms are built as part of the house, belong to exactly one house, and when the house is demolished, the rooms go with it. A football club and its players is aggregation. The club groups them, but players existed before they signed and will play for another club after. A doctor and a patient is plain association. They meet, they know each other, neither owns the other, and either can exist without the other.
UML gives each one its own line ending, and the diagram below is the notation you'll draw in an interview.
UML notation for three relationships: Doctor to Patient as a plain association line with many-to-many multiplicity, Team to Player with a hollow diamond at Team for aggregation, and Order to OrderLine with a filled diamond at Order for composition, each with a short meaning on the right
Notice three things:
- The diamond sits at the whole, never at the part. Hollow means shared, filled means owned.
- Multiplicity goes at each end:
1order has1..*lines; each line belongs to exactly1order. A composed part can belong to at most one whole at a time. - An arrowhead on the line marks navigability: which side can reach the other in code. No arrowheads usually means "not specified" or bidirectional.
There's also a weaker cousin you'll sometimes draw: dependency, a dashed arrow, for "uses temporarily", such as a method parameter (checkout.pay(gateway)) that's never stored in a field.
02Under the hood: what each one looks like in Java
Java has no owns or shares keyword. In all three cases, you write a field that holds a reference. The relationship lives in how you create, expose and release that reference. Let's write each one properly.
The diagram below separates the three relationships by the two questions that matter most in code: sharing and lifecycle.
A lifecycle map contrasts association as independent uses links, aggregation as a shared whole-part link with a hollow diamond, and composition as exclusive owned parts with a filled diamond and delete cascade
Composition: Order owns its OrderLines
Try it yourself first
Before reading on: list three things Order must do in code so that its lines are truly owned and can't be changed or kept alive by outsiders.
public final class Order {
private final String id;
private final List<OrderLine> lines = new ArrayList<>();
public Order(String id) { this.id = id; }
public void addLine(String sku, int quantity, BigDecimal unitPrice) {
lines.add(new OrderLine(sku, quantity, unitPrice)); // the whole creates its parts
}
public List<OrderLine> lines() { return Collections.unmodifiableList(lines); }
public BigDecimal total() {
return lines.stream().map(OrderLine::subtotal).reduce(BigDecimal.ZERO, BigDecimal::add);
}
public record OrderLine(String sku, int quantity, BigDecimal unitPrice) {
public OrderLine {
if (quantity <= 0) throw new IllegalArgumentException("quantity must be positive");
}
BigDecimal subtotal() { return unitPrice.multiply(BigDecimal.valueOf(quantity)); }
}
}
var order = new Order("ORD-1001");
order.addLine("SKU-1", 2, new BigDecimal("499.00"));
order.addLine("SKU-7", 1, new BigDecimal("1299.00"));
System.out.println(order.total()); // prints: 2297.00
order.lines().clear(); // throws UnsupportedOperationException
Notice how ownership is enforced:
- The whole creates the parts. Callers pass data (
sku,quantity,price), notOrderLineobjects they might keep and mutate. - Nothing mutable escapes. The list is exposed read-only, and
OrderLineis an immutable record, so handing out a line is harmless. - The part makes no sense alone.
OrderLineis nested insideOrder, so even its name says "this belongs to an order".
Aggregation: Team groups Players
final class Player {
private final String name;
Player(String name) { this.name = name; }
@Override public String toString() { return name; }
}
final class Team {
private final String name;
private final Set<Player> squad = new LinkedHashSet<>();
Team(String name) { this.name = name; }
void sign(Player p) { squad.add(p); } // part created elsewhere, passed in
void release(Player p) { squad.remove(p); } // removing does not destroy the player
Set<Player> squad() { return Set.copyOf(squad); }
}
Player virat = new Player("Virat");
Team rcb = new Team("RCB");
Team india = new Team("India");
rcb.sign(virat);
india.sign(virat); // the same Player object in two teams
rcb = null; // the team is gone...
System.out.println(india.squad()); // prints: [Virat] ...the player isn't
The structural difference from Order is small but decisive: the part is created outside and passed in, it can sit in more than one whole, and the whole's API talks about signing and releasing, never deleting.
Association: Doctor and Patient
Association has no whole and no part. A doctor treats many patients and a patient sees many doctors (many-to-many). If both sides need to navigate to the other, the association is bidirectional, and you must keep both sides in sync:
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Real world: the garbage collector doesn't know about ownership
- Advanced: edge cases interviewers probe
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (6)
- OMG Unified Modeling Language 2.5.1, §9.5.3 and §11.5 (aggregation kinds)
- Martin Fowler: UML Distilled (aggregation and composition)
- JLS §12.6 Finalization of Class Instances (reachability)
- HotSpot Virtual Machine Garbage Collection Tuning Guide (Java 21)
- Jakarta Persistence 3.1: OneToMany cascade and orphanRemoval
- Baeldung: Composition, Aggregation, and Association in Java