UML class diagrams

The class header (alias uml) switches to single-column UML class boxes, connected with UML relationship arrowheads instead of the crow's-foot notation of an ERD. It shares that family's block grammar: a class is a named block of members, and a line of dashes inside it is a compartment divider.

class
Animal {
  +name string
  -age int
  ---
  +makeSound() void
  +move() void
}
Dog {
  +breed string
  ---
  +fetch() void
}
Dog --|> Animal
DiagramAnimal+name: string-age: int+makeSound(): void+move(): voidDog+breed: string+fetch(): void
Attributes above the divider, methods below; the hollow triangle points at the parent.

Members and compartments#

  • A --- (two or more dashes on their own line) draws a compartment divider. Members above the first divider are attributes; those below are methods.
  • A leading +, -, #, or ~ sets visibility (public, private, protected, or package), drawn as that glyph.
  • A member reads name type. Method parameters are not stored, so a method shows as name().

The block must span lines: the opening { and closing } sit on their own lines, one member per line between them. (An inline Dog { +fetch() void } on a single line is not parsed as a class.) A class with no members is written Runnable { }.

class
Account {
  +id uuid
  -balance decimal
  #owner string
  ~region string
  ---
  +deposit() void
  +withdraw() bool
  -audit() void
}
DiagramAccount+id: uuid-balance: decimal#owner: string~region: string+deposit(): void+withdraw(): bool-audit(): void
All four visibilities, split into attribute and method compartments.

Relationships#

Each relationship has its own connector and UML arrowhead. Every connector has a mirror spelling (<|-- and --|>, *-- and --*, …) so you can write the ends in either order; the marker always lands on the correct end.

ConnectorRelationshipArrowhead
--|>inheritance (extends)hollow triangle on the parent
..|>realization (implements)dashed, hollow triangle
*--compositionfilled diamond on the whole
o--aggregationhollow diamond on the whole
..>dependency (uses)dashed open arrow

Both ends of a relationship must be declared classes. A plain -> (or a cardinality like 1:N) stays a normal ERD relation, so one diagram can mix UML relationships with crow's-foot links.

class
Shape {
  ---
  +area() float
}
Drawable {
  ---
  +draw() void
}
Circle {
  -radius float
}
Canvas {
  ---
  +render() void
}
Pen { }

Circle --|> Shape
Circle ..|> Drawable
Canvas *-- Shape
Canvas o-- Pen
Canvas ..> Drawable
DiagramShape+area(): floatDrawable+draw(): voidCircle-radius: floatCanvas+render(): voidPen
Inheritance, realization, composition, aggregation, and dependency in one diagram.

Two larger models round this out. A small domain model first: a payment hierarchy with an interface, composition, and a dependency.

class
title: Payments domain

Payment {
  +id uuid
  #amount decimal
  ---
  +process() bool
  #validate() bool
}
CardPayment {
  +last4 string
  ---
  +process() bool
}
BankTransfer {
  +iban string
  ---
  +process() bool
}
Refundable {
  ---
  +refund() bool
}
Gateway {
  ---
  +charge() bool
}
Receipt {
  +number string
}

CardPayment --|> Payment
BankTransfer --|> Payment
CardPayment ..|> Refundable
Payment *-- Receipt
Payment ..> Gateway
Payments domainPayment+id: uuid#amount: decimal+process(): bool#validate(): boolCardPayment+last4: string+process(): boolBankTransfer+iban: string+process(): boolRefundable+refund(): boolGateway+charge(): boolReceipt+number: string

A layered service architecture expressed as classes and dependencies.

class
title: Service layers

Controller {
  ---
  +handle() Response
}
Service {
  ---
  +execute() Result
}
Repository {
  ---
  +find() Entity
  +save() void
}
Database {
  -dsn string
}
Logger {
  ---
  +log() void
}

Controller ..> Service
Service ..> Repository
Service ..> Logger
Repository o-- Database
Service layersController+handle(): ResponseService+execute(): ResultRepository+find(): Entity+save(): voidDatabase-dsn: stringLogger+log(): void
Every diagram on these pages is compiled live from the code above it, through the same pipeline the app uses. Open the canvas to try it.