Moises Gamio
Moises Gamio Software Engineer. Comprehensive experience in all phases of the software development lifecycle for several economic sectors.

UML Diagrams for Java Developers: From Requirements to Code

UML Diagrams for Java Developers: From Requirements to Code

The Unified Modeling Language (UML) is a graphical notation for modeling software systems. UML diagrams are not just pretty pictures: they communicate design decisions to the developers who build the system, before anyone writes code.

In this article, you’ll take one small business requirement and turn it into diagrams, from the big picture down to the classes, and then into Java code. Along the way, you’ll learn the relationships that confuse most developers: dependency versus association, and aggregation versus composition.

In this article 12 sections

The Use Case

Small business owners often pay for expensive advertising. A simple application that generates printable flyers can save them money, and it makes a good design exercise.

The User Requirement

Given a text message, a URL link and an image, build a service that composes a flyer with the image, the text and a QR code for the link. The result is an A4 PDF file divided into one, two, four or eight flyers, ready to print.

Form with a message, a link, an image and the number of flyers per page

From this requirement, we can identify three tasks: create a QR code, combine the QR code, text and image into one design, and create a PDF file. Whether we write the code ourselves or use libraries, the result should look like this:

Printed A4 page with flyers that combine an image, a message and a QR code

Different developers can read the same requirement in different ways. A shared notation removes this ambiguity from the start.

Why Do We Model?

  • To understand the system before we build it.
  • To discuss the design with the team while changes are still cheap.
  • To document the decisions we made, and why.
  • To speed up implementation, because technical problems come up during design, not in the middle of coding.

From the Big Picture to the Code: the C4 Model

The C4 model describes software at four levels of detail, like zooming in and out on a map: Context, Containers, Components and Code. UML is the usual notation for the last level, the code.

Context Diagram

The context diagram shows the big picture: people and software systems, not technologies. For our use case, a new web application fulfills the user requirement.

C4 context diagram: a client uses the web application that builds flyer designs

Container Diagram

The container diagram shows the applications and data stores, the separately running units, and how responsibilities are split among them. Our web application needs a form in the front end and an API application in the back end.

C4 container diagram: a web application delivers a single-page application, which calls the API application

Component Diagram

Inside the API application, we give each sub-task to a separate component. Each component has one responsibility, as the Single Responsibility Principle recommends. Read more in SOLID Principles.

  • The QR Service generates a QR code for the link.
  • The Image Service combines the QR code, the message and the image into one design.
  • The PDF Service puts the design on an A4 page, once, twice, four or eight times.

C4 component diagram: the Flyer Controller uses the QR, Image and PDF services

In this diagram, the controller calls the three services itself. It’s better to add one more class, FlyerComposerService, between the controller and the services. It coordinates the three calls, keeps the controller thin, and makes it easier to move the services into separate microservices later.

UML Class Diagrams

A class is a template for creating objects. A UML class box has three compartments:

UML class box for FlyerComposerService with its name, attributes and operations

  1. The name of the class.
  2. The attributes, with their visibility (- private, + public, # protected), name and type.
  3. The operations, with their visibility, parameters and return type.

Here is the same class in Java. The services are passed in through the constructor, which is how Spring injects dependencies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class FlyerComposerService {

  private final QRService qrService;
  private final ImageService imageService;
  private final PDFService pdfService;

  public FlyerComposerService(QRService qrService, ImageService imageService, PDFService pdfService) {
    this.qrService = qrService;
    this.imageService = imageService;
    this.pdfService = pdfService;
  }

  public byte[] composeFlyer(FlyerRequest request) {
    byte[] qrCode = qrService.generateQRCode(request.link());
    byte[] design = imageService.composeImage(qrCode, request.message(), request.image());
    return pdfService.createPdf(design, request.layout());
  }
}

The requirement allows only one, two, four or eight flyers per page. An int would accept any number, so we model the choice as an enum, and we group the user’s input in a record:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum FlyerLayout {
  ONE(1), TWO(2), FOUR(4), EIGHT(8);

  private final int flyersPerPage;

  FlyerLayout(int flyersPerPage) {
    this.flyersPerPage = flyersPerPage;
  }

  public int flyersPerPage() {
    return flyersPerPage;
  }
}

public record FlyerRequest(String message, String link, byte[] image, FlyerLayout layout) {}

Relationships Between Classes

Dependency vs. Association

Many developers draw every relationship as a dependency, especially in Spring projects, where everything is called a “dependency”. UML makes a clear distinction:

  • Association (solid line, open arrow): the class keeps a reference to the other class in a field. FlyerComposerService has the fields qrService, imageService and pdfService, so it has three associations. The field names become the role names on the lines.
  • Dependency (dashed line, open arrow, often labeled «use»): the class uses the other class only for a moment, as a method parameter, a local variable or a return type. FlyerComposerService receives a FlyerRequest as a parameter but doesn’t store it, so that’s a dependency.

In short: a field is an association; anything else the class only uses is a dependency.

Realization

Realization (dashed line, hollow triangle) means that a class implements an interface. QRServiceImpl realizes QRService. Because FlyerComposerService depends only on interfaces, you can replace an implementation, for example with a different QR library, without changing the composer. Read more in The Open-Closed Principle.

The Complete Class Diagram

UML class diagram: FlyerComposerService with associations to the QR, Image and PDF service interfaces, their implementations, and dependencies on FlyerRequest and FlyerLayout

The code follows the diagram directly. The interface:

1
2
3
public interface QRService {
  byte[] generateQRCode(String link);
}

And its implementation:

1
2
3
4
5
6
7
8
@Service
public class QRServiceImpl implements QRService {

  @Override
  public byte[] generateQRCode(String link) {
    // generate the QR code image, for example with the ZXing library
  }
}

UML Sequence Diagrams

A class diagram shows the structure: which classes exist and how they’re related. It doesn’t show the order of the calls. A sequence diagram does. Here is what happens when the controller calls composeFlyer:

UML sequence diagram: the composer calls generateQRCode, then composeImage, then createPdf, and returns the PDF to the controller

Each vertical dashed line is an object’s lifeline. Solid arrows are calls, dashed arrows are return values, and the narrow boxes show when an object is active. The diagram matches the three lines of composeFlyer exactly. Draw a sequence diagram whenever the order of the calls matters, for example when you explain a flow to the team or plan error handling.

Design interviews often start with “Sketch the classes for this problem.” Practicing with real interview questions helps you draw and explain a design under time pressure:

java interview

Relationships Among Objects

Objects work together to do something useful: one object calls another to use its operations. Association, aggregation and composition describe how objects are connected. OOP Concepts in Java explains the foundations behind these decisions.

Association

An association is a solid line between two classes. You can name it with a verb from the business domain, such as register below. An arrow on one end shows which class holds a reference to the other; without arrows, the direction is left open.

UML association between two classes

Aggregation

Aggregation is a has-a association, drawn with a hollow diamond on the side of the whole. The parts can exist without the whole: a buyer works with suppliers, but if you delete the buyer, the suppliers still exist.

UML aggregation: Buyer with a hollow diamond to Supplier

1
2
3
4
5
6
7
8
public class Buyer {

  private final List<Supplier> suppliers = new ArrayList<>();   // the suppliers exist on their own

  public void addSupplier(Supplier supplier) {
    suppliers.add(supplier);
  }
}

Composition

Composition is a stronger aggregation, drawn with a filled diamond. The whole owns its parts, and the parts are deleted with the whole: if you delete an order, its shipping address is deleted too.

UML composition: Order with a filled diamond to Address

1
2
3
4
5
6
7
8
public class Order {

  private final Address shippingAddress;   // created and owned by the order

  public Order(String street, String city) {
    this.shippingAddress = new Address(street, city);
  }
}

In JPA, composition usually means @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) or an @Embedded value: the database deletes the parts together with the whole.

UML to Java Cheat Sheet

Relation­ship UML notation In Java
Dependency Dashed line, open arrow Parameter, local variable or return type
Association Solid line, open arrow A field
Aggregation Solid line, hollow diamond at the whole A field holding parts that live on their own
Composition Solid line, filled diamond at the whole A field holding parts the whole creates and owns
Generali­zation Solid line, hollow triangle extends
Realization Dashed line, hollow triangle implements


The authors of UML explain every diagram type and relationship, with many examples, in The Unified Modeling Language User Guide. It’s the reference to keep on your desk once your diagrams grow beyond a few classes:

The Unified Modeling Language User Guide

Use Aggregation and Composition to Design REST APIs

Aggregation and composition diagrams also help you design REST API endpoints. The following diagram shows the relationships among the business objects of a purchasing system:

UML diagram of Buyer, Order, BuyingList, Assortment, Article, Supplier and Address

The has-a relationships become nested resources. To get the orders of a buyer:

1
GET /api/v2/buyers/{buyerId}/orders

To get the articles in one of a buyer’s assortments:

1
GET /api/v2/buyers/{buyerId}/assortments/{assortmentId}/articles

Diagrams as Code

Many teams keep their diagrams in the repository, next to the code, so the diagrams change in the same pull request as the code. With Mermaid, which GitHub and GitLab render in Markdown files, part of our class diagram looks like this:

1
2
3
4
5
6
7
8
9
10
11
classDiagram
    class FlyerComposerService {
        +composeFlyer(FlyerRequest request) byte[]
    }
    class QRService {
        <<interface>>
        +generateQRCode(String link) byte[]
    }
    FlyerComposerService --> QRService : qrService
    FlyerComposerService ..> FlyerRequest : uses
    QRServiceImpl ..|> QRService

PlantUML works the same way and is supported by IntelliJ IDEA and most documentation tools.

Conclusions

  • UML turns a requirement into a design the whole team understands, before anyone writes code.
  • The C4 model takes you from the big picture to the classes. Class diagrams show the structure, and sequence diagrams show the order of the calls.
  • A field is an association; a parameter or local variable is a dependency.
  • Composition means the whole owns its parts; with aggregation, the parts live on their own.
  • Depending on interfaces, not on implementations, keeps the design easy to change.

Choosing the right relationship is a software design decision. My book shows when interfaces, composition and the SOLID principles help, what they cost, and when they make your code worse:

codersite

“If this article helped you, help me create the next one.”

☕ Buy me a coffee