Classes rarely work alone. A class relationship describes how one class is linked to another, and how much it depends on it.
It answers three plain questions. Is one object a kind of the other? Does it hold the other as a part? Or does it only use the other for a moment?
This note covers the is-a / has-a / uses-a vocabulary, what each relationship means, how it is drawn in UML, how it looks in Java code, and how the same idea shows up in database tables.
New to class diagram boxes (visibility markers, interfaces, abstract classes, enums)? Read UML Class Diagrams first. This note is about the lines between the boxes.
Table of Contents
- The five relationships in one picture
- IS-A, HAS-A and USES-A
- UML notation: how each line is drawn
- Multiplicity: the numbers on the line
- Association
- Aggregation
- Composition
- Dependency (uses-a)
- Aggregation vs Composition: the "who calls new" test
- Full comparison table
- How these look in database tables
- Worked example: Library, Book, Chapter
- Bigger example: E-commerce order
- More real world examples
- Common mistakes
- How to decide which one to use
- Quiz with answers
- Cheat sheet
1. The five relationships in one picture
Think of them as a ladder. The further right you go, the tighter two classes are stuck together.
WEAKEST STRONGEST
| |
v v
Dependency -> Association -> Aggregation -> Composition -> Inheritance
"uses-a" "knows-a" "has-a" "owns-a" "is-a"
Method param Field holding Field holding Field holding extends /
or local var another object parts that can parts that die implements
only long term live alone with the whole
\_____________ all three are HAS-A ______________/
The words under each box are the phrases people say. "Knows-a" and "owns-a" are just weaker and stronger flavours of has-a, because all three store the other object as a field. More on that in the next section.
Quick sentence for each:
| Relationship | Sentence you can say out loud | Example |
|---|---|---|
| Dependency | "I use it for a moment, then forget it." | Printer uses Doc |
| Association | "I keep a reference to it and can talk to it." | Teacher and Student |
| Aggregation | "It is part of me, but it was fine before me." | Department, Employee |
| Composition | "It is part of me, and it dies when I die." | House, Room |
| Inheritance | "I am a special kind of it." | Dog is an Animal |
Association, aggregation and composition are the three this note focuses on. Aggregation is a special case of association. Composition is a stricter case of aggregation. They are one idea getting tighter, not three separate things.
+-------------------------------------------------------+
| ASSOCIATION |
| (any long term link between two classes) |
| |
| +-----------------------------------------+ |
| | AGGREGATION | |
| | (whole-part, parts can live alone) | |
| | | |
| | +-----------------------------+ | |
| | | COMPOSITION | | |
| | | (whole-part, parts die | | |
| | | with the whole) | | |
| | +-----------------------------+ | |
| +-----------------------------------------+ |
+-------------------------------------------------------+
2. IS-A, HAS-A and USES-A
Before the UML symbols, get this vocabulary right. Interviewers often ask "is this is-a or has-a?", so keep this framing in mind.
+----------------------------+
| How is A related to B? |
+----------------------------+
| | |
"A is a B" "A has a B" "A uses a B"
| | |
v v v
INHERITANCE ASSOCIATION DEPENDENCY
extends / AGGREGATION method
implements COMPOSITION parameter
IS-A
A is-a B means A is a special kind of B. You get it with extends or implements. A gets all of B's behaviour. Anywhere the code expects a B, you can pass an A.
class Animal { }
class Dog extends Animal { } // Dog IS-A Animal
interface PaymentMethod { void pay(double amount); }
class CreditCard implements PaymentMethod { } // CreditCard IS-A PaymentMethodSanity check: say the sentence out loud. "A dog is an animal" sounds fine. "A car is an engine" sounds wrong, so Car extends Engine is wrong.
HAS-A
A has-a B means A holds B as a field and uses it to do its job. A does not extend B. This is the umbrella term: association, aggregation and composition are all has-a relationships. They differ only in how strongly A owns B.
class Car {
private final Engine engine; // Car HAS-A Engine
}That is the key point people miss. Has-a is not a fourth relationship sitting next to the other three. It is the family name.
HAS-A
(A holds B as a field and uses it)
|
+-------------------+-------------------+
| | |
ASSOCIATION AGGREGATION COMPOSITION
plain link weak ownership strong ownership
no whole-part part can live part dies with
without the whole the whole
| | |
Teacher has a Library has a Car has an
Student list Book list Engine
So when someone says "use has-a here", they mean "hold it as a field instead of extending it". Which of the three you actually pick depends on ownership and lifecycle (who creates the part, and what happens to it when the whole is deleted). The rest of this note is about that.
USES-A
A uses-a B means A touches B only inside a method and does not keep it. That is dependency.
class ReportService {
void print(Report report) { // ReportService USES-A Report
System.out.println(report.render());
}
}The three in one table
| Phrase | Relationship type | Java shape | Question to ask | Example |
|---|---|---|---|---|
| is-a | Inheritance | extends / implements | Is A a special kind of B? | Dog is an Animal |
| has-a | Association, aggregation, composition | B is a field inside A | Does A hold a B long term? | Car has an Engine |
| uses-a | Dependency | B is a method parameter or local | Does A only borrow B briefly? | Service uses a DTO |
Same two classes, all three phrases
To make it concrete, here is Engine used three different ways:
// IS-A: true for a kind of engine (but Car extends Engine would be wrong)
class ElectricEngine extends Engine { } // an electric engine IS an engine
// HAS-A: Car keeps an Engine as a field
class Car {
private final Engine engine;
Car(String model) {
this.engine = new Engine(model); // created inside, so composition
}
void start() {
engine.ignite();
}
}
// USES-A: the mechanic does not own an engine, one is handed to him per job
class Mechanic {
void service(Engine engine) { // parameter only
engine.checkOilLevel();
}
}Why "prefer has-a over is-a"
You will hear "favour composition over inheritance". In this vocabulary it means: prefer has-a unless is-a is clearly true. Reasons:
- You can swap a has-a part while the program runs. A
Carcan be given a differentEngine. A subclass cannot change its parent once the code is compiled. - Has-a only exposes what you choose to expose. Inheritance drags in every public method of the parent, whether it makes sense or not.
- In a deep inheritance chain, a change in the base class can break every class below it. With has-a, a change breaks far fewer classes.
Longer version with examples in CompositionVsInheritance.md.
3. UML notation: how each line is drawn
This is the part people usually forget in interviews. The shape at the end of the line tells you the relationship.
| Relationship | Drawn as | Diamond sits on | Mermaid syntax |
|---|---|---|---|
| Association | plain line, arrow if one direction | nothing | A --> B |
| Aggregation | line with a hollow (empty) diamond | the whole | A o-- B |
| Composition | line with a filled (black) diamond | the whole | A *-- B |
| Dependency | dashed line with open arrow | nothing | A ..> B |
| Inheritance | solid line with hollow triangle | the parent | A <|-- B |
| Implements | dashed line with hollow triangle | the interface | A <|.. B |
Drawn by hand:
Association Teacher ------------ Student (plain line)
Aggregation Department <>------- Employee (hollow diamond on Department)
Composition House <#>----------- Room (filled diamond on House)
Dependency Printer - - - - - -> Document (dashed arrow)
Inheritance Animal <|----------- Dog (hollow triangle on Animal)
The rule to remember: the diamond always goes on the "whole" side, the side that contains the other one. Empty diamond means it borrows the part. Filled diamond means it owns the part.
All of them together in one diagram:
4. Multiplicity: the numbers on the line
Next to each end of the line you write how many objects can be there. This is called multiplicity.
| Notation | Meaning | Example in words | In Java |
|---|---|---|---|
1 | exactly one | An order has exactly one customer | Customer customer; |
0..1 | zero or one, optional | A person may or may not have a passport | Passport passport; (can be null) |
* | zero or more | A playlist can have any number of songs | List<Song> songs; |
1..* | one or more | An order must have at least one item | List<Item> items; (never empty) |
0..* | same as * | - | List<X> xs; |
2..5 | a fixed range | A car has 2 to 5 doors | List<Door> doors; + check |
How it is written on the diagram:
Department Employee
+----------+ 1 0..* +----------+
| |<>--------------------| |
+----------+ +----------+
Read it as: one Department has zero or more Employees.
In Mermaid you put the numbers in quotes:
5. Association
Association just means two classes know about each other. One holds a reference (a field that points to the other object) and can call its methods. There is no owner and no part. They are just linked.
Real life: a Teacher teaches Students. Neither owns the other. Delete the teacher and the students still exist.
5.1 One-to-one
One object of A is linked to exactly one object of B.
class Passport {
private final String number;
Passport(String number) {
this.number = number;
}
String getNumber() {
return number;
}
}
class Person {
private final String name;
private Passport passport; // one person, one passport
Person(String name) {
this.name = name;
}
void assignPassport(Passport passport) {
this.passport = passport;
}
}5.2 One-to-many
One object of A is linked to many objects of B.
class Teacher {
private final String name;
private final List<Student> students = new ArrayList<>();
Teacher(String name) {
this.name = name;
}
void addStudent(Student student) {
students.add(student);
}
}
class Student {
private final String name;
Student(String name) {
this.name = name;
}
}Notice the students are created outside the Teacher. That is the giveaway that nobody owns anybody here.
5.3 Many-to-many
Many A objects link to many B objects, both ways.
class Student {
private final String name;
private final List<Course> courses = new ArrayList<>();
Student(String name) {
this.name = name;
}
void enroll(Course course) {
courses.add(course);
course.addStudent(this); // keep both sides in sync
}
}
class Course {
private final String title;
private final List<Student> students = new ArrayList<>();
Course(String title) {
this.title = title;
}
void addStudent(Student student) {
students.add(student);
}
}Warning for many-to-many: if you update only one side, the two lists disagree and you get bugs that are painful to find. Always update both sides in one method, like enroll above does.
5.4 Direction
Association can be one way or two way.
Unidirectional: Order -------> Customer
Order knows its Customer.
Customer does not know its Orders.
Bidirectional: Student <------> Course
Both sides hold a reference to the other.
Prefer unidirectional when you can. Fewer references means fewer things to keep in sync.
6. Aggregation
Aggregation is a whole-part relationship where the part can live on its own. The whole is the bigger thing (Department). The parts are the smaller things inside it (Employees).
The classic one: a Department has Employees. If the Department is shut down, the employees are still people. They can move to another department.
class Employee {
private final String name;
Employee(String name) {
this.name = name;
}
String getName() {
return name;
}
}
class Department {
private final String name;
private final List<Employee> employees;
// Employees are created outside and passed in.
// The department only borrows them.
Department(String name, List<Employee> employees) {
this.name = name;
this.employees = employees;
}
void addEmployee(Employee employee) {
employees.add(employee);
}
}Showing that the part survives:
Employee rizon = new Employee("Rizon");
Employee aisha = new Employee("Aisha");
Department backend = new Department("Backend", new ArrayList<>(List.of(rizon, aisha)));
backend = null; // department is gone
System.out.println(rizon.getName()); // still prints "Rizon", the object is aliveSigns you are looking at aggregation
- The parts are created somewhere else and handed to the whole (constructor parameter or setter).
- The same part object can belong to more than one whole at the same time, or move between them.
- Deleting the whole should not delete the parts.
7. Composition
Composition is the strict version. The whole owns the parts. When the whole is destroyed, the parts go with it. A part belongs to exactly one whole and cannot be shared.
The classic one: a House has Rooms. Demolish the house and there is no "Living Room" floating around anywhere.
class Room {
private final String name;
private final double areaSqFt;
Room(String name, double areaSqFt) {
this.name = name;
this.areaSqFt = areaSqFt;
}
String getName() {
return name;
}
}
class House {
private final String address;
private final List<Room> rooms = new ArrayList<>();
House(String address) {
this.address = address;
// The house creates its own rooms. Nobody outside can hand them in.
rooms.add(new Room("Living Room", 320));
rooms.add(new Room("Bedroom", 240));
}
// Return copies of the data, not the Room objects themselves.
List<String> getRoomNames() {
return rooms.stream().map(Room::getName).toList();
}
}Now the rooms cannot outlive the house, because nothing outside the house ever gets a reference to a Room. When nothing refers to the House any more, Java can clean it up, and its Rooms go with it.
Signs you are looking at composition
- The whole creates the parts itself, usually inside the constructor (
newis inside). - The part is never handed out to the outside world, or only a copy is handed out.
- A part cannot be shared between two wholes.
- Deleting the whole must delete the parts, otherwise you have orphan data.
8. Dependency (uses-a)
Worth knowing because people mix it up with association. Dependency is the weakest link. Class A uses class B only inside a method, as a parameter, local variable or return type. A does not store B.
class ReportService {
// Report is used only here, not stored as a field.
void print(Report report) {
System.out.println(report.render());
}
}Rule of thumb: field means association, parameter means dependency.
9. Aggregation vs Composition: the "who calls new" test
This is the fastest way to tell them apart in code.
| Question | Aggregation | Composition |
|---|---|---|
| Who creates the part? | Someone outside | The whole itself |
Where is new Part()? | In the caller | Inside the whole's constructor |
| Can the part be shared with another whole? | Yes | No |
| Does the part survive the whole? | Yes | No |
| Would you cascade-delete it in a database? | No | Yes |
Side by side:
// AGGREGATION: parts come from outside, so they exist before and after
class Playlist {
private final List<Song> songs;
Playlist(List<Song> songs) { // handed in
this.songs = songs;
}
}
Song song = new Song("Kesariya");
Playlist p1 = new Playlist(new ArrayList<>(List.of(song)));
Playlist p2 = new Playlist(new ArrayList<>(List.of(song))); // same song in two playlists, fine
// COMPOSITION: parts are born inside, so they die inside
class Car {
private final Engine engine;
Car(String model) {
this.engine = new Engine(model); // created here, owned here
}
}
Car car = new Car("Swift");
// There is no way to take this engine and put it in another Car.10. Full comparison table
| Aspect | Association | Aggregation | Composition |
|---|---|---|---|
| Meaning | "knows-a" | "has-a", weak | "part-of", strong |
| Strength | General link | Weak whole-part | Strong whole-part |
| Ownership | None | Whole holds the part but does not own it | Whole owns the part |
| Lifecycle | Fully independent | Part survives the whole | Part dies with the whole |
| Who creates the part | Anyone | Outside the whole | The whole itself |
| Can part be shared | Yes | Yes | No |
| UML symbol | Plain line | Hollow diamond | Filled diamond |
| Mermaid | A --> B | A o-- B | A *-- B |
| Java hint | Field, set from outside | Field, injected | Field, new in constructor |
| Database delete rule | Just remove the link | ON DELETE SET NULL | ON DELETE CASCADE |
| Example | Teacher and Student | Department and Employee | House and Room, Car and Engine |
| Other examples | Doctor and Patient | Library and Book, Team and Player | Order and OrderItem, Book and Chapter |
11. How these look in database tables
The same three ideas show up when you design tables. This is usually the clearest way to see the difference, because the delete rule makes ownership obvious.
11.1 One-to-one association
A foreign key (FK) is a column that holds the id of a row in another table. Put one on either side and mark it unique, so no two rows can share a value.
persons passports
+----+---------+ +----+------------+-----------+
| id | name | | id | number | person_id | <- UNIQUE
+----+---------+ +----+------------+-----------+
| 1 | Rizon | | 9 | X1234567 | 1 |
| 2 | Aisha | | 10 | Y7654321 | 2 |
+----+---------+ +----+------------+-----------+
CREATE TABLE passports (
id BIGINT PRIMARY KEY,
number VARCHAR(20) NOT NULL,
person_id BIGINT UNIQUE REFERENCES persons(id)
);11.2 One-to-many association or aggregation
The "many" side holds the foreign key. For aggregation the FK is nullable (it may be empty), because the employee stays alive when the department is deleted.
departments employees
+----+-----------+ +----+--------+---------------+
| id | name | | id | name | department_id | <- nullable FK
+----+-----------+ +----+--------+---------------+
| 1 | Backend | | 5 | Rizon | 1 |
| 2 | Design | | 6 | Aisha | 1 |
+----+-----------+ | 7 | Karan | NULL | <- no department, still an employee
+----+--------+---------------+
CREATE TABLE employees (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
department_id BIGINT NULL REFERENCES departments(id) ON DELETE SET NULL
);ON DELETE SET NULL is aggregation written in SQL. Drop the department, keep the people.
11.3 Many-to-many association
You cannot do it with two tables. You need a third table in the middle, called a join table or link table.
students student_course courses
+----+--------+ +------------+-----------+ +----+----------+
| id | name | | student_id | course_id | | id | title |
+----+--------+ +------------+-----------+ +----+----------+
| 1 | Rizon | | 1 | 100 | |100 | DSA |
| 2 | Aisha | | 1 | 101 | |101 | DBMS |
+----+--------+ | 2 | 100 | +----+----------+
+------------+-----------+
PK (student_id, course_id)
CREATE TABLE student_course (
student_id BIGINT REFERENCES students(id),
course_id BIGINT REFERENCES courses(id),
PRIMARY KEY (student_id, course_id)
);Row (1, 100) means Rizon is in DSA. Rizon appears twice because he takes two courses. DSA appears twice because two students take it. That is many-to-many.
11.4 Composition
The child table has a NOT NULL foreign key (it can never be empty) and a cascade delete (deleting the parent row deletes its child rows). A child row cannot exist without its parent.
orders order_items
+----+------------+ +----+----------+----------+-----+
| id | placed_on | | id | order_id | sku | qty | <- NOT NULL FK
+----+------------+ +----+----------+----------+-----+
| 70 | 2026-08-16 | | 1 | 70 | SHOE-42 | 1 |
+----+------------+ | 2 | 70 | SOCK-M | 3 |
+----+----------+----------+-----+
Delete order 70 -> both item rows are deleted automatically.
CREATE TABLE order_items (
id BIGINT PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
sku VARCHAR(40) NOT NULL,
qty INT NOT NULL
);Summary of the table rules
| Relationship | FK lives on | FK nullable | Delete rule | Extra table needed |
|---|---|---|---|---|
| One-to-one association | Either side | Yes | SET NULL | No |
| One-to-many aggregation | The many side | Yes | SET NULL | No |
| Many-to-many | Join table | No | Delete link row | Yes |
| Composition | The part side | No | CASCADE | No |
If you can point at a table and say "these rows are meaningless without the parent row", that is composition.
12. Worked example: Library, Book, Chapter
One class can be in different relationships at the same time. This is the standard example.
- A Library aggregates Books. Burn down the library and the books still exist as objects, they can be moved to another library.
- A Book composes Chapters. Delete the book and its chapters mean nothing.
import java.util.*;
// PART owned by Book. Package private, so code in other packages cannot build one.
// (Demo below is in the same package, but it never does.)
class Chapter {
private final String title;
private final int pages;
Chapter(String title, int pages) {
this.title = title;
this.pages = pages;
}
String getTitle() {
return title;
}
int getPages() {
return pages;
}
}
class Book {
private final String title;
private final List<Chapter> chapters = new ArrayList<>();
Book(String title) {
this.title = title;
}
// COMPOSITION: the Book creates the Chapter. The caller only sends data.
void addChapter(String chapterTitle, int pages) {
chapters.add(new Chapter(chapterTitle, pages));
}
// Never hand out the real Chapter objects or the real list.
List<String> getTableOfContents() {
List<String> toc = new ArrayList<>();
for (Chapter chapter : chapters) {
toc.add(chapter.getTitle() + " (" + chapter.getPages() + " pages)");
}
return toc;
}
String getTitle() {
return title;
}
}
class Library {
private final String name;
private final List<Book> books = new ArrayList<>();
Library(String name) {
this.name = name;
}
// AGGREGATION: the Book is created outside and only borrowed.
void addBook(Book book) {
books.add(book);
}
void removeBook(Book book) {
books.remove(book); // the Book object is still alive for the caller
}
}
public class Demo {
public static void main(String[] args) {
Book book = new Book("Clean Code");
book.addChapter("Meaningful Names", 22);
book.addChapter("Functions", 30);
Library cityLibrary = new Library("City Library");
cityLibrary.addBook(book);
cityLibrary.removeBook(book);
// Book survives, because Library only aggregated it.
System.out.println(book.getTitle()); // Clean Code
System.out.println(book.getTableOfContents()); // chapters still there
// Chapters cannot survive without the Book: there is no reference to them
// anywhere outside, so when `book` goes away they go away too.
}
}13. Bigger example: E-commerce order
This one has all three relationships in a single diagram, which is what a real design looks like.
Why each one is what it is:
| Link | Type | Reason |
|---|---|---|
| Order to Customer | Association | The customer existed before the order and stays after. Nobody owns anybody. |
| Customer to Address | Aggregation | An address book entry can be reused across orders and moved around. |
| Order to Address | Aggregation | The order only points at an address the customer already saved. |
| Order to OrderItem | Composition | A line item makes no sense without its order. Delete order, delete items. |
| Order to Payment | Composition | The payment record belongs to that one order only. |
| OrderItem to Product | Dependency | It only reads product info when needed, it does not store the Product. |
import java.util.*;
class Customer {
private final String id;
private final String name;
Customer(String id, String name) {
this.id = id;
this.name = name;
}
String getName() {
return name;
}
}
class Address {
private final String line1;
private final String city;
private final String pincode;
Address(String line1, String city, String pincode) {
this.line1 = line1;
this.city = city;
this.pincode = pincode;
}
}
class OrderItem {
private final String sku;
private final int quantity;
private final double unitPrice;
// package private on purpose: other packages cannot build one, and only Order does here
OrderItem(String sku, int quantity, double unitPrice) {
this.sku = sku;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
double lineTotal() {
return quantity * unitPrice;
}
}
class Payment {
private final String txnId;
private final double amount;
Payment(String txnId, double amount) {
this.txnId = txnId;
this.amount = amount;
}
}
class Order {
private final String orderId;
private final Customer customer; // association, passed in
private final Address shippingAddress; // aggregation, reused from customer's list
private final List<OrderItem> items = new ArrayList<>(); // composition, built here
private Payment payment; // composition, built here
Order(String orderId, Customer customer, Address shippingAddress) {
this.orderId = orderId;
this.customer = customer;
this.shippingAddress = shippingAddress;
}
// Composition: Order creates the item, caller never sees the constructor
void addItem(String sku, int quantity, double unitPrice) {
items.add(new OrderItem(sku, quantity, unitPrice));
}
void pay(String txnId) {
this.payment = new Payment(txnId, total());
}
double total() {
double sum = 0;
for (OrderItem item : items) {
sum += item.lineTotal();
}
return sum;
}
}Matching tables:
customers addresses orders
+----+-------+ +----+-------------+ +----+----------+-------------+
| id | name | | id | customer_id | | id | customer | address_id |
+----+-------+ +----+-------------+ +----+----------+-------------+
| 1 | Rizon | | 8 | 1 | | 70 | 1 | 8 |
+----+-------+ +----+-------------+ +----+----------+-------------+
nullable FK customer_id NOT NULL, no cascade
order_items payments
+----+----------+---------+-----+ +----+----------+--------+
| id | order_id | sku | qty | | id | order_id | amount |
+----+----------+---------+-----+ +----+----------+--------+
| 1 | 70 | SHOE-42 | 1 | | 3 | 70 | 4999 |
+----+----------+---------+-----+ +----+----------+--------+
order_id NOT NULL, ON DELETE CASCADE order_id NOT NULL, ON DELETE CASCADE
Delete order 70 and the item and payment rows go with it. The customer and address rows stay.
14. More real world examples
| Pair | Relationship | Why |
|---|---|---|
| Car and Engine | Composition | The engine is built into that car, not shared |
| Human and Heart | Composition | Same idea |
| Order and OrderItem | Composition | A line item is meaningless alone |
| Book and Chapter | Composition | Chapter dies with the book |
| House and Room | Composition | Rooms do not exist after demolition |
| Bank Account and Transaction | Composition | A transaction belongs to one account only |
| Question and Options (MCQ) | Composition | Options belong to that question |
| Library and Book | Aggregation | Books can be moved to another library or sold |
| University and Student | Aggregation | A student exists before joining and after leaving |
| Team and Player | Aggregation | Players get transferred between teams |
| Playlist and Song | Aggregation | One song can be in many playlists |
| Company and Employee | Aggregation | People change jobs |
| Computer and USB Mouse | Aggregation | Unplug it and use it on another machine |
| Teacher and Student | Association | They interact, neither contains the other |
| Doctor and Patient | Association | Same |
| Driver and Car | Association | The driver uses the car but is not part of it |
| Student and Course | Association | Many-to-many, no ownership |
| Controller and Service | Association | Injected reference, both live independently |
| Service and DTO parameter | Dependency | Used inside a method only |
A useful test for the tricky ones: ask what happens to the part when you delete the whole. If the part still makes sense on its own, it is aggregation or association. If the part becomes garbage data, it is composition.
Car and Engine trips people up. In the real world you can swap an engine, so people say aggregation. But in most software models the engine object is created with the car and never shared, so it is drawn as composition. Model what your system actually does, not what is theoretically possible in a garage.
15. Common mistakes
1. Saying it is composition but leaking the parts.
Leaking means outside code gets the real parts, so it can keep them alive after the whole is gone.
// Looks like composition, but is not
class House {
private final List<Room> rooms = new ArrayList<>();
House() {
rooms.add(new Room("Bedroom"));
}
List<Room> getRooms() {
return rooms; // BAD: caller can keep this list and the Rooms forever
}
}
List<Room> escaped = new House().getRooms();
// house is gone, rooms are still alive. Ownership is broken.Fix it by returning a copy, or by returning read-only data:
List<Room> getRooms() {
return List.copyOf(rooms); // caller cannot add or remove
}Even better, do not return the Room objects at all, return the strings or a small record (a short, read-only data class).
2. Using inheritance where composition fits.
class Car extends Engine is wrong. A car is not an engine, a car has an engine. If your sentence sounds silly with "is a", use composition. See CompositionVsInheritance.md.
3. Bidirectional links that drift apart.
If student.enroll(course) adds to the student list but forgets course.addStudent(student), the two sides disagree. Always change both in one method.
4. Cascade delete on an aggregation.
Putting ON DELETE CASCADE on employees.department_id means deleting a department deletes the people. That is a data loss bug, not a design choice.
5. Calling everything association because it is safe.
It is technically true, since aggregation and composition are both associations. But in a design interview it shows you cannot tell ownership apart, which is the whole point of the question.
16. How to decide which one to use
Does class A only use B inside a method, without storing it?
YES -> Dependency
NO |
v
Is A a special kind of B?
YES -> Inheritance (extends / implements)
NO |
v
Is B a part of A? (would you say "A has a B" in a whole-part sense)
NO -> Association
YES |
v
If A is deleted, should B be deleted too?
NO -> Aggregation (hollow diamond, o--, FK nullable, SET NULL)
YES -> Composition (filled diamond, *--, FK not null, CASCADE)
17. Quiz with answers
Q1. Which best describes association in OOP?
Answer: a general link between two classes. Objects of one class hold references to objects of the other and can use them. Nobody owns anybody. It can be one-to-one, one-to-many or many-to-many, and each object has its own lifecycle. In code, one class has a field that refers to the other.
Q2. In an aggregation relationship, what happens to the part objects when the whole is destroyed?
Answer: nothing. They keep existing. Aggregation is a weak link, so the parts have their own lifecycle. Delete a Department and the Employee objects are still there.
Q3. Which of the following is true about composition?
- The composed object and its parts can exist independently. (Wrong, that is aggregation.)
- Composition is a form of association where the whole owns the parts, and the parts are destroyed if the whole is destroyed. (Correct.)
- Composition is a weak form of aggregation where parts are not tightly bound. (Wrong, it is the strong form. Composition is the stricter one, aggregation is the weaker one.)
- Composition is the same as inheritance. (Wrong. Composition is "has-a", inheritance is "is-a".)
Q4. Main difference between aggregation and composition?
Answer: lifecycle and ownership. In aggregation the whole holds parts that can live without it and can be shared. In composition the whole owns its parts, creates them, and the parts die with it. In UML it is a hollow diamond versus a filled diamond, and in a database it is ON DELETE SET NULL versus ON DELETE CASCADE.
Q5. Which is an example of composition?
- A library and the books it contains. (Aggregation. Books can move to another library.)
- A university and the students it enrolls. (Aggregation. Students exist after graduating.)
- A playlist and its songs. (Aggregation. The same song can be in many playlists.)
- A car and its engine. (Composition. The engine is created with that car and is not shared.)
The pattern in all four options: only in the car and engine case does the part stop making sense once the whole is gone.
Q6. Is "has-a" the same as aggregation?
Answer: no. Has-a is the general phrase for "A holds B as a field". Association, aggregation and composition are all has-a relationships. Aggregation is only the middle one, where the part can live without the whole. If an interviewer asks "is this is-a or has-a", they are asking whether to use inheritance or a field. If they then ask "which kind of has-a", they are asking about ownership and lifecycle.
Q7. class Car extends Engine or class Car { private Engine engine; }?
Answer: the second one. A car is not a kind of engine, a car has an engine. Use is-a only when the child can stand in for the parent everywhere. Here it cannot, so it is has-a, and since the car creates and owns the engine it is specifically composition.
18. Cheat sheet
DEPENDENCY uses-a A ..> B method parameter only Printer, Document
ASSOCIATION has-a A --> B field, both independent Teacher, Student
AGGREGATION has-a A o-- B field, part passed in Library, Book
COMPOSITION has-a A *-- B field, part created inside Car, Engine
INHERITANCE is-a A <|-- B extends / implements Animal, Dog
HAS-A is the family name for the middle three. They are all "B is a field
inside A", they only differ in who owns the lifecycle:
association -> nobody owns anybody
aggregation -> A holds B, B survives A
composition -> A owns B, B dies with A
Diamond goes on the WHOLE. Empty diamond = borrowed. Filled diamond = owned.
Java tell: new Part() outside the whole -> aggregation
new Part() inside the whole -> composition
SQL tell: ON DELETE SET NULL -> aggregation
ON DELETE CASCADE -> composition
Multiplicity: 1 exactly one
0..1 optional
* zero or more
1..* at least one
See also
- CompositionVsInheritance.md for when to prefer "has-a" over "is-a"
- Notes.md for the core OOP concepts
- ../JavaConcepts/12-DependencyInjection.md since injecting a dependency is aggregation in practice
- ../../DesignPatterns/StructuralPatterns/CompositeDesignPattern/ where composition is used to build trees