A curated collection of object-oriented design patterns and concurrency constructs implemented in modern Java, intended as teaching material. Each example is reduced to the smallest set of classes that still exhibits the essential structure of the pattern, and is accompanied by a written explanation, a class diagram and a unit test. The examples are hosted in a small Spring Boot application with a Swing front end, so that they can be executed one at a time and their output observed.
- Quickstart
- Catalogue
- Design of the host application
- Building and quality gates
- Adding a new example
- Documentation conventions
- Related projects
- References
- Contributing
- A Java 25 JDK. The repository ships an
.sdkmanrcfile, sosdk env installwill fetch a matching distribution if you use SDKMAN!. - Nothing else: the Maven wrapper (
./mvnw) downloads the build tool on first use.
git clone https://github.com/lpenap/java-patterns-and-constructs
cd java-patterns-and-constructs
./mvnw spring-boot:runA window opens with one button per example and a Run All button. Clicking a button runs that example on a background thread and writes its trace output to the Logger Output panel. Tick Preserve log to keep the output of previous runs instead of clearing the panel before each one.
The examples can also be run without the window by enabling the console runner in src/main/resources/application.properties:
app.enableCommandLineRunner=trueExample output is written at TRACE level. The default configuration already enables it for the project packages:
logging.level.com.penapereira.example.*=TRACEThe patterns follow the classification of Gamma, Helm, Johnson and Vlissides [1]. Each entry links to a folder containing the source code and a README that discusses intent, structure, participants, consequences and references.
| Pattern | Intent |
|---|---|
| Abstract Factory | Provide an interface for creating families of related objects without naming their concrete classes. |
| Factory Method | Define an interface for creating an object, but let subclasses decide which class to instantiate. |
| Factory (Simple Factory) | Centralise the creation of related products behind a single method that selects the concrete class. |
| Singleton | Ensure a class has exactly one instance and provide a global point of access to it. |
| Pattern | Intent |
|---|---|
| Adapter | Convert the interface of a class into another interface clients expect. |
| Decorator | Attach additional responsibilities to an object dynamically. |
| Pattern | Intent |
|---|---|
| Chain of Responsibility | Pass a request along a chain of handlers until one of them handles it. |
| Observer | Define a one-to-many dependency so that dependents are notified when a subject changes state. |
| Strategy | Define a family of interchangeable algorithms and let the client choose one at run time. |
| Template Method | Define the skeleton of an algorithm and defer some steps to subclasses. |
| Construct | Problem |
|---|---|
| Producer/Consumer | Coordinate threads that generate data with threads that process it through a bounded, thread-safe buffer. |
The application exists only to discover, run and display the examples. Its design is deliberately small so that it does not distract from the patterns themselves.
com.penapereira.example.constructs
├── JavaPatternsAndConstructsApplication Spring Boot entry point (headless mode disabled)
├── app
│ ├── ExampleRunnerInterface Contract every example implements
│ ├── AppCommandLineRunner Opens the main window on the AWT event thread
│ ├── ExamplesCommandLineRunner Optional console runner (app.enableCommandLineRunner)
│ ├── properties
│ │ ├── ApplicationProperties Typed binding of the app.* properties
│ │ └── Messages Typed binding of the msg.* user-facing strings
│ └── ui
│ ├── MainWindow Swing frame: one button per example, output panel
│ ├── OutputSink Interface through which text reaches the window
│ ├── GuiAppender Logback appender that forwards log events to an OutputSink
│ └── HyperlinkMouseListener Opens the project URL in the system browser
├── abstractfactory, adapter, ... One package per pattern or construct
└── producerconsumer
The moving parts and how they fit together:
- Discovery by type. Every example is a Spring
@ComponentimplementingExampleRunnerInterface, whose single method isrunExample(). Both runners ask theApplicationContextfor all beans of that type, so adding an example never requires touching the host. The bean name, minus theExampleRunnersuffix, becomes the button label. - Output through logging. Examples do not print; they log at
TRACEthrough SLF4J.GuiAppenderregisters itself on the Logback root logger at start-up and forwards each formatted message to anOutputSink.MainWindowimplementsOutputSinkby appending to its text area on the AWT event thread. The indirection through an interface keeps the appender testable without a display. - Configuration binding. Margins, colours and all user-visible strings live in
application.propertiesand are bound toApplicationPropertiesandMessagesthrough@ConfigurationProperties. Lombok's@Datagenerates the accessors. - Dependency injection. Beans receive their collaborators through constructors generated by Lombok's
@RequiredArgsConstructor. There is no field injection. - Threading. Each example runs on its own thread so a slow example cannot freeze the window. The producer/consumer example is the only one that creates threads of its own, and it shuts them down before returning.
| Tool | Role |
|---|---|
| Maven Wrapper 3.9 | Reproducible builds without a local Maven installation |
| Spring Boot 4.1 parent | Dependency management and the spring-boot:run goal |
| JUnit Jupiter | Unit tests, one test class per pattern plus one per example runner |
| JaCoCo | Coverage report and enforcement |
| Lombok | Boilerplate generation (@Data, @RequiredArgsConstructor) |
./mvnw verify compiles, runs the tests and fails the build if instruction coverage drops below 95% or branch coverage below 90%. Only the Swing shell (MainWindow, AppCommandLineRunner and the application class) is excluded from the measurement, because it cannot be exercised without a display.
Continuous integration runs on two services:
- GitHub Actions (
.github/workflows/maven.yml) builds every pull request. On pushes tomasterit additionally refreshes the dependency graph and regenerates the coverage badges in.github/badges/, committing them through a short-lived pull request that the workflow merges itself. - CircleCI (
.circleci/config.yml) runsmvn verifyon every push as an independent check.
If you open the project in an IDE, install its Lombok plugin so the generated constructors and accessors resolve.
The host discovers examples by type, so a new example is a self-contained package plus a test. Follow the existing packages as templates.
-
Create the package
com.penapereira.example.constructs.<name>undersrc/main/java, using a single lower-case word or compound (for examplechainofresponsibility). -
Implement the pattern with the fewest classes that still show its structure. Name the classes after the roles in the reference literature (
Product,ConcreteProductA,Handler, and so on) so the README can map them directly to the participants. -
Add the runner. A class named
<Name>ExampleRunner, annotated@Component, implementingExampleRunnerInterface. Log atTRACE, start with a one-line heading and indent the details by two spaces. Keep total output to a handful of lines and the run time under a second.@Component public class FooBarExampleRunner implements ExampleRunnerInterface { private static final Logger log = LoggerFactory.getLogger(FooBarExampleRunner.class); @Override public void runExample() throws Exception { log.trace("Executing FooBar Pattern Implementation:"); log.trace(" " + new FooBar().operation()); } }
-
Write the tests under the matching package in
src/test/java: one class exercising the pattern's behaviour and one asserting that the runner executes without error. The coverage gate applies to new code. -
Document it with a
README.mdin the package following the documentation conventions, and a class diagram: add the PlantUML source to the README and export it asassets/images/<name>.png. -
List it in the Catalogue above under the appropriate category.
If the example needs threads, make sure every thread it starts has finished or been interrupted before runExample() returns, so that repeated runs from the window do not leak resources.
Each pattern README follows the same outline, adapted from the pattern template of Gamma et al. [1]:
- Intent: the problem the pattern solves, in one or two sentences.
- Motivation: the forces that make the naive solution inadequate.
- Structure: the class diagram, as an image and as PlantUML source.
- Participants: the roles in the pattern and the class in this package that plays each one.
- The example: what the runner does and what output to expect.
- Consequences: benefits, costs and common misuses.
- Related patterns and References, with numbered citations in the text.
java-monitor-example is a companion repository by the same author dedicated to a single concurrency construct: the monitor, as formulated by Hoare [5] and Brinch Hansen [6], and its realisation in Java through intrinsic locks, synchronized and wait/notifyAll. It works through mutual exclusion and condition synchronisation on a shared pool of integers consumed exactly once each by competing threads, and visualises the contention in real time. Readers who want to understand what happens inside the BlockingQueue used by the Producer/Consumer example here should start there.
- E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
- J. Bloch, Effective Java, 3rd ed. Addison-Wesley, 2018.
- E. Freeman and E. Robson, Head First Design Patterns, 2nd ed. O'Reilly, 2020.
- B. Goetz, T. Peierls, J. Bloch, J. Bowbeer, D. Holmes and D. Lea, Java Concurrency in Practice. Addison-Wesley, 2006.
- C. A. R. Hoare, "Monitors: An Operating System Structuring Concept," Communications of the ACM, vol. 17, no. 10, pp. 549–557, 1974.
- P. Brinch Hansen, Operating System Principles. Prentice-Hall, 1973.
Issues are welcome for new examples, corrections to the explanations or additional references. Pull requests should target master, include tests, keep the coverage gate green and follow the conventions above. If you have an idea but are unsure how to implement it, open an issue describing the pattern or construct and the source you learned it from.