The class diagram is the most-used, most-misused UML diagram. Easy to read in a single example; harder to draw when domain semantics get complex. This walks every relationship type once, end to end.
class Car class Engine class Driver class Wheel class SteeringWheel
Car "1" *-- "4" Wheel : composition Car "1" o-- "1" Engine : aggregation Car "1" --> "1" SteeringWheel : association Driver --> Car : dependency Car ..> Driver : generalization (rare)
note bottom of Car *-- composition — whole dies, parts die o-- aggregation — whole dies, parts live on --> association — long-lived reference ..> dependency — temporary usage --|> generalization — subclass ..|> realization — implements interface end note @enduml
Relation
Keyword
Meaning
Code form
Composition
*--
whole/part, parts die with whole
new inside
Aggregation
o--
whole/bunch, parts can survive
injected via constructor
Association
-->
long-term reference
field
Dependency
..>
temporary
method param / local
Generalization
--|>
subclass
extends
Realization
..|>
interface impl
implements
Modifiers on associations: multiplicity, role, visibility
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@startuml class Company class Employee
Company "1..*" o-- "*" Employee : employs
note right of Company 1..* is multiplicity 'employs' is the role name on this end visibility like '-' '+' '#' goes by the role name end note
Company --> "- boss +name" Employee : role + visibility @enduml
Multiplicity is min..max:
1 exactly one
0..1 zero or one
* zero or more (no upper bound)
1..* at least one
Role name sits next to the target. - boss means “Employee is the private boss role on Company”.
Nested classes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@startuml class Order { -id: Long -items: List<Item> +checkout() }
class OrderStatus { +PENDING +PAID +SHIPPED +DELIVERED }
Order +-- OrderStatus : inner enum @enduml
+-- says Order contains OrderStatus, renders as inner + outer nested box.
Package dependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
@startuml package "com.app" { package "domain" { class Order class Customer } package "service" { class OrderService class CustomerService } package "controller" { class OrderController } }
AggregateRoot <|-- Order Auditable ..|> Order Auditable ..|> Customer
Order "1" *-- "1..*" OrderItem OrderItem "1" --> "1" Product Order "1" --> "1" Customer Order "1" --> "1" OrderStatus OrderItem "1" --> "1" Money Product "1" --> "1" Money
note bottom of Order Order model notes: - Order is the aggregate root - Composes 1..* OrderItems - References Customer / Product (no composition) - Status enum expresses lifecycle end note @enduml
Review checklist
Every class has visibility markers on fields / methods (- / # / +)?
Relation arrows point the right way? (A holds B → A --> B; reverse if opposite)
Don’t draw all 5 relations for every pair; only the semantically meaningful ones
Interfaces use <<interface>> or the dedicated interface keyword
Abstract methods marked {abstract} or italic
Abstract classes use abstract class
Enums use enum
Abstract class name starts with Abstract (per project convention)
Common anti-patterns
Too many relationships
1 2 3 4 5 6 7
class A class B class C A --> B B --> C C --> A @enduml
A → B → C → A cycle. Mixing runtime and compile-time dependencies in one picture confuses reviewers.
No visibility
1 2 3 4 5 6
class User { id name email login() }
Without visibility the reviewer is left guessing. Always mark.
Mutual references
1 2 3 4 5
class Foo class Bar
Foo --> Bar : holds Bar --> Foo : holds
Mutual references usually mean the modules should split, or the two classes should merge.
TL;DR
Class diagrams have 5 relationships + 4 visibilities + 5 structural types. Get the arrow direction, visibility, and semantics (aggregation vs composition vs association) right, and the diagram becomes professional.
Title: PlantUML class diagrams: abstract, interface, association, dependency, qualifiers