Class Diagrams
Learning Objectives#
- Translate a Java class into its diagram form, the box, the compartments, the visibility markers, so the mapping is second nature.
- Read the five relationship arrows, inheritance, implementation, association, composition, and dependency, and draw each one in the correct direction.
- Use a class diagram as a review artifact, the place where a wrong relationship is caught before it is shipped as a wrong structure.
Introduction#
The class diagram is the workhorse of low level design, and the reason is simple: it shows the shape of the code. A class diagram answers the question every reviewer is asking, what depends on what, who owns what, who extends what, in a single glance. Everything else in this chapter is about behavior over time or flow; the class diagram is about structure, the bones, and it is the diagram you will draw first in almost every design conversation. It is also the easiest diagram to get subtly wrong, because its symbols are small and its mistakes are quiet. An arrow drawn the wrong way around is a diagram that teaches the team the opposite of the truth. Getting the notation right is not pedantry, it is precision that pays.
Problem Statement#
A design review is on. The team is adding a loyalty program, and someone sketches the structure on the whiteboard: a box called LoyaltyService, a box called PointRepository, a box called Customer, and a line between LoyaltyService and PointRepository with an arrowhead somewhere on it. Nobody can say which end the arrow is on, because nobody drew it clearly, and the conversation moves on without the question being resolved. Three weeks later the code lands. LoyaltyService calls a static PointRepository.update(...) directly, because the engineer who drew the box meant the arrow that way, and the engineer who built it read it the other way. The service is now welded to a concrete repository, there is no seam for a test, and the diagram that was supposed to prevent exactly this did nothing, because the notation was too loose to carry the meaning. The failure is not that the team drew. The failure is that they drew with symbols that could not express the distinction that mattered. A class diagram with real notation, the dependency arrow pointing at the dependency, the interface marked, the multiplicity stated, would have settled the question on the whiteboard, in the same meeting, for free.
Core Concept#
The unit of a class diagram is the class box. It is a rectangle with the class name in a top compartment, the attributes in a middle compartment, and the operations in a bottom compartment. That is the classic three-compartment box, and it is already more than most diagrams need. In a design discussion, the middle and bottom compartments are usually noise; the interesting information is in the name and in the relationships. A box with Order, a few attributes, and a couple of operations is usually enough, and you can drop the compartments entirely when the point is the relationships. The visibility markers come from Java and go straight into the diagram. + is public, - is private, # is protected, and ~ is package-private, which you will almost never draw because package-private is rarely worth communicating. A method written as + checkout(gateway: PaymentGateway): Receipt means the return type comes after the colon. If that reads familiar, it is because it is the Java signature with the parameter names dropped. The relationships are where the class diagram earns its keep, and each one has a specific arrow.
| Relationship | Notation | Java meaning | Direction of the arrow |
|---|---|---|---|
| Inheritance | Solid line, hollow triangle | class B extends A | Triangle points at the parent |
| Implementation | Dashed line, hollow triangle | class B implements I | Triangle points at the interface |
| Association | Solid line, optional arrowhead | A field that references another type | Arrow, if drawn, points at the referenced type |
| Aggregation | Solid line, open diamond at owner | Shared lifetime, the parts can outlive the whole | Diamond at the owner |
| Composition | Solid line, filled diamond at owner | Exclusive lifetime, the parts die with the whole | Diamond at the owner |
| Dependency | Dashed line, open arrowhead | Uses as a parameter, return, or local | Arrow points at the dependency |
The hollow triangle has two variants, and mixing them up is the most common silent error. Solid line with hollow triangle means inheritance, class SavingsAccount extends Account. Dashed line with hollow triangle means implementation, class SavingsAccount implements InterestBearing. The triangle always points at the thing being extended or implemented. If you keep the rule "the triangle points at the parent, the arrow points at the dependency," the direction stops being a guess. |
That snippet maps to two arrows. A solid line with a hollow triangle from SavingsAccount up to Account. A dashed line with a hollow triangle from SavingsAccount up to InterestBearing. Both triangles point at the supertype. Anyone who reads the diagram later knows, without looking at the code, that SavingsAccount is an Account and honors an InterestBearing contract. The association family is where "has-a" gets its shades. A plain association is just a field reference, the customer an order points at. An aggregation, an open diamond, means the parts are shared or can outlive the container, a Team that references Employees who still exist after the team is dissolved. A composition, a filled diamond, means the parts live and die with the container, the OrderItems that are deleted when their Order is deleted. The rule of thumb most teams use: if deleting the container deletes the parts, it is composition; if the parts can survive alone, it is aggregation; if you are not sure it matters, draw a plain association and move on. The dependency arrow is the most useful and the most ignored. A dashed line with an open arrowhead means "this class uses that class," as a parameter, a return type, or a local. It is how you show that Order.checkout depends on PaymentGateway without claiming an ownership relationship. Dependency arrows are how a class diagram shows coupling, and coupling is what this entire handbook has been about. A diagram that shows every dependency arrow is a diagram that shows you where the seams are missing. A diagram is only as good as its labels, and the multiplicities are the labels that carry the most design information. 1 on the customer end of the order association says an order has exactly one customer. * on the order-item end says an order has many items. Multiplicity is the part of a class diagram that corresponds to the constraints in your database schema, and it is the part most likely to catch a real bug, because "one" and "many" are exactly the assumptions that break when a requirement changes. Diagram: the class structure of an order checkout, showing association, composition, and a dependency arrow.
Reading this diagram is the whole lesson. The solid line between Order and Customer with a 1 on the customer end says an order references exactly one customer, a plain association. The filled diamond at the Order end of the OrderItem line, with a * on the item end, says an order owns its items and they die with it, composition. The dashed line with the arrowhead pointing at PaymentGateway says Order.checkout depends on the gateway but does not own it. The dashed box with <<interface>> says the dependency is on a contract, which means the gateway can be faked in a test. Read left to right, the diagram is a summary of the coupling decisions in the code, and every arrow is a decision someone should be able to defend. The direction rule is the one to internalize. Inheritance and implementation point at the parent. Dependency points at the dependency. Association and composition point at the thing being referenced or owned, and when the ownership is the point, the diamond makes it unambiguous. If you draw the triangle or the arrowhead on the wrong end, you have inverted the truth, and the team that reads your diagram will build the inverse of what you meant.
Real Production Usage#
The class diagram shows up in production as a documentation artifact, and the most common form in the Java world is generated. IntelliJ IDEA renders a live class diagram from your source, and every team that works in it has seen the view that turns a package into boxes and arrows. That generated diagram is the honest version: it cannot lie, because it is derived from the code, and it rots the instant the code changes, which is why nobody treats it as a deliverable. It is a tool for reading, not a document to maintain. ArchUnit is the closest thing to a class diagram that enforces itself. An ArchUnit test declares the dependencies the architecture allows, "controllers may not depend on repositories directly," and fails the build when the code violates it. That is the dependency arrow from this article turned into a build gate. Teams that use it are not drawing the diagram, they are compiling it, and the coupling decisions are enforced instead of sketched. The framework docs you read are full of class diagrams for the same reason they work in a review. Spring's reference documentation draws the request lifecycle and the bean definitions as boxes and arrows because a framework's structure is read fastest as relationships. When you look at one of those diagrams, you are using the exact skill from this article: identify the boxes, read the arrows, and know immediately which classes the framework will wire together.
Common Mistakes#
The most common mistake is drawing the inheritance triangle with a solid line where an interface is involved. The distinction between extends and implements is a solid versus dashed line, and mixing them up silently teaches the wrong relationship. The fix is the triangle rule: solid for class, dashed for interface, triangle at the parent, and it becomes mechanical with practice. The second mistake is arrow direction. The dependency arrow points at the dependency, and a large fraction of hand-drawn diagrams get this backwards, showing LoyaltyService <- PointRepository when the meaning is LoyaltyService -> PointRepository. When the direction is inverted, the diagram claims the repository depends on the service, and a reviewer will draw the wrong conclusions about which side owns the seam. If you are unsure, check the Java: the class that names the other type in its signature is the one the arrow starts at. The third mistake is over-drawing. A class diagram that lists every field and every method of every class in a system is unreadable, and it is usually produced by someone who confused the diagram with the code. The diagram earns its keep when it shows the relationships, the multiplicities, the interfaces, and the direction of the dependencies. Attributes are context, not content. When a box is taller than the relationships are interesting, you have over-drawn.
Interview Perspective#
The class diagram is the first thing most interviewers expect from an LLD question, even when they do not name it. The question "design a parking lot" is, in practice, "draw me the class diagram of a parking lot." The candidate who draws boxes named ParkingLot, Level, Spot, Vehicle, and ParkingTicket and then labels the relationships, filled diamond from Level to Spot, association from ParkingLot to Level, has answered the structural half of the question before the interviewer has finished asking. The weak answer describes classes in prose, "so we'd have a parking lot class, and it would have levels, and levels would have spots," and the interviewer has to assemble the structure from the stream of words. The strong answer draws and lets the drawing carry the structure, then talks. The difference is visible in the first two minutes: one candidate is building a picture the interviewer can point at, the other is asking the interviewer to hold a paragraph in their head. The follow-up that tests depth is "why is the diamond filled here and not there." The weak answer says "because that's how composition is drawn." The strong answer explains the ownership decision. "The level owns its spots, so a level with no spots is a level with nothing, filled diamond. The ticket references a vehicle but the vehicle exists without the ticket, plain association, and the vehicle can be checked out and returned, so it outlives the ticket." The interviewer is not testing notation recall. They are testing whether the notation is carrying a real design decision.
Knowledge Check#
- A
WarehousereferencesLocations that are assigned by an external system and may be re-assigned between warehouses. State which relationship arrow you draw between the two, and justify why aggregation rather than composition. - You are shown a class diagram with a solid line and hollow triangle from
CartoVehicle, and a dashed line with an open arrow fromCarServicetoCar. Translate both relationships into the Java that would produce them, including the modifiers. - A reviewer looks at your diagram and asks why the dependency arrow points at
PaymentGatewaywhen the gateway is "used by" the order. Write the one-sentence rule that settles the direction dispute, and give the Java line that confirms it.
Key Takeaways#
- A class diagram is the bones of the design, and it answers "what depends on what" faster than any prose.
- The triangle points at the parent, the arrow points at the dependency, the diamond marks the owner.
- Composition is filled and exclusive, aggregation is open and shared, and when it does not matter, draw a plain association.
- Multiplicity carries the schema constraints, and the diagram is only as honest as its arrow directions.
What's Next#
The class diagram is static, a snapshot of structure. The sequence diagram is the same system moving, and it shows one interaction between objects over time, who calls whom in what order. The next article covers the lifelines, the activation bars, and the arrows that turn a class diagram's relationships into a story about a single request.