A package diagram shows how a system is split into named groups (packages) and which groups depend on which. It is the map of your codebase's modules, read by architects and developers deciding where new code belongs and checking that layers do not leak into each other.
A UML package is a namespace: a folder that can contain classes, other packages, use cases or any model element. In code it usually maps to a Java package, a Python package, a Go module or a directory. A package diagram hides the contents and focuses on the arrows between packages, because those arrows decide how hard the system is to change.
The key question it answers is: if I change this package, what else might break? Every arrow is a compile-time or import-time dependency. A clean design points arrows in one direction (usually toward the stable core) and has no cycles.
A package is a rectangle with a small tab carrying its name. Contents can be listed inside or left out. Packages nest. A dependency is a dashed arrow with an open head, pointing from the user to the used package. The stereotypes «import» (public: the imported names become visible to others too) and «access» (private import) refine that. «merge» is mostly used in metamodels such as the UML specification itself; you will rarely need it.
Walkthrough: web depends on application, which depends on both domain and infrastructure. The interesting arrow is the horizontal one: infrastructure depends on domain, not the reverse. The domain defines interfaces (ports) such as PaymentGateway; infrastructure implements them. This is the dependency inversion behind hexagonal and clean architecture, and the diagram makes it obvious in one glance.
Walkthrough: data flows ingest → transform → warehouse, and everything uses common. The highlighted pair is a problem: transform calls quality checks, and quality imports helpers from transform. That cycle means neither can be tested or released alone. The usual fix is to move the shared helpers into common or a new package so arrows point one way. Tools such as import-linter (Python) or ArchUnit (Java) can enforce the drawn rules in CI.
Read the arrows, not the boxes. Count how many arrows point into each package (its fan-in) and how many leave it (its fan-out). Packages with high fan-in should be stable and change rarely, because everything depends on them; packages with high fan-out are volatile and safe to change. Robert C. Martin's Stable Dependencies Principle says arrows should point toward stability, and a package diagram is where you check it.
Common mistakes: drawing packages that mirror the folder tree without asking whether the boundaries are meaningful; omitting the utility package that everything imports, which hides the real coupling; and drawing the intended architecture instead of the real one. A good habit is to generate the diagram from imports (for example with pydeps for Python or jdeps for Java), compare it with the intended picture, and treat every difference as either a bug in the code or a bug in the design.
PlantUML draws packages with package blocks and dashed arrows with ..>:
@startuml
package web {}
package application {}
package domain {}
package infrastructure {}
web ..> application
application ..> domain
application ..> infrastructure
infrastructure ..> domain : implements ports
@endumlMermaid has no package diagram; a flowchart with subgraph blocks is the common substitute.