Diagrams as Code
Object-Oriented Design with UML
Course 1 · Chapter 2 · Diagrams as Code: Mermaid and PlantUML
Chapter 1 argued for sketches that are cheap to change. A diagram you draw with a mouse in a drawing tool is cheap to make and expensive to change: nobody can see what changed between two versions, two people cannot edit it at once, and it quietly rots in a folder nobody opens. A diagram written as text behaves like code: it goes in the repository, shows up in a pull request, and is regenerated by a command. This chapter sets up the two tools the course uses, for real, and shows where each is strong.
Why Text Beats Drag-and-Drop (for Developers)
| Diagram as text | Drawn in a GUI tool | |
|---|---|---|
| Reviewing a change | A normal diff, a few lines | "Binary file changed", or a screenshot someone must remember to update |
| Version history | Every change is a commit with a reason | Whatever the file format and your discipline allow |
| Merging two people's edits | Normal merge, normal conflict markers | Usually "pick one" |
| Layout | Automatic (you cannot place boxes exactly) | Fully yours (and you must move every box when something is added) |
| Learning curve | A small syntax to learn | A tool to learn |
| Pretty, bespoke diagrams | Limited: you accept the tool's look | Better: this is where GUI tools win |
The "automatic layout" row is both the cost and the benefit. You give up control of exactly where a box sits and gain the end of tidying. For a design sketch that is the right trade; for a poster, use a drawing tool. Here is the benefit measured on a real change: I committed the library diagram's source and its rendered SVG, then added one class and one relationship, and ran git diff.
The source change is readable at a glance (a new Reservation class and one arrow); the same change in the rendered SVG is a single 24 KB line, and the diff of it is 54,559 bytes of noise. So review the source, not the picture. Whether to commit the rendered files at all is a choice: committing them lets a plain Markdown viewer show them without a build step; not committing them keeps diffs clean and avoids stale pictures. A common compromise is to commit the source and have a build or CI step render the pictures; I have not set up CI here, so I only describe it.
Two Tools, Compared
Mermaid is JavaScript. A diagram is a text block starting with its type (classDiagram, sequenceDiagram), and it is drawn in a browser or by a command-line wrapper that drives a headless browser. PlantUML is Java (here, a native build of it): a diagram sits between @startuml and @enduml and is rendered by a program to SVG, PNG and other formats. Layout for several PlantUML diagram types is done by a separate program, Graphviz.
| Mermaid | PlantUML | |
|---|---|---|
| Needs | Node and a browser (for the CLI) | Java, or the native build used here; Graphviz for some layouts (see below) |
| Renders natively on GitHub | Yes: a mermaid fenced block in Markdown (GitHub's documentation, read for this chapter) | Not mentioned in that documentation page, so plan on rendering it yourself |
| Class, sequence, state | Yes | Yes |
| Use case | Yes in 12.x, but the keyword is usecase-beta: treat it as unstable | Yes |
| Activity, component, deployment, object | Not as UML diagram types in the syntax I tried (below) | Yes, all four (below) |
| Look | Modern, themeable, light or dark by a setting | Plainer by default; many themes |
| Error behaviour | Fails with a parse error, non-zero exit | Draws the error into the image (below) |
The coverage row was tested, not assumed. I wrote the smallest diagram I could for each type and rendered it with each tool:
Read that carefully. A failure for activity and componentDiagram proves only that those words are not Mermaid diagram types; Mermaid has flowcharts (which can draw an activity-like flow) and an architecture-beta type that I did not test. Mermaid's own syntax reference page (read for this chapter) lists use case but not activity, component, deployment or object diagrams. So the course draws the structure of the data and the interactions in Mermaid, and reaches for PlantUML for the diagram types Mermaid does not offer.
Installing Both (Without Touching Your System)
The usual way to run PlantUML is java -jar plantuml.jar; I did not use it because Java is not installed on this machine, and the native build needs none (it ships its own runtime, which the Java Runtime: GraalVM line shows). PlantUML also ran here with Graphviz present:
Graphviz's dot was already on this machine's path; I do not know where it came from, and I did not test PlantUML without it. If a class or component diagram fails on your machine with a message about dot, install Graphviz (I understand sequence diagrams do not need it, but did not test that). The browser config for mermaid-cli (-p) and a config that sets a plain font (-c) are two small JSON files:
The Same Two Diagrams, in Both Tools
Chapter 1's class and sequence sketches, written first in Mermaid and then in PlantUML. Same meaning, different syntax. Compare how each says "a member has zero or more loans" and "the librarian asks the system".
Class diagram
Mermaid
PlantUML. hide empty methods hides the empty operations compartment and scale 1.5 enlarges the picture; without it PlantUML's drawing is small on a web page.
Sequence diagram
Mermaid
PlantUML
| Idea | Mermaid | PlantUML |
|---|---|---|
| Start | classDiagram | @startuml ... @enduml |
| Attributes in a class | Lines inside class X { } | Lines inside class X { } |
| Association with multiplicity | A "1" --> "0..*" B : label | The same text |
| Call and reply in a sequence | A->>B: text and B-->>A: text | A -> B : text and B --> A : text |
| A person | actor M as Member | actor Member as M (the alias comes last) |
The two languages are close enough that you can move between them in an afternoon, with one trap in the table: the order of name and alias in actor is reversed. In both tools the arrow --> in a class diagram is an association you can navigate in one direction (chapter 5 covers all the arrow types).
A Render Script
Typing mmdc and plantuml commands for every file is how diagrams go stale. This small script renders every source under a folder, skips the ones whose picture is already newer, and fails loudly (exit status 1) if a diagram has an error. It names each output after its source (x.mmd.svg, x.puml.svg), so a Mermaid and a PlantUML version of one diagram can sit side by side.
The real output of running it twice on the folder holding the four diagram sources above, and then on two deliberately broken files:
Note the last lines: both broken diagrams were reported as failures. The PlantUML one is the interesting one. PlantUML does not refuse to produce a picture for bad input; it draws the error into the picture (here "Syntax Error? (Assumed diagram type: sequence)") and, with -pipe, exits with status 200. The script treats both the exit status and the words "Syntax Error" in the output as a failure. A script that only checked for "a file appeared" would have published the error as if it were your design.
Themes and the Dark Page
UML is conventionally drawn dark on light. This course's pages are dark, so light diagrams sit on a white panel (the .diagram class in the course stylesheet). The alternative is to draw the diagram dark. Mermaid takes a theme in an init comment on the first line; PlantUML has built-in themes, selected with !theme. Both worked offline here:
Mermaid, theme: dark
PlantUML, !theme cyborg. The theme names come from PlantUML's bundled list; I tried one, not all of them.
A caution about this convenience: the panel colour must match the diagram. A dark diagram placed on a white panel, or a light one on a dark page without its panel, is the commonest readability bug (chapter 1's class diagram had pale text for exactly that reason). Whichever you choose, look at the diagram on the real page.
Where the Sources Live
Keep the sources next to the code they describe, in the same repository, so a change to the code and a change to the diagram can be one pull request:
- Name by subject, then type, then tool (
loans.class.mmd): you find the diagram by what it is about. - One idea per diagram. A diagram that needs scrolling has stopped being a sketch.
- Mermaid inside Markdown renders on GitHub with no build step, which is the cheapest way to keep a diagram where people already read. Putting a
mermaidfence in your README works for the structure and interaction diagrams the course draws in Mermaid. PlantUML needs the render step above. - Mind what you send out. PlantUML has a public web server that renders text you send it. A design for a real system may be confidential; render locally, as here.
The Course's Choice
| Diagram | Tool in this course | Why |
|---|---|---|
| Class (chapters 4, 5, 6, 11) | Mermaid | Renders on GitHub; the output is clean; enough notation for what we draw |
| Sequence (7) | Mermaid | Readable output, simple syntax |
| State (8) | Mermaid | Supported and clean; chapter 8 will say if PlantUML is needed for composite states or history |
| Use case (3) | PlantUML | Mermaid's is beta; PlantUML's is mature |
| Activity (8) | PlantUML | No Mermaid equivalent (a flowchart is the nearest) |
| Component, deployment, object (9) | PlantUML | Not Mermaid UML types |
That is a decision with a cost: you learn two syntaxes. I judge it worth it, because the alternative is to leave out half the diagram types or draw them badly. If you prefer to use one tool, PlantUML alone covers everything in this course; Mermaid alone does not.
- My course outline was out of date. It said the latest PlantUML was V1.2026.6 and that PlantUML needs Java, "not installed on this machine". Checking the releases page showed 1.2026.8 (2026-09-05), with a native Windows build that needs no Java. What went wrong: I carried a version and a prerequisite from notes written earlier. How to avoid it: re-check versions and requirements on the day you use them.
- My render script overwrote its own output. The first version named every picture after its source's stem, so
loans.class.mmdandloans.class.pumlboth wroteloans.class.svg; the second silently replaced the first, and the "up to date" check then believed the wrong file. Fixed by keeping the source extension in the output name. Takeaway: when two inputs can map to one output name, the tool will lose one without telling you; test with the awkward case on purpose. - I tested error handling with input that was not an error. My first "broken" PlantUML file was
A ->with no target, which PlantUML accepts as a sequence message, so the script correctly reported success. I then assumed PlantUML exits 0 on syntax errors; with a really broken file it exits 200 and draws the error. Takeaway: before you write a check, produce a failure you have verified is a failure, and read the exit status instead of guessing it. - A failing probe proves less than it seems. Mermaid rejected my guessed keywords
activityandcomponentDiagram. That shows those words do not exist, not that Mermaid cannot draw such things. I limited the claim in the comparison to what I had tested and what Mermaid's syntax reference lists.
Hands-On Exercises
In Mermaid, draw the lifecycle of a library loan as a state diagram: it starts when the book is borrowed, can become overdue, and ends when it is returned (an overdue loan returns with a fine due). Render it with the script.
📄 View solutionIn PlantUML, draw a use case diagram for the library: a Member can borrow, return and pay a fine; a Librarian can borrow and return on a member's behalf. Draw the system boundary. Render it with the script.
📄 View solutionSet up a diagrams folder with one Mermaid and one PlantUML source, and use the render script to show four things: the first render, a no-change run that skips both, a run after editing one file that re-renders only that one, and a run with a broken file that fails with exit status 1.
📄 View solutionChapter 2 Quick Reference
- Diagrams as text: reviewable diffs, history, merges, regenerated by command; review the source, not the picture (the SVG diff was 54,559 bytes against a 483-byte source diff)
- Mermaid 12.1.0 + mermaid-cli 12.0.0:
mmdc -i x.mmd -o x.svg -p cfg.json -c mm.json --svgId unique; renders natively in GitHub Markdown; class, sequence, state; use case only asusecase-beta - PlantUML 1.2026.8:
@startuml ... @enduml;plantuml -tsvg -pipe; native build needs no Java; Graphviz for some layouts (untested without); covers use case, activity, component, deployment, object - PlantUML draws syntax errors into the picture (exit status 200 with
-pipe): check for it in scripts - Keep sources beside the code, named subject.type.tool; one idea per diagram; render locally for confidential designs
- Dark page: light diagrams on a white panel, or draw dark (
theme: dark,!theme cyborg); look at the real page - Course choice: Mermaid for class, sequence, state; PlantUML for use case, activity, component, deployment, object