PlantUML Class Diagrams: Modeling Classes and Relationships
A class diagram captures the static structure of an object-oriented design. PlantUML lets you write one as text, with precise control over members, visibility, and every relationship type.
A class diagram is the backbone of object-oriented design documentation: it shows the classes in a system, the fields and methods each one has, and - most importantly - how they relate through inheritance, association, aggregation, and composition. PlantUML expresses all of this in text, which means your class model can live in version control, be reviewed alongside the code it describes, and update in the same pull request when the design changes. That is a meaningful advantage over a class diagram drawn once and abandoned in a wiki.
This tutorial walks through building a PlantUML class diagram: declaring classes and their members, marking visibility, and - the part people most often get wrong - choosing the correct relationship arrow for each connection. Since PlantUML is a text format, any model here can be referenced and rebuilt as a polished, editable diagram in Atlas Diagram Studio at /diagrams, or you can start from the UML diagram tool at /diagram-tools/uml-diagram. For the wider PlantUML picture, see /guides/plantuml-complete-guide.
Declaring classes and their members
Inside the usual @startuml and @enduml markers, the simplest declaration is just class Order, which draws an empty box. To give it members, open a body: write class Order followed by a brace, then list fields and methods on their own lines, then close the brace. A field is a name, and a method is a name with parentheses, so a body might contain id, total, and calculateTotal(). PlantUML sorts members into a field compartment and a method compartment automatically based on whether they have parentheses.
You can also add members without braces using the double-colon syntax: Order : id and Order : calculateTotal() append to the class from anywhere in the file, which is handy when you build a class up gradually. Beyond plain classes, PlantUML supports the full family of classifiers - use interface, abstract class, enum, and annotation to draw the right stereotype box. An enum body simply lists its constants, and an interface body lists the methods implementers must provide.
Visibility, types, and modifiers
PlantUML renders UML visibility markers when you prefix a member with the right symbol: a minus sign for private, a plus sign for public, a hash for protected, and a tilde for package-private. So -id, +calculateTotal(), and #status render with the little lock, circle, and diamond icons UML uses. You can include types the way you would in code, writing +getTotal() : Money or -items : List, and PlantUML displays the return and field types after the member name.
Two useful modifiers round out member declarations. Prefix a member with the abstract keyword or wrap a method name to mark it abstract, which renders it in italics; use the static keyword to underline it, matching UML's convention for static members. Combined with the classifier keywords, this lets a PlantUML class box convey exactly what a hand-drawn UML box would - visibility, types, abstract and static members - without any ambiguity, which matters when the diagram is meant to be an authoritative reference for developers.
Getting the relationships right
Relationships are where class diagrams carry the most meaning and where the arrow choice matters. PlantUML uses a distinct notation for each UML relationship, and picking the correct one is the difference between a diagram that documents the design and one that misleads.
- Inheritance (generalisation): Dog --|> Animal, drawn with a hollow triangle pointing at the parent.
- Interface realisation: Circle ..|> Shape, a dashed line with a hollow triangle for implementing an interface.
- Association: Order -- Customer, a plain solid line for a general structural link.
- Directed association: Order --> Customer, a solid line with an open arrowhead showing navigability.
- Aggregation: Team o-- Player, a hollow diamond at the whole, for a has-a where parts can outlive the whole.
- Composition: Order *-- LineItem, a filled diamond at the whole, for a strong owns-a where parts die with the whole.
- Dependency: OrderService ..> Logger, a dashed open arrow for a uses relationship.
Multiplicity, notes, and packaging
Add multiplicities by quoting them at the ends of a relationship: Order "1" *-- "many" LineItem states that one order composes many line items, and you can use exact numbers or ranges like "0..1" and "1..*". Label the relationship itself by appending a colon and text, as in Customer "1" -- "0..*" Order : places, which reads as a sentence. These annotations turn a bare set of boxes into a precise structural model, capturing not just what connects to what but how many and why.
For larger models, group related classes with package Name and a brace-delimited body, or use namespace to nest them; PlantUML draws a labelled container around the members. Notes attach with note top of Order or note on link for a comment on a relationship. When the class diagram needs styling, layout control, or review by people who do not write PlantUML, treat the text as the source of record and rebuild it in Atlas Diagram Studio at /diagrams, where the same model becomes an editable, collaborative diagram. To draft a first version from a prompt, try the AI diagram generator at /diagram-tools/ai-diagram-generator, and to generate class structure from real source, see /guides/how-to-generate-diagrams-from-code.