UML Class Diagram Example: Model a System Step by Step
You've got a feature to build — an online bookstore — and four people on the call keep saying "order" to mean four different things. One means the row in a database. One means the checkout button. One means the email receipt. A UML class diagram cuts through that in about ten minutes: you draw the things in your system, what each one knows, what it can do, and how they connect. By the end everyone is pointing at the same box.
Here's the catch most tutorials skip. People confuse a class diagram with two other diagrams that look similar. So before the example, let's draw the line clearly.
What a UML class diagram is (and isn't)
A class diagram shows the structure of your code: the classes, their data, their behavior, and the static relationships between them. It's a blueprint of types.
It is not an entity relationship diagram — an ERD models database tables and columns. They overlap (a Book class often maps to a books table), but a class has methods and inheritance, which a table doesn't. And it's not a sequence diagram, which shows messages firing in time order. The class diagram is the cast of characters; the sequence diagram is the script.
Keep that straight and the rest is mechanical.
Reading a single class box
Every class is a rectangle split into three stacked compartments:
+----------------------+
| Book | <- name
+----------------------+
| - title: string | <- attributes (data)
| - price: decimal |
| - isbn: string |
+----------------------+
| + getPrice(): decimal| <- operations (behavior)
| + applyDiscount(p) |
+----------------------+
Top is the name. Middle is attributes — what the object knows. Bottom is operations — what it can do. The - and + are visibility: - is private (internal only), + is public (callable from outside). You'll also see # for protected. That's the whole vocabulary for one box. If you only ever drew boxes like this, you'd already have a useful diagram.
The relationships are where it gets interesting — and where people draw the wrong line.
The four relationships that actually matter
There are only four you'll use 95% of the time. The trick is matching the line to the real relationship, because each one is a different promise about how the objects live and die.
Association — a plain solid line. "These two know about each other." A Customer places an Order. Neither owns the other; they're just linked. Use this when in doubt.
Aggregation — a solid line with a hollow diamond at the owner's end. "Has-a, but they can live apart." A ShoppingCart aggregates Book objects. Empty the cart and the books still exist in the catalog. The diamond sits on the whole (the cart).
Composition — a solid line with a filled diamond. "Owns-a, and they die together." An Order is composed of OrderLine objects. Delete the order and its lines are meaningless — they have no life of their own. Filled diamond on the whole (the order).
Inheritance — a solid line with a hollow triangle pointing at the parent. "Is-a." PhysicalBook and Ebook both inherit from Book. The triangle always points up, at the more general type.
The aggregation-vs-composition distinction trips everyone up. One question settles it: if I destroy the container, should the parts survive? Yes → aggregation (hollow diamond). No → composition (filled diamond).
Multiplicity: how many of each
On each end of a line you write a number that says how many. A Customer 1 ──places──> 0..* Order reads "one customer places zero or more orders." Common values:
1— exactly one0..1— optional, at most one1..*— one or more0..*or just*— any number
Multiplicity is the part stakeholders argue about most, and it's the part that catches real bugs. "Can an order have zero items?" is a question your diagram is now forcing someone to answer.
Worked example: the online bookstore
Let's assemble it. The domain: customers buy books; an order has line items; books come in physical and digital flavors.
The classes:
- Customer —
name,email; canplaceOrder(). - Order —
orderDate,status; cantotal(),addLine(). - OrderLine —
quantity; cansubtotal(). - Book —
title,price,isbn. - PhysicalBook — adds
weight,shippingClass. - Ebook — adds
fileSize,format.
Now the lines, said out loud so the notation has meaning:
Customer 1 ──places──> 0..* Order— plain association. A customer exists with no orders; an order belongs to exactly one customer.Order ◆──> 1..* OrderLine— composition (filled diamond on Order). Kill the order, the lines go with it. An order has at least one line.OrderLine 1 ──references──> 1 Book— association. The line points at a catalog book but doesn't own it.PhysicalBook ──▷ BookandEbook ──▷ Book— inheritance (hollow triangle pointing at Book). Both are kinds of book.
Read it back: a customer places orders; each order owns its lines; each line references a book; a book is physical or digital. Six boxes, five lines, and the whole team now shares one mental model. That's the entire job of the diagram.
Three mistakes that make class diagrams useless
Modeling the database instead of the code. If your boxes have no methods and every line is an association, you drew an ERD. A class diagram earns its keep by showing behavior and inheritance — use them.
Filled diamonds everywhere. Composition is a strong claim: these parts cannot exist alone. Most "has-a" relationships are actually aggregation or plain association. When unsure, use a plain line, not a filled diamond.
Cramming in every getter and setter. A diagram is a conversation tool, not a code dump. Show the operations that matter to the design — total(), placeOrder() — and leave the boilerplate out. If the box needs a scrollbar, you've overdone it.
Skip the line-by-line drawing
The reasoning above is the valuable part. The actual placement of boxes and the fiddly arrowheads — the hollow triangle vs. filled diamond that you'll re-google every time — is the part worth handing off.
In Ridvay, you describe the system in plain English and get an editable class diagram back: "online bookstore with Customer, Order, OrderLine, and a Book class that PhysicalBook and Ebook inherit from." It picks the right arrowheads — composition for the order lines, the inheritance triangle for the book subtypes — and lays the boxes out so the lines don't cross. Then you tweak: rename a class, change a multiplicity from 1 to 0..*, drag a box, add a method. You're editing a structure instead of fighting a canvas.
Generate a class diagram from your own description →
Draw the boxes, name the relationships honestly, and your next design review is an agreement instead of an argument.