UML Class Diagrams
Introduction
Pedagogical Note: This chapter is designed using principles of Active Engagement (frequent retrieval practice). We will build concepts incrementally. Please complete the “Concept Checks” without looking back at the text—this introduces a “desirable difficulty” that strengthens long-term memory.
🎯 Learning Objectives
By the end of this chapter, you will be able to:
- Translate real-world object relationships into UML Class Diagrams.
- Differentiate between structural relationships (Association, Aggregation, Composition).
- Read and interpret system architecture from UML class diagrams.
Diagram – The Blueprint of Software
Imagine you are an architect designing a complex building. Before laying a single brick, you need blueprints. In software engineering, we use similar models. The Unified Modeling Language (UML) is the most common one. Among UML diagrams, Class Diagrams are the most common ones, because they are very close to the code. They describe the static structure of a system by showing the system’s classes, their attributes, operations (methods), and the relationships among objects.
The Core Building Blocks
2.1 Classes
A Class is a template for creating objects. In UML, a class is represented by a rectangle divided into three compartments:
- Top: The Class Name.
- Middle: Attributes (variables/state).
- Bottom: Operations (methods/behavior).
2.2 Modifiers (Visibility)
To enforce encapsulation, UML uses symbols to define who can access attributes and operations:
+Public: Accessible from anywhere.-Private: Accessible only within the class.#Protected: Accessible within the class and its subclasses.~Package/Default: Accessible by any class in the same package.
2.3 Interfaces
An Interface represents a contract. It tells us what a class must do, but not how it does it. It is denoted by the <<interface>> stereotype. Interfaces contain method signatures and usually do not declare attributes (the UML specification allows it, but I recommend not to use it)
🧠 Concept Check 1 (Retrieval Practice) Cover the screen above. What do the symbols
+,-, and#stand for? Why does an interface lack an attributes compartment?
Connecting the Dots: Relationships
Software is never just one class working in isolation. Classes interact. We represent these interactions with different types of lines and arrows.
Generalization — “Is-A” Relationships
Generalization connects a subclass to a superclass. It means the subclass inherits attributes and behaviors from the parent.
- UML Symbol: A solid line with a hollow, closed arrow pointing to the parent.
Interface Realization
When a class agrees to implement the methods defined in an interface, it “realizes” the interface.
- UML Symbol: A dashed line with a hollow, closed arrow pointing to the interface.
Dependency (Weakest Relationship)
A dependency indicates that one class uses another, but does not hold a permanent reference to it. For example, a class might use another class as a method parameter, local variable, or return type. Dependency is the weakest relationship in a class diagram.
- UML Symbol: A dashed line with an open arrowhead.
In this example, Train depends on ButtonPressedEvent because it uses it as a parameter type in addStop(). However, Train does not store a permanent reference to ButtonPressedEvent—the dependency exists only for the duration of the method call.
Here is another example where a class depends on an exception it throws:
Association — “Has-A” / “Knows-A” Relationships
A basic structural relationship indicating that objects of one class are connected to objects of another (e.g., a “Teacher” knows about a “Student”). Attributes can also be represented as association lines: a line is drawn between the owning class and the target attribute’s class, providing a quick visual indication of which classes are related.
- UML Symbol: A simple solid line.
- You can also name associations and make them directional using an arrowhead to indicate navigability (which class holds a reference to the other).
Multiplicities
Along association lines, we use numbers to define how many objects are involved. Always show multiplicity on both ends of an association.
| Notation | Meaning |
|---|---|
1 |
Exactly one |
0..1 |
Zero or one (optional) |
* or 0..* |
Zero to many |
1..* |
One to many (at least one required) |
Navigability
By default, an association is bidirectional—both classes know about each other. In practice, the relationship is often one-way: only one class holds a reference to the other. UML uses arrowheads and X marks to show this navigability.
- Navigable end An open arrowhead pointing to the class that can be “reached.” The left object has a reference to the right object.
- Non-Navigable end An X on the end that cannot be navigated. This explicitly states that the class at the X end does not hold a reference to the other.
Here are the four navigability combinations, each with an example:
Unidirectional (one arrowhead): Only one class holds a reference.
Vote holds a reference to Politician, but Politician does not know about individual Vote objects.
Bidirectional (arrowheads on both ends): Both classes hold a reference to each other.
Employee knows about their Boss, and Boss knows about their Employee. A plain line with no arrowheads is also acceptable for bidirectional associations.
Non-navigable on one end (X on one side): One class is explicitly prevented from navigating.
In the full UML notation, an X on the Voter end would mean: Vote knows about Voter, but Voter does not hold a reference to Vote. (Note: the X mark is a formal UML notation not commonly rendered in simplified tools—when you see a unidirectional arrow, the absence of an arrowhead on the other end implies non-navigability.)
Non-navigable on both ends (X on both sides): Neither class holds a reference—the association is recorded only in the model, not in code.
An X on both ends of AccountClearTextPassword means neither class should store a reference to the other. This is a deliberate design decision (e.g., for security: an Account should never hold a reference to a ClearTextPassword).
When to use navigability: Navigability is a design-level detail. In analysis/domain models, plain associations (no arrowheads) are preferred because you haven’t decided which class holds the reference yet. Once you move into detailed design, add navigability to show which class stores the reference—this maps directly to code (a field/attribute in the class at the arrow tail).
Aggregation (“Owns-A”)
A specialized association where one class belongs to a collection, but the parts can exist independently of the whole. If a University closes down, the Professors still exist. Think of aggregation as a long-term, whole-part association.
- UML Symbol: A solid line with an empty diamond at the “whole” end.
Composition (“Is-Made-Up-Of”)
A strict relationship where the parts cannot exist without the whole. If you destroy a House, the Rooms inside it are also destroyed. A part may belong to only one composite at a time (exclusive ownership), and the composite has sole responsibility for the lifetime of its parts.
- UML Symbol: A solid line with a filled diamond at the “whole” end.
- Per the UML spec, the multiplicity on the composite end must be
1or0..1.
A helpful way to think about the difference: In C++, aggregation is usually expressed through pointers/references (the part can exist separately), while composition is expressed by containing instances by value (the part’s lifetime is tied to the whole). In Java and Python, every object reference is effectively a pointer — the distinction between aggregation and composition is communicated through design intent (who created the part? who destroys it?) rather than through language syntax. Inner classes in Java are one indicator of composition but are not required.
⚠ Honest caveat on aggregation. Aggregation has intentionally informal semantics in the UML 2 specification. Martin Fowler (UML Distilled) observes: “Aggregation is strictly meaningless; I recommend that you ignore it in your own diagrams.” When you aren’t sure whether something is aggregation or plain association, use association — it is always safe. Reserve the hollow diamond for the cases where part-whole semantics clearly add communicative value.
🧠 Concept Check 2 (Self-Explanation) In your own words, explain the difference between the empty diamond (Aggregation) and the filled diamond (Composition). Give a real-world example of each that is not mentioned in this text.
Relationship Strength Summary
From weakest to strongest, the class relationships are:
| Relationship | Symbol | Meaning | Example |
|---|---|---|---|
| Dependency | Dashed arrow | "uses" temporarily | Method parameter, thrown exception |
| Association | Solid line | "knows about" structurally | Employee knows about Boss |
| Aggregation | Hollow diamond | "has-a" (parts can exist alone) | Library has Books |
| Composition | Filled diamond | "made up of" (parts die with whole) | House is made of Rooms |
| Generalization | Hollow triangle | "is-a" (inheritance) | Car is-a Vehicle |
| Realization | Dashed hollow triangle | "implements" (interface) | Car implements Drivable |
⚠ The Five Most Common UML Class Diagram Mistakes
Empirical studies of student diagrams (Chren et al., “Mistakes in UML Diagrams: Analysis of Student Projects in a Software Engineering Course,” ICSE SEET 2019) identify these recurring errors. Watch for them in your own work:
| # | Mistake | Fix |
|---|---|---|
| 1 | Generalization arrow pointed the wrong way — triangle at the child instead of the parent | The triangle always rests at the parent. Sanity-check with the “is-a” sentence: “A [child] is a [parent]”. |
| 2 | Multiplicity on the wrong end — e.g., * placed next to the “one” side |
Multiplicity answers “for one of the opposite class, how many of this class?” Place it next to the class being quantified. |
| 3 | Missing multiplicity on one end | Per Ambler (G117), always show multiplicity on both ends of every relationship. An unlabeled end is ambiguous, not “just 1.” |
| 4 | Confusing aggregation and composition — using the filled diamond when parts are actually shared | Composition = exclusive ownership and lifecycle dependency. If the part can exist without the whole, use aggregation (or plain association). |
| 5 | Verbose 0..* when * suffices |
Use the shorthand * for zero-or-more. The UML spec defines them as identical; * is more concise. Reserve 0..* only when contrasting explicitly with 1..* nearby. |
Pedagogy tip: Before turning in any class diagram, run this five-item checklist over every relationship. Catching these five mistakes catches the majority of grading-level errors.
Advanced Class Notation
Abstract Classes and Operations
An abstract class is a class that cannot be instantiated directly—it serves as a base for subclasses. In UML, an abstract class is indicated by italicizing the class name or adding {abstract}.
An abstract operation is a method with no implementation, intended to be supplied by descendant classes. Abstract operations are shown by italicizing the operation name.
In this example, Shape is abstract (it cannot be created directly) and declares an abstract draw() method. Rectangle inherits from Shape and provides a concrete implementation of draw().
Static Members
Static (class-level) attributes and operations belong to the class itself rather than to individual instances. In UML, static members are shown underlined.
From Code to Diagram: Worked Examples
A key skill is translating between code and UML class diagrams. Let’s work through several examples that progressively build this skill.
Example 1: A Simple Class
public class BaseSynchronizer {
public void synchronizationStarted() { }
}
class BaseSynchronizer {
public:
void synchronizationStarted() { }
};
class BaseSynchronizer:
def synchronization_started(self) -> None:
pass
class BaseSynchronizer {
synchronizationStarted(): void { }
}
Each public method becomes a + operation in the bottom compartment. The return type follows a colon after the method signature.
Example 2: Attributes and Associations
When a class holds a reference to another class, you can show it either as an attribute or as an association line (but be consistent throughout your diagram).
public class Student {
Roster roster;
public void storeRoster(Roster r) {
roster = r;
}
}
class Roster { }
class Roster { };
class Student {
public:
void storeRoster(Roster& r) {
roster = &r;
}
private:
Roster* roster = nullptr;
};
class Roster:
pass
class Student:
def __init__(self) -> None:
self._roster: Roster | None = None
def store_roster(self, roster: Roster) -> None:
self._roster = roster
class Roster { }
class Student {
private roster?: Roster;
storeRoster(roster: Roster): void {
this.roster = roster;
}
}
Notice: in the Java version, the roster field has package visibility (~) because no access modifier was specified (Java default is package-private). Other languages express visibility differently, but the relationship is the same: Student holds a reference to a Roster.
Example 3: Dependency from Exception Handling
public class ChecksumValidator {
public boolean execute() {
try {
this.validate();
} catch (InvalidChecksumException e) {
// handle error
}
return true;
}
public void validate() throws InvalidChecksumException { }
}
class InvalidChecksumException extends Exception { }
#include <exception>
class InvalidChecksumException : public std::exception { };
class ChecksumValidator {
public:
bool execute() {
try {
validate();
} catch (const InvalidChecksumException&) {
// handle error
}
return true;
}
void validate() { }
};
class InvalidChecksumException(Exception):
pass
class ChecksumValidator:
def execute(self) -> bool:
try:
self.validate()
except InvalidChecksumException:
# handle error
pass
return True
def validate(self) -> None:
pass
class InvalidChecksumException extends Error { }
class ChecksumValidator {
execute(): boolean {
try {
this.validate();
} catch (error) {
if (!(error instanceof InvalidChecksumException)) throw error;
// handle error
}
return true;
}
validate(): void { }
}
The ChecksumValidator depends on InvalidChecksumException (it uses it in a throws clause and catch block) but does not store a permanent reference to it. This is a dependency, not an association.
Example 4: Composition from Inner Classes
public class MotherBoard {
private class IDEBus { }
private final IDEBus primaryIDE = new IDEBus();
private final IDEBus secondaryIDE = new IDEBus();
}
class MotherBoard {
class IDEBus { };
IDEBus primaryIDE;
IDEBus secondaryIDE;
};
class MotherBoard:
class _IDEBus:
pass
def __init__(self) -> None:
self._primary_ide = MotherBoard._IDEBus()
self._secondary_ide = MotherBoard._IDEBus()
class IDEBus { }
class MotherBoard {
private readonly primaryIDE = new IDEBus();
private readonly secondaryIDE = new IDEBus();
}
The private part type plus owned fields indicate composition: the IDEBus instances are created and controlled by the MotherBoard.
Concept Check (Generation): Before looking at the answer below, try to draw the UML class diagram for this code:
import java.util.ArrayList; import java.util.List; public class Division { private List<Employee> division = new ArrayList<>(); private Employee[] employees = new Employee[10]; }Reveal Answer
TheList<Employee>field suggests aggregation (the collection can grow dynamically, employees can exist independently). The array with a fixed size of 10 is a direct association with a specific multiplicity.
Putting It All Together: The E-Commerce System
Pedagogical Note: We are now combining isolated concepts into a complex schema. This reflects how you will encounter UML in the real world.
Let’s read the architectural blueprint for a simplified E-Commerce system.
System Walkthrough:
- Generalization:
VIPandGuestare specific types ofCustomer. - Association (Multiplicity):
1Customer can have*(zero to many) Orders. - Interface Realization:
Orderimplements theBillableinterface. - Composition: An
Orderstrongly contains1..*(one or more)LineItems. If the order is deleted, the line items are deleted. - Association: Each
LineItempoints to exactly1Product.
Real-World Examples
The following examples apply everything from this chapter to systems you interact with every day. Try reading each diagram yourself before the walkthrough — this is retrieval practice in action.
Example 1: Spotify — Music Streaming Domain Model
Scenario: An analysis-level domain model for a music streaming service. The goal is to capture what things are and how they relate — not implementation details like database schemas or network calls.
What the UML notation captures:
- Generalization (hollow triangle):
FreeUserandPremiumUserboth extendUser, inheritingsearch()andcreatePlaylist(). OnlyPremiumUseraddsdownload()— a capability unlocked by upgrading. The hollow triangle always points up toward the parent class. - Composition (filled diamond, User → Playlist): A
Userowns their playlists. Deleting a user account deletes their playlists — the parts cannot outlive the whole. The filled diamond sits on the owner’s side. - Aggregation (hollow diamond, Playlist → Track): A
Playlistcontains tracks, but tracks exist independently — the same track can appear in many playlists. Deleting a playlist does not remove the track from the catalog. - Association with multiplicity (Track → Artist): Each track is performed by
1..*artists — at least one (solo) or more (collaboration). This multiplicity directly encodes a real business rule.
Analysis vs. design level: This diagram has no visibility modifiers (
+,-). That is intentional — at the analysis level we model what things are and do, not encapsulation decisions. Visibility is a design-level concern added in a later phase.
Example 2: GitHub — Pull Request Design Model
Scenario: A design-level diagram (note the visibility modifiers) showing how GitHub’s code review system could be modeled internally. Notice how an interface creates a formal contract between components.
What the UML notation captures:
- Interface Realization (dashed hollow arrow):
PullRequestimplementsMergeable— a contract committing the class to providecanMerge()andmerge(). A merge pipeline can work with anyMergeableobject without knowing the concrete type. - Composition (Repository → PullRequest): A PR cannot exist without its repository. Delete the repo, and all its PRs are deleted — the filled diamond on
Repository’s side shows ownership. - Composition (PullRequest → Review): A review only exists in the context of one PR.
1 *-- *reads: one PR can have zero or more reviews; each review belongs to exactly one PR. - Dependency (dashed open arrow, PullRequest → CICheck):
PullRequestusesCIChecktemporarily — perhaps receiving it as a method parameter. It does not hold a permanent field reference, so this is a dependency, not an association.
Example 3: Uber Eats — Food Delivery Domain Model
Scenario: The domain model for a food delivery platform. This example is excellent for practicing multiplicity — every 0..1, 1, and * encodes a real business rule the engineering team must enforce.
What the UML notation captures:
Customer "1" -- "*" Order: One customer can have zero orders (a new account) or many. The navigability arrow showsCustomerholds the reference — in code, aCustomerwould have anorderscollection field.- Composition (Order → OrderItem): Order items only exist within an order. Cancelling the order destroys the items. The
1..*onOrderItemenforces that every order must have at least one item. OrderItem "*" -- "1" MenuItem: Each item references exactly one menu item. Many orders can reference the same menu item — deleting an order does not remove the menu item from the restaurant’s catalog.Driver "0..1" -- "0..1" Order: A driver handles at most one active delivery at a time; an order has at most one assigned driver. Before dispatch, both sides satisfy0— neither requires the other to exist yet. This captures a real business constraint in two characters.
Example 4: Netflix — Content Catalogue Model
Scenario: Netflix serves two fundamentally different types of content — movies (watched once) and TV shows (composed of seasons and episodes). This diagram shows how inheritance and composition work together to model a content catalog.
What the UML notation captures:
- Abstract class (
abstract class Content): The italicised class name and{abstract}onplay()signal thatContentis never instantiated directly — you never watch a “content”, only aMovieorTVShow. Both subclasses overrideplay()with their own implementation. - Generalization hierarchy: Both
MovieandTVShowextendContent, inheritingtitleandrating. AMovieaddsdurationdirectly; aTVShowdelegates duration implicitly through its episodes. - Nested composition (
TVShow → Season → Episode): ATVShowis composed of seasons; each season is composed of episodes. Delete a show and the seasons disappear; delete a season and the episodes disappear. The chain of filled diamonds models this cascade. - Association with multiplicity (
Content → Genre): A movie or show belongs to1..*genres (at least one — e.g., Action). A genre classifies*content items. This is a plain association — deleting a genre does not delete the content.
Example 5: Strategy Pattern — Pluggable Payment Processing
Scenario: A shopping cart needs to support multiple payment methods (credit card, PayPal, crypto) and let users switch between them at runtime. This is the Strategy design pattern — and a class diagram is the canonical way to document it.
What the UML notation captures:
- Interface as contract:
PaymentStrategydefines the contract —pay()andrefund(). Every concrete implementation must provide both. The interface appears at the top of the hierarchy, with implementors below. -
**Three realizations (.. >):** CreditCardPayment,PayPalPayment, andCryptoPaymentall implementPaymentStrategy. The dashed hollow arrow points toward the interface each class promises to fulfill. - Association
ShoppingCart --> PaymentStrategy: The cart holds a reference toPaymentStrategy— not to any specific implementation. This navigability arrow (open head, not filled diamond) meansShoppingCarthas a field of typePaymentStrategy. Crucially, it is typed to the interface, not a concrete class. - The power of this design: Because
ShoppingCartdepends onPaymentStrategy(the interface), you can callcart.setPayment(new CryptoPayment())at runtime and the cart works without any changes to its own code. The class diagram makes this extensibility visible — and it shows exactly where the seam between context and strategy is.
Connection to practice: This is the same pattern behind Java’s
Comparator, Python’ssort(key=...), and every payment SDK you will ever integrate in your career. Class diagrams let you see the shape of the pattern independent of any language.
5. Chapter Review & Spaced Practice
To lock this information into your long-term memory, do not skip this section!
Active Recall Challenge: Grab a blank piece of paper. Without looking at this chapter, try to draw the UML Class Diagram for the following scenario:
- A School is composed of one or many Departments (If the school is destroyed, departments are destroyed).
- A Department aggregates many Teachers (Teachers can exist without the department).
- Teacher is a subclass of an Employee class.
- The Employee class has a private attribute
salaryand a public methodgetDetails().
Review your drawing against the rules in sections 2 and 3. How did you do? Identifying your own gaps in knowledge is the most powerful step in the learning process!
6. Interactive Practice
Test your knowledge with these retrieval practice exercises. These diagrams are rendered dynamically to ensure you can recognize UML notation in any context.
Knowledge Quiz
UML Class Diagram Practice
Test your ability to read and interpret UML Class Diagrams.
Look at the following diagram. What is the relationship between Customer and Order?
Which of the following members are private in the class Engine?
What type of relationship is shown here between Graphic and Circle?
Which of the following relationships is shown here?
What type of relationship is shown between Payment and Processable?
What does the multiplicity 0..* on the Order side mean in this diagram?
Looking at this e-commerce diagram, which statements are correct? (Select all that apply.)
What does the # visibility modifier mean in UML?
What type of relationship is shown here between Formatter and IOException?
Given this Java code, what is the correct UML class diagram?
java public class Student {
Roster roster;
public void storeRoster(Roster r) {
roster = r;
}
}
How is an abstract class indicated in UML?
Which of the following Java code patterns would result in a dependency (dashed arrow) relationship in UML, rather than an association? (Select all that apply.)
What does the arrowhead on this association mean?
When should you add navigability arrowheads to associations in a class diagram?
Retrieval Flashcards
UML Class Diagram Flashcards
Quick review of UML Class Diagram notation and relationships.
What does the following symbol represent in a class diagram?
How do you denote a Static Method in UML Class Diagrams?
What is the difference between these two relationships?
What is the difference between Generalization and Realization arrows?
What do the four visibility symbols mean in UML?
What does the multiplicity 1..* mean on an association?
What does a dashed arrow () between two classes represent?
How do you indicate an abstract class in UML?
List the class relationships from weakest to strongest.
What does a navigable association () indicate?
Pedagogical Tip: If you find these challenging, it’s a good sign! Effortful retrieval is exactly what builds durable mental models. Try coming back to these tomorrow to benefit from spacing and interleaving.
7. Interactive Tutorials
Master UML class diagrams by writing code that matches target diagrams in our interactive tutorials: