Association: linking objects that know about each other
Understand association in OOP: how objects reference each other without ownership, with examples of unidirectional, bidirectional, and multiplicity relationships.
Association is the most fundamental relationship between classes, and the one most people skip over. They jump straight to inheritance or composition without understanding the baseline: two objects that know about each other but neither owns the other. A Teacher knows which Student objects are in their class. A Student knows which Teacher taught them. Neither creates or destroys the other.
What Is Association
Association means one object holds a reference to another. That is the entire definition. No lifecycle control, no ownership semantics, no containment. Both objects exist independently and can survive without each other.
Think of a real hospital. A Doctor treats many Patient objects over time. The doctor does not create patients (they walk in) and does not destroy them (they walk out). The patient does not create or destroy the doctor either. They simply know about each other for the duration of their interaction.
This is different from composition, where the container controls the lifetime of the contained object. It is different from inheritance, where one class IS another. Association says: "I use you" or "I know about you." Nothing more.
For your interview: if someone asks about class relationships, start with association. It is the default, and aggregation and composition are just special cases of it with added semantics.
Association is the baseline
Every aggregation is an association. Every composition is an association. The difference is in the ownership and lifecycle semantics layered on top. Start here, then layer on meaning.
Types of Association
Association comes in two flavors based on direction, and three based on multiplicity. You need both dimensions to describe a relationship fully.
Directionality
Unidirectional: One class knows about the other, but not the reverse. A Driver holds a reference to a Car. The car has no idea who drives it. This is the simpler, less coupled option and should be your default choice.
Bidirectional: Both classes hold references to each other. A Teacher knows their students, and each Student knows their teachers. This creates tighter coupling because changes to one class can ripple into the other. Use it only when both sides genuinely need to navigate to the other.
I default to unidirectional unless there is a concrete use case requiring navigation from both sides. Bidirectional associations double the maintenance cost because you must keep both sides in sync.
Multiplicity
Multiplicity describes how many objects participate on each side of the relationship.
| Multiplicity | Notation | Example |
|---|---|---|
| One-to-one | 1..1 | A Person has one Passport |
| One-to-many | 1..* | A Department has many Employee objects |
| Many-to-many | *..* | A Student enrolls in many Course objects, and each Course has many students |
The multiplicity you choose affects your data structure. One-to-one uses a single field reference. One-to-many or many-to-many use collections (List, Set). Many-to-many bidirectional is the most complex because both sides hold collections that must stay synchronized.
Implementation
// Unidirectional association: Driver knows Car, Car does not know Driver.
// This is the simplest form. Driver holds a reference; Car is unaware.
// The Driver does not create or destroy the Car. It is passed in.
public class Driver {
private final String name;
private Car car; // nullable: a driver may not have a car right now
public Driver(String name) {
this.name = name;
}
// Association is set from outside. Driver does not construct the Car.
public void assignCar(Car car) {
this.car = car;
}
public void unassignCar() {
this.car = null; // Car still exists elsewhere; we just drop the reference
}
public String getName() { return name; }
public Car getCar() { return car; }
}Notice the synchronization pattern in Teacher and Student. The public methods (addStudent, enrollWith) update both sides. The package-private *Internal methods prevent infinite recursion. This is the standard approach for bidirectional associations in Java, and it is exactly the pattern JPA/Hibernate expects for @ManyToMany mappings.
Association vs Aggregation vs Composition
This is the question interviewers love. All three are forms of association, but they differ in ownership and lifecycle semantics.
| Dimension | Association | Aggregation | Composition |
|---|---|---|---|
| UML arrow | --> (plain) | o-- (open diamond) | *-- (filled diamond) |
| Ownership | None | Weak ("has-a, loosely") | Strong ("owns, controls") |
| Lifecycle | Independent | Independent | Dependent: child dies with parent |
| Can child exist alone? | Yes | Yes | No |
| Example | Doctor treats Patient | Team has Players | House has Rooms |
| Deletion effect | No cascading | No cascading | Child deleted when parent deleted |
The practical test I use: if you delete the "parent" object, does the "child" still make sense? If yes, it is association or aggregation. If no, it is composition.
Interview shortcut
When asked "what's the difference between association, aggregation, and composition," answer with the lifecycle test. "If deleting A also logically deletes B, it's composition. If B survives, it's association or aggregation. Aggregation just means A groups B into a collection without owning it."
Common Mistakes
Mistake 1: Confusing association with composition.
A Student has a List<Course>. Some candidates call this composition because "Student has courses." But courses exist independently of any student. Dropping a student does not delete the course catalog. This is a many-to-many association, not composition.
Mistake 2: Making everything bidirectional. Bidirectional associations require sync logic, increase coupling, and make serialization harder (circular references in JSON). Start unidirectional. Add the back-reference only when you have a concrete navigation requirement, not "just in case."
Mistake 3: Forgetting to sync both sides.
In a bidirectional association, adding a Student to Teacher.students without also adding the Teacher to Student.teachers creates an inconsistent object graph. The standard fix: one side owns the sync logic and the other side has an internal helper method.
Mistake 4: Using inheritance when you mean association.
"A Doctor IS a Person" is inheritance. "A Doctor treats a Patient" is association. The verb matters. IS-A vs uses/knows/treats/manages. If the verb is anything other than "is a kind of," it is not inheritance.
Mistake 5: Ignoring multiplicity in interviews. Saying "Doctor has Patients" is incomplete. Is it one-to-one, one-to-many, or many-to-many? In most real systems a patient sees multiple doctors and a doctor treats multiple patients. State the multiplicity explicitly when drawing UML in an interview.
Bidirectional = double the bugs
Every bidirectional association is a potential consistency bug. If teacher.addStudent(s) does not also call student.addTeacher(teacher), your object graph is half-updated. Hibernate makes this worse by silently ignoring the non-owning side. Always designate one side as the "owner" of the sync.
Real-World Examples
JPA @ManyToMany is the most common bidirectional association in enterprise Java. Student and Course each have a collection annotated with @ManyToMany. JPA generates a join table (student_course) automatically. The "owning side" is whichever entity has the @JoinTable annotation.
Spring's ApplicationContext and beans are associated, not composed. The context holds references to beans, but beans can exist as plain Java objects outside the context. Destroying the context does not invalidate the object itself (though its lifecycle hooks fire).
Java Collections and elements are a pure association. A List<String> holds references to String objects. Clearing the list does not destroy the strings; they continue to exist wherever else they are referenced. Garbage collection handles the rest.
Test Your Understanding
Quick Recap
- Association means one object references another. No ownership, no lifecycle control, no containment.
- Default to unidirectional. Add bidirectional only when you have a concrete navigation requirement from both sides.
- Bidirectional associations require sync logic: one side owns the add/remove, the other side has an internal helper.
- Multiplicity (1..1, 1.., .. ) must be stated explicitly in UML and in interviews. "Doctor has Patients" is vague; "Doctor 1.. Patient" is precise.
- The lifecycle test distinguishes the three: association = independent, aggregation = independent but grouped, composition = child dies with parent.
- Many-to-many bidirectional is the hardest to maintain. Consider whether a query can replace one direction of the reference.
- In interviews: start with association as the default relationship, then layer on aggregation or composition only when ownership semantics apply.