Skip to content

Appendix B: Tools and Reproducible Setup

Pinned toolchain, Maven build commands, PMD setup, and Git pull request workflows.

OO Development and Agile Practices
Designing, Testing and Evolving Software with AI Assistance

How to use this appendix

A design discussion is only useful if you can run the code it describes. This appendix records, in one place, the tools the book uses, the exact versions it pins, the commands that build and test the examples, and the Git and pull-request routine your team will follow. Chapters explain design and cite this appendix for setup, so that a change of tool does not force a rewrite of an explanation.

Start with B.1 and B.2: they tell you what to install and how to check it. Chapters 1 to 6 need only a JDK and a Bash shell. From Chapter 7 the examples use Maven. Use B.8 beside Chapter 2’s Git exercise and again when your team starts its pull-request routine. The remaining sections are reference: look up a tool when a chapter first uses it.

This appendix records what works for the book’s examples. It is not institutional policy. Your approved assessment instructions decide which tools you may use in an assessment, and a section here never grants permission. The Appendix B companion (in the Code, Explanations and Solutions companion) contains a complete small starter project (every file), a scripted Git walk-through and the answers to the activity in B.12.

B.1 What you need, and when

ToolNeeded fromUsed forWhat the book pins or requires
JDK (a full Java Development Kit, not only a runtime)Chapter 1Compile and runVersion 17 or later. The examples are compiled with --release 17.
BashChapter 4The check scriptsGit Bash on Windows; a terminal on macOS or Linux.
GitChapter 2Version control and reviewVersion 2.28 or later, because the commands use git init -b, git switch and git restore.
Apache MavenChapter 7Build, dependencies and testsVersion 3.9.11.
JUnit JupiterChapter 7Unit tests5.13.4 (the junit-jupiter aggregate artifact).
MockitoChapter 7One interface-only test doublemockito-core 5.20.0.
Maven Compiler Plugin, SurefireChapter 7Compile with --release 17; run the tests3.14.1 and 3.5.4.
PMDChapter 5 onlyThe static-analysis lab7.28.0.
GitHub ActionsChapters 4 and 13A hosted checkactions/checkout@v7, actions/setup-java@v6, Temurin 17.
An editor or IDEAnyWriting codeNot pinned. Use what you know.

What “pinned” means. A pinned version is named exactly, so that everyone who follows the book gets the same behaviour. The Maven versions live in each project’s pom.xml; the others are named in this table. When a tool changes, this table and the POMs change and the design explanations stay as they are. Maven plugins that the POM does not name take the versions that Maven 3.9.11 itself binds; the two that appear in the examples are the Resources plugin (3.3.1) and the Jar plugin (3.4.1).

Newer releases exist. The Maven site lists a newer stable release than 3.9.11. The book’s output was produced with 3.9.11, so use that version when you want to reproduce what the chapters show, and treat a newer one as your own experiment.

Why the book compiles with --release 17

The examples use only the Java 17 language and library. If you compile with a newer JDK and no option, the compiler checks your code against that JDK’s library, so you can call a method that does not exist in Java 17 and nothing complains until the program meets a Java 17 runtime. The option --release 17 asks the compiler to check against the Java 17 language and library instead. The following program uses getFirst(), a method added to lists after Java 17:

import java.util.List;

public class NewApi {
    public static void main(String[] args) {
        System.out.println(List.of("a", "b").getFirst());
    }
}

Compiled with plain javac on a recent JDK it succeeds and prints a. Compiled with javac --release 17 it fails with cannot find symbol for getFirst(). The option restricts what the compiler accepts. It does not make a newer runtime behave like Java 17, so run java -version separately to see which runtime you have [1].

B.2 Check your setup

The companion lists scripts/check-setup.sh. It changes nothing and downloads nothing; it reports whether each tool is on the path and whether the compiler is recent enough:

bash scripts/check-setup.sh

On a machine where everything is in place it prints one OK line per tool and ends with SETUP OK:

OK       bash: GNU bash, version 5.3.9(1)-release (x86_64-pc-cygwin)
OK       git: git version 2.54.0.windows.1
OK       java: java version "24.0.2" 2025-07-15
OK       maven: Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b)
OK       javac: compiler version 24 can compile with --release 17
SETUP OK

The version text on your machine will differ. What matters is that no line starts with MISSING or TOO-OLD. A MISSING line names the command that is not on the path. A TOO-OLD line means the compiler cannot target Java 17; install a newer JDK. A runtime without javac is not enough, which is why the script checks the compiler separately.

Installing. Obtain a JDK of version 17 or later from the vendor your institution recommends. The book’s hosted-check example uses the Eclipse Temurin build of Java 17 [2]. For Maven, the project’s installation page says to extract the binary archive, add its bin directory to the PATH, make sure a JDK is found either through the JAVA_HOME variable or through java on the path, and confirm with mvn -v in a new shell [3]. To reproduce the book, take version 3.9.11 from the Apache archive, where each archive is published with a checksum file [4]:

apache-maven-3.9.11-bin.zip
SHA-512  03e2d65d4483a3396980629f260e25cac0d8b6f7f2791e4dc20bc83f9514db8d0f05b0479e699a5f34679250c49c8e52e961262ded468a20de0be254d8207076

Compare it with the digest of your download (sha512sum apache-maven-3.9.11-bin.zip) before you unpack it. If the two differ, delete the file and download it again.

Windows notes. Use Git Bash for the scripts. Windows can have more than one program called bash on the path, among them the one from Git for Windows and the Windows Subsystem for Linux launcher, and the first one found runs your script. Scripts that assume Windows-style paths fail under the wrong one. The book’s scripts take the Maven command from an optional variable, CHAPTER_MVN, for the case where mvn is not on the path or you call it by its full path; on Windows the launcher is mvn.cmd.

B.3 Compile and run without a build tool (Chapters 1 to 6)

The early chapters use plain Java. A typical compile-and-check pair is:

javac --release 17 -Xlint:all -encoding UTF-8 -d build/classes src/main/java/*.java src/test/java/*.java
java -cp build/classes Chapter04Demo

-Xlint:all turns on every compiler warning category, -encoding UTF-8 makes the source encoding explicit, -d names the output folder and -cp (class path) tells java where to find the compiled classes. The shell expands *.java into the files that are directly inside the two folders; it does not enter sub-folders, so a project that grows nested packages needs a build tool. Chapter 4’s scripts/check.sh wraps these commands, stops at the first failure and ends with an explicit success message that appears only if everything before it passed. Appendix A’s lab uses the same commands.

B.4 Build and test with Maven (from Chapter 7)

Maven builds from a pom.xml that names the project, the Java release, the dependencies and the plugin versions. By convention, application code is in src/main/java, tests in src/test/java and test resources in src/test/resources. Generated files go to target, which Git ignores.

A Maven build moves through phases in a fixed order, and asking for a phase runs everything before it. The ones the book uses are compile, test, package and verify [5]. The command you will run most is:

mvn --batch-mode verify

--batch-mode removes interactive prompts and colour codes, so the output is the same in a terminal, in a script and in a hosted check. A successful run ends with BUILD SUCCESS and exit status 0; a failure prints BUILD FAILURE and returns a non-zero status, which is what scripts and hosted checks look at.

CommandWhat it does
mvn --batch-mode verifyCompile, run all tests, package, verify. The normal check.
mvn --batch-mode testCompile and run the tests only.
mvn --batch-mode -Dtest=ToolchainReportTest testRun one test class.
mvn --batch-mode "-Dtest=ToolchainReportTest#aJavaVersionBelowTheTargetCannotRunTheCode" testRun one test method. Quote the argument so the shell does not treat # as a comment [6].
-Dgroups=integration, -DexcludedGroups=integration,e2eRun or leave out tests that carry a JUnit tag (Chapter 13) [7].
--offlineDo not contact any remote repository.
-Dmaven.repo.local=PATHUse another local repository folder.

What the first build downloads. Maven fetches the plugins and dependencies named in the POM, and the plugins’ own dependencies, from Maven Central, and keeps them in a local repository, by default the .m2/repository folder in your home directory. Later builds reuse that folder. --offline works only when the folder already holds everything. On an empty one it fails with a message such as:

Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact
org.apache.maven.plugins:maven-resources-plugin:jar:3.3.1 has not been downloaded from it before.

Run one online build first, or copy a populated repository folder to the machine. Behind a proxy, Maven reads a <proxies> section from settings.xml, either in the Maven installation’s conf folder or in .m2 in your home directory [8]. Ask your institution for the proxy details; the book does not supply any.

Counting tests. Surefire reports invocations: a parameterised test with three values counts as three. The starter in the companion has five test methods and runs seven invocations. When you report a count, say what you counted and do not compare counts from different projects.

Warnings that are not failures. On a recent JDK a passing build can still print warnings. Maven’s own libraries may print WARNING: A terminally deprecated method in sun.misc.Unsafe has been called; they come from Maven itself and do not mean your project is wrong. Mockito may print a notice that it is attaching itself as a Java agent (see B.5). Read the last lines: BUILD SUCCESS and a zero exit status mean the build passed.

B.5 JUnit and Mockito

JUnit Jupiter is the test framework. The single dependency junit-jupiter brings the API for @Test, the parameterised-test support (@ParameterizedTest) and the engine that runs them, and Surefire runs it through the JUnit Platform [9]. The POM gives it the scope test, so it is on the class path for tests and not for the application.

Mockito creates test doubles. The book uses it in one place, to replace the notification interface in Chapter 7, and configures it for interface-only doubles. A one-line file, src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker, contains mock-maker-proxy, which selects the mock maker that Mockito’s documentation describes as avoiding code generation and able to mock only interfaces [10]. With it you cannot mock a final class or a Clock; use a real value or a small hand-written fake instead. Chapter 7 shows hand-written fakes and explains where each kind of double fits.

Whether you may use a mocking framework in an assessment is decided in your assessment instructions. This appendix only records how the book configures it.

The agent warning. On a recent JDK, Mockito can still load its helper as a Java agent when it starts, and the JVM prints a warning that a Java agent has been loaded dynamically. The tests pass either way. The JDK’s own documentation says that dynamic loading will be disallowed by default in a future release, and names the option -XX:+EnableDynamicAgentLoading that permits it and hides the warning [11]. To run the starter without the warning:

mvn --batch-mode -DargLine=-XX:+EnableDynamicAgentLoading verify

B.6 Static analysis with PMD (Chapter 5)

Chapter 5’s lab uses PMD 7.28.0. It is not a Maven dependency: you download it once and run it from the chapter’s script.

  1. Download pmd-dist-7.28.0-bin.zip from the project’s GitHub releases page (tag pmd_releases/7.28.0) [12].
  2. Compare its SHA-256 digest with the one the release page shows. The digest recorded for this file is:
f974ba571f7bc01c73fffe11bade25fbc1f438698f9e9c00e416e8d65e9fbac2
  1. Unzip it into a new folder and set PMD_HOME to the pmd-bin-7.28.0 folder inside it. PMD needs a Java runtime of version 8 or above.
  2. From each Chapter 5 version, run bash scripts/pmd.sh. Confirm the installation with "$PMD_HOME/bin/pmd" --version (pmd.bat on Windows), whose output includes PMD 7.28.0.

A static analyser reports patterns, not defects. Its output is specific to its version and ruleset, and a different version may report different findings. Treat a finding as a prompt for review.

B.7 Dependency-injection containers (Chapter 6)

The book uses no library container. Chapter 6 walks through a small educational container written in plain Java, so there is nothing to install for that section. It also compares the walk-through with what a framework container does, for reading only. If your team uses a framework container in its own project, record its name and exact version in your team’s setup notes, as the pinned tools above are recorded here.

B.8 Git and the pull-request routine

One-time setup

Tell Git who you are, once per machine. Use your own name and address; do not use another person’s identity.

git config --global user.name "Your Name"
git config --global user.email "[email protected]"

On Windows, Git may convert line endings to CRLF when it checks files out and print a warning such as LF will be replaced by CRLF the next time Git touches it. The warning is harmless for Java files. It matters for shell scripts, because Linux Bash and WSL do not accept a CRLF script (set -euo pipefail fails with invalid option name). Every starter in this book therefore contains a .gitattributes file with one line, *.sh text eol=lf, which keeps shell scripts in LF form [13].

The commands you will use

The companion’s git-walkthrough.sh runs all of these in a throwaway folder, with a bare repository standing in for the remote so that no account or network is needed. Its output matches what is shown here.

TaskCommandWhat to look for
Start a repositorygit init -b mainAn empty history on branch main.
See what changedgit status --shortM modified, A staged new file, ?? untracked.
See the changegit diffUnstaged changes only.
See what will be committedgit diff --stagedStaged changes. An empty git diff after staging is normal.
Stage a filegit add path/to/fileName files; avoid git add . until you have read git status.
Record a commitgit commit -m "Say what changed"One logical change per commit.
Start a branchgit switch -c feature/short-nameSwitched to a new branch.
Return to a branchgit switch main
Discard an edit to a tracked filegit restore path/to/fileThe edit is gone: this cannot be undone [14].
Unstage a filegit restore --staged path/to/fileThe file stays changed but leaves the next commit.
Reverse a commit safelygit revert --no-edit HEADA new commit that undoes the old one; history is kept [15].
Combine branchesgit merge feature/short-nameFast-forward when main has not moved, otherwise a merge commit.
Look at historygit log --oneline --graph --decorateOne line per commit, with the branches.
Sharegit push -u origin feature/short-nameThe branch exists on the remote and is tracked.
Copy a repositorygit clone URLA full copy with its history.

Two cautions. First, git restore on a tracked file and git reset --hard throw away uncommitted work. Run git status first. Second, never rewrite the history of a branch that other people have pulled, for example with a forced push, unless your team has agreed to it.

From change to merge

A pull request (a merge request on some platforms) is a proposal, on a hosting platform, to bring one branch into another so that others can review it. It is not the same as a commit [16]. A routine that works for a small team:

  1. Update your copy of the shared branch.
  2. Create a short branch for one change.
  3. Make small commits, each with a message that says what changed. A team may adopt a written convention such as Conventional Commits; use whichever it agrees and apply it consistently (Chapter 2).
  4. Run the project’s check script before you push, and keep its output.
  5. Push the branch and open the pull request. Describe the problem, the limited change and the checks you ran, with their results.
  6. Reviewers read the diff and comment on what they inspected and why it matters. Answer by adding commits, not by rewriting the ones they have read.
  7. Merge when the agreed review is complete and the required check has passed. Platforms offer a merge commit, a squash or a rebase; your repository’s setting decides which one you get.
  8. Delete the merged branch and update your copy of the shared branch.

Steps 5 to 8 happen in the hosting platform’s interface, which differs between platforms and institutions. The book supplies no remote and no account. Record real pull-request addresses and real review comments in your team’s evidence notes, and never invent review attribution. Commit and pull-request counts show activity, not contribution or quality; Chapter 14 and Appendix E explain what such evidence can and cannot show.

B.9 A starter hosted check

The companion lists .github/workflows/verify.yml. It asks GitHub Actions to run the project’s check script on every push and pull request, and when started by hand:

name: Verify

on:
  push:
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: '17'
          cache: maven
      - name: Build and test
        run: bash scripts/check.sh

permissions: contents: read gives the job read access only. actions/checkout fetches your repository, and actions/setup-java installs a Java 17 JDK from the Temurin distribution and, with cache: maven, keeps Maven’s downloads between runs [17][18]. The job then runs the same script you run locally, so a local pass and a hosted pass mean the same thing. A push that belongs to an open pull request can start two runs; the template accepts that.

A workflow file in the repository is only a request. It becomes evidence when it has actually run on your repository, and you can quote its run address. A green run shows that the script passed on a clean machine of one kind. It does not show that the tests are good, that the design is sound or that a reviewer agreed. To make a pull request wait for the check, configure the shared branch to require it where the platform supports that, or write the manual rule down. If no hosted platform is available, run the script locally and keep its output; that is a weaker but honest substitute.

Major action versions and runner images change. Check the current versions before you reuse the file, and record the versions that a run actually resolved from its log.

B.10 Repository metrics

Chapter 12 measures flow from real data, and its repository lab pins one method, RM-12, with a supplied calculator and a blank evidence record. This appendix adds only the data-collection facts that every method shares.

Git alone can list commits with their times, for example:

git log --first-parent -20 --format='%H%x09%cI'

%H is the commit hash and %cI the committer time in strict ISO form. Following only the first parent changes which history you see. A list of commits does not give a pull request’s opening time, its review time or any deployment time, and commit counts do not measure work. Those times come from the hosting platform’s interface or an authorised export with exact timestamps. If you can see only relative dates such as “3 days ago”, you cannot calculate a precise duration; say so, and do not guess a time.

The module’s agreed repository-metrics method is not settled by this appendix. Until your module states one, follow Chapter 12.

B.11 When something goes wrong

What you seeLikely causeWhat to do
mvn: command not foundMaven’s bin folder is not on the PATH.Run scripts/check-setup.sh; fix the PATH and open a new shell.
MISSING or TOO-OLD from check-setup.shA tool is absent, or the JDK is older than 17 or has no compiler.Install a full JDK of version 17 or later.
Cannot access central ... in offline mode--offline with an empty or incomplete local repository.Run one online build, or supply a populated repository folder.
BUILD FAILURE, Tests run: ..., Failures: 1A test failed.Open the named test, read the expected and actual values, and decide whether the code or the test is wrong.
No tests matching pattern ... were executed-Dtest= named a class or method that does not exist.Check the spelling; the message names the pattern it used.
WARNING: ... sun.misc.Unsafe ...Maven’s own libraries on a recent JDK.Ignore if the build ends in BUILD SUCCESS.
WARNING: A Java agent has been loaded dynamicallyMockito attaching its helper (B.5).Ignore, or add -DargLine=-XX:+EnableDynamicAgentLoading.
set: pipefail: invalid option nameA shell script was checked out with CRLF line endings and run by Linux Bash or WSL.Add .gitattributes with *.sh text eol=lf and check the file out again (Chapter 4).
LF will be replaced by CRLF from GitWindows line-ending conversion.A warning only; see B.8.
A script behaves differently in two terminalsDifferent programs called bash on the PATH.Run it from Git Bash and check with command -v bash.

B.12 A short verification activity

Do these in a copy of the companion’s starter project, in a throwaway folder.

  1. Run bash scripts/check-setup.sh. Resolve any MISSING or TOO-OLD line.
  2. Run bash scripts/check.sh. Note the number of tests Maven reports and say what it counted.
  3. In a scratch copy, change >= to > in ToolchainReport.canRunOn, and run bash scripts/check.sh again. Read the failing test’s message. Restore the original.
  4. Initialise a repository, commit the starter on main and make the one-line change from step 3 on a branch. Run git diff, stage the file, run git diff --staged, and commit.
  5. Write a short setup note: the versions your machine reported, the commands you ran and anything that differed from this appendix.

Optional AI activity, with an identical offline route. The following is an author-constructed candidate answer, not a captured model response:

“Any Java installation will do because the code is plain Java. To confirm a project works, run mvn install -DskipTests. If tests fail unpredictably, add -Dmaven.test.failure.ignore=true so the build always passes. Share your work with git add . and git push --force.”

Check each sentence against the starter and this appendix, and say what evidence you used. A live assistant may produce a different candidate if your course allows one, but it is not required. Record the claim, your check and your conclusion; do not accept an unsupported explanation as the final evidence.

What you hand in at the end is the short setup note. This is formative practice and adds no assessment requirement.

Sources and further reading

Official references opened on 13 October 2026. The explanations and commands are original; source examples have not been reproduced.

  1. Oracle, The javac Command, Java SE 17. The --release option and compilation target.
  2. Eclipse Adoptium, Temurin. A source of JDK builds; the build named in the hosted-check example.
  3. Apache Maven, Installation. Prerequisites, extracting the archive, adding bin to the path and mvn -v.
  4. Apache, Maven 3.9.11 binary archive and checksum. The archive and its .sha512 file.
  5. Apache Maven, Introduction to the build lifecycle. Phases and their order.
  6. Apache Maven, Surefire: running a single test. The -Dtest parameter.
  7. Apache Maven, Surefire and the JUnit Platform. Tag selection with groups and excludedGroups.
  8. Apache Maven, Settings reference. The local repository, offline mode and proxies.
  9. JUnit, User Guide 5.13.4. Dependency artifacts and parameterised tests.
  10. Mockito, MockMakers. The proxy mock maker, which can mock only interfaces.
  11. OpenJDK, JEP 451: Prepare to Disallow the Dynamic Loading of Agents. The warning and the -XX:+EnableDynamicAgentLoading option.
  12. PMD, Installation and the 7.28.0 release page.
  13. Git, gitattributes. The eol and text attributes.
  14. Git, git-restore and git-switch.
  15. Git, git-revert. A revert records a new commit.
  16. GitHub Docs, About pull requests. Also Chacon and Straub, Pro Git: branching.
  17. GitHub, actions/checkout. Usage and versions.
  18. GitHub, actions/setup-java. Distributions, java-version and cache: maven.