Appendix B

JUnit 5 + Maven Setup

Running tests locally (optional — OnlineGDB still works)

Appendix B — JUnit 5 + Maven Setup

Read this first. You do not need anything on this page to pass Coding 2. The default workflow for the whole course is still OnlineGDB in your browser — see Coding 1’s Appendix D — The Online Coding Workflow, which applies unchanged here (Coding 2 just uses Java instead of C++). This appendix exists for one specific situation: you have a JUnit 5 test class — like the ones in Chapter 4, Chapter 9, and the Project 13 reps — and you want to run it and watch it pass or fail. That’s it. Everything below is one of several ways to do exactly that.


Here is the thing this appendix is actually about. A JUnit 5 test class is not a normal Java program. It has no main method. You can’t just type java EventLogTest and watch it go — there’s nothing for the JVM to start. Instead, a test runner has to load your test class, find every method marked @Test, call each one, catch the assertion failures, and print a report. JUnit 5 ships that runner; the only question this appendix answers is how you hand your compiled classes to it.

The tests you’ll meet look like this (from Chapter 9’s EventLogTest):

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

public class EventLogTest {

    @Test
    void newLogHasNoCategories() {
        EventLog log = new EventLog();
        assertTrue(log.categories().isEmpty());
    }
    // ... more @Test methods
}

Those org.junit.jupiter.api imports are JUnit 5 (its internal name is “Jupiter” — same thing). They are not part of the JDK. So whatever path you pick, the job is the same: put JUnit 5 on the classpath, compile, and let the runner do its work.

There are four paths below, ordered roughly from “least to install” to “most.” Pick one. You don’t need all four, and you almost certainly don’t need a local Maven install to finish this course.

A note on versions — As of 2026, the JUnit 5 line and a newer JUnit 6 line both exist on Maven Central, and the version numbers move every few months. Everywhere below, <ver> means “whatever the current version is on Maven Central the day you download it.” Don’t hard-code a number from this page — copy it off the download page. The course code only uses the basic @Test / assertEquals / assertThrows features, which have been stable across every 5.x and 6.x release.


This is the path that works almost anywhere a JDK works. You download one file — the JUnit Platform Console Standalone JAR — and from then on it is both your compiler dependency and your test runner. No Maven, no Gradle, no project files, no internet after the download.

You do not need admin rights for this, as long as java and javac already run in your terminal (see Coding 1’s Appendix B — Setting Up a Local JDK if they don’t, but check first — your machine may already have a JDK).

Step 1 — Download the one JAR

Go to Maven Central and search for junit-platform-console-standalone (the project page is at central.sonatype.com, and the raw files live under repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/). Open the newest version folder and download the file named:

junit-platform-console-standalone-<ver>.jar

It’s a single self-contained (“fat”) jar — it already bundles the JUnit Platform, the Jupiter test engine, and the console launcher inside it. There is nothing else to install. Put it in the same folder as your .java files so the commands below stay short. (You can keep it anywhere and use a longer path; same-folder is just easier to type.)

Step 2 — Compile your test and the class it tests

Both files go through javac together, with the jar on the classpath so the org.junit.jupiter.api imports resolve. The -d out puts the compiled .class files in an out/ subfolder so they don’t clutter your working directory:

javac -cp junit-platform-console-standalone-<ver>.jar -d out *.java

(If *.java doesn’t expand on your shell — some Windows shells don’t — just list the files: javac -cp junit-platform-console-standalone-<ver>.jar -d out EventLog.java EventLogTest.java.)

No output means it compiled. No output is good output. If you get errors here, they’re ordinary compile errors — fix them the same way you’d fix any other.

Step 3 — Run the tests

Now hand the jar your compiled classes and tell it to scan them for @Test methods:

java -jar junit-platform-console-standalone-<ver>.jar execute -cp out --scan-classpath

Three things worth understanding rather than memorizing:

  • execute is the subcommand that says “actually run the tests.” (As of 2026 the launcher wants this verb. On older 1.x jars the bare java -jar … --scan-class-path form, with no execute, also worked — so if you’re on a very old jar and execute is rejected, drop it. Adding execute is the safe modern choice.)
  • -cp out points the runner at the folder of compiled classes from Step 2. (--classpath and --class-path are accepted spellings of the same flag.)
  • --scan-classpath tells it “look through those classes and find everything annotated @Test.” (--scan-class-path, with the extra hyphen, is the same flag — both work.)

What success looks like

When every test passes, you get a tree and a summary something like this:


├─ JUnit Jupiter ✔
│  └─ EventLogTest ✔
│     ├─ newLogHasNoCategories() ✔
│     ├─ logRecordsAndRecentReturnsNewestFirst() ✔
│     └─ categoriesCannotBeMutated() ✔

Test run finished after 142 ms
[         8 tests successful      ]
[         0 tests failed          ]

A green checkmark on the summary line is the whole goal. (The exact box-drawing and timings vary by version and terminal — don’t anchor on the pixels, anchor on successful vs failed counts.)

What failure looks like

The Project 13 reps ship with planted bugs on purpose — several tests are supposed to be red until you fix the implementation. A failing run names the failing test and shows the mismatch:

├─ JUnit Jupiter ✔
│  └─ StudyStreakTrackerTest ✗
│     ├─ getCurrentStreak_initiallyZero() ✔
│     ├─ recordCheckIn_consecutiveDays_advancesStreak() ✗
│     │     expected: <3> but was: <1>
│     └─ formatStreak_singular_usesDay() ✗
│           expected: <1 day> but was: <1 days>

[        12 tests found           ]
[         7 tests successful      ]
[         5 tests failed          ]

That expected: <3> but was: <1> line is the gift. It tells you exactly what the contract wanted and what your code actually did. In Project 13 that’s your to-do list: drive each red test to green, one bug at a time.

Coach’s Note — This path is worth doing once even if you live in OnlineGDB, just so the words “test runner” stop being abstract. You will see JUnit load your class, call your @Test methods, and tally the results. After you’ve watched it once, the whole framework stops being magic and becomes “oh — it’s a program that runs my other programs and counts.” That demystification is most of the battle.


Path 2 — No-install cloud path (for locked-down laptops)

If you’re on a school- or work-managed laptop where you can’t install a JDK, can’t download jars, or the terminal is locked, you can still run JUnit 5 — in a full Linux machine that lives in your browser.

The most reliable option as of 2026 is GitHub Codespaces. A Codespace is a real Visual Studio Code running in your browser, backed by an actual Linux container with a real terminal. Because it’s a real machine, the Maven path in Path 3 below just works inside it — you open the terminal in the Codespace and type mvn test, exactly as if it were your own laptop, with nothing installed on your device. GitHub’s Java/Maven editor tooling also adds little green “run” arrows next to each @Test method once a project is opened. A free GitHub account includes a monthly allotment of Codespaces hours that is far more than this course needs.

Gitpod is an equivalent browser IDE (also full VS Code, also a real terminal, also free for light use) if you prefer it or your school standardizes on it. The workflow is the same: open a workspace, drop your .java files and a pom.xml (Path 3) into it, run mvn test in the terminal.

A few honest caveats so you’re not surprised:

  • These spin up a real environment, so the first launch takes a minute or two while it downloads Maven and JUnit. After that it’s quick.
  • They need network access and a GitHub (or GitLab/Google) login, so they’re for the “my laptop is locked, not my internet” case.
  • They are genuinely running the same mvn test you’d run locally — there’s no special trick, which is exactly why they’re trustworthy.

Coach’s Note — If your only goal is “run this one test class right now and my laptop won’t let me install anything,” a Codespace is the shortest honest path. It’s also a soft preview of Coding 3, where a cloud-or-local real dev environment becomes the everyday tool rather than the escape hatch.


Path 3 — Local Maven (mvn test)

This is the path the rest of the world uses on real projects, and it’s what Project 4 and Project 13 are quietly preparing you for. Maven is a build tool: you declare your dependencies once in a file called pom.xml, and Maven downloads them, compiles everything, and runs your tests with one command. It’s more machinery than Path 1, but once it’s set up, mvn test is the only command you’ll ever type.

Step 1 — Install Maven (and a JDK, if you don’t have one)

Maven needs a JDK underneath it — see Coding 1’s Appendix B — Setting Up a Local JDK if java -version doesn’t already work.

For Maven itself, a package manager is the no-fuss route and usually needs no admin rights if you already have the package manager:

  • macOS (Homebrew): brew install maven
  • Linux (Debian/Ubuntu): sudo apt install maven — or, to avoid sudo, use SDKMAN! (sdk install maven), which installs into your home directory.
  • Windows: the simplest no-admin route is to install SDKMAN! under Git Bash/WSL, or download the binary zip from maven.apache.org, unzip it into your home folder, and add its bin/ to your PATH. You don’t need administrator rights for the unzip-and-PATH approach.

Verify with:

mvn -version

It should print a Maven version and the JDK it found.

Step 2 — Lay out the project

Maven expects a specific folder shape. The minimum is:

my-project/
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── EventLog.java        ← the class under test
    └── test
        └── java
            └── EventLogTest.java    ← the @Test class

Your implementation classes go under src/main/java; your …Test classes go under src/test/java. (If a course file uses a package, mirror the package as subfolders here; the chapter examples are package-free, so the files sit directly in those folders.)

Step 3 — Write a minimal pom.xml

Drop this in the project root. It pulls in JUnit 5 (the junit-jupiter aggregate artifact, which is what the chapters reference) and wires up the Surefire plugin that runs the tests:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>coding2</artifactId>
  <version>1.0</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.10.0</version>   <!-- bump to the current 5.x/6.x on Maven Central -->
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

A few notes so nothing here is a mystery:

  • <scope>test</scope> means JUnit is only on the classpath while testing — it won’t ship with your actual program. That’s correct and intentional.
  • 5.10.0 is a known-good version the course chapters cite. Newer versions on Maven Central work the same for everything we do; bump it if you like, but you don’t have to.
  • maven.compiler.release is set to 17 because Coding 2 targets Java 17+. Set it to whatever LTS you installed (21 and 25 are fine too).
  • Recent Maven ships a recent Surefire that runs JUnit 5 out of the box, so this minimal pom needs no <build> block. If you’re on an older Maven and tests “aren’t found,” that’s the one thing to add — pin a newer maven-surefire-plugin version under <build><plugins>.

Step 4 — Run the tests

From the project root (the folder with pom.xml):

mvn test

Maven compiles src/main and src/test, then runs every @Test. The first run downloads JUnit (needs internet, one time); later runs are offline and fast. Output ends in a summary like:

[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

BUILD SUCCESS with zero failures is the goal. When a test fails, Maven prints BUILD FAILURE, names the failing test, and shows the same expected … but was … detail you saw in Path 1 — Surefire also writes a full report under target/surefire-reports/ if you want to read it later.

A one-line Gradle alternative

If your world is Gradle instead of Maven, the same idea in a build.gradle:

plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test { useJUnitPlatform() }   // ← required, or Gradle won't run JUnit 5

Then ./gradlew test. The useJUnitPlatform() line is the one people forget; without it Gradle ignores your JUnit 5 tests entirely.


Path 4 — OnlineGDB, honestly

OnlineGDB is the default tool for this course, and for plain Java programs with a main method it is excellent — the Project 13 implementation classes, the Chapter 9 EventLog demo, anything with a main runs there with one click, and you submit a share link exactly as described in Coding 1’s Appendix D.

Here’s the honest part. A JUnit class has no main, so “press Run” doesn’t have an obvious entry point the way a normal program does. Whether a given web IDE will discover and run your @Test methods depends on that IDE’s Java setup, and that behavior changes over time — so this appendix won’t promise you a specific button on a specific day. If JUnit runs cleanly for you in OnlineGDB’s Java project type, wonderful, keep going. If it doesn’t — if pressing Run on a test class does nothing useful, or complains there’s no main — don’t fight it. Use one of the three paths above:

  • the one-JAR path (Path 1) if you can run java/javac anywhere, or
  • a Codespace (Path 2) if your laptop is locked down and you can’t install anything.

Both of those are guaranteed to run JUnit 5, because you are running the JUnit runner directly rather than hoping a Run button does it for you.

A bridge that always works. If you ever need a JUnit-shaped test to run somewhere that only runs main methods, you can drive the runner yourself from a tiny main. This is the trick Chapter 4 hints at, and it’s worth knowing as a fallback:

import org.junit.platform.launcher.*;
import org.junit.platform.launcher.core.*;
import org.junit.platform.launcher.listeners.*;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

public class RunTests {
    public static void main(String[] args) {
        LauncherDiscoveryRequest req = LauncherDiscoveryRequestBuilder.request()
            .selectors(selectClass(EventLogTest.class))   // your test class
            .build();
        SummaryGeneratingListener listener = new SummaryGeneratingListener();
        LauncherFactory.create().execute(req, listener);
        listener.getSummary().printTo(new java.io.PrintWriter(System.out));
    }
}

Then you “Run” RunTests (which does have a main), and it runs EventLogTest and prints a pass/fail summary. This still needs JUnit 5 on the classpath — those org.junit.platform.launcher classes are bundled in the very same standalone jar from Path 1. It’s a convenience, not a different runner.


When things go wrong

error: package org.junit.jupiter.api does not exist — JUnit isn’t on the classpath. On Path 1, you forgot -cp junit-platform-console-standalone-<ver>.jar on the javac line, or the jar filename you typed doesn’t match the file you downloaded (check the version in the name). On Path 3, your pom.xml is missing the dependency, or the file isn’t named exactly pom.xml.

The runner says 0 tests found — Three usual causes: (1) you compiled the implementation but not the test class, so there are no @Test methods to find; recompile with both files. (2) You pointed --scan-classpath at the wrong folder — it must be the out directory that actually contains the .class files. (3) On Maven, your test class isn’t under src/test/java, or its name doesn’t end in Test (Surefire matches *Test by default — and the course files are already named that way, e.g. EventLogTest, so don’t rename them).

Error: Could not find or load main class EventLogTest — You tried to run the test class like a normal program (java EventLogTest). It has no main. Use the launcher (Path 1) or Maven (Path 3), not bare java.

mvn works but no tests run — Almost always an old maven-surefire-plugin that predates JUnit 5 support. Update Maven, or pin a newer Surefire in your pom.xml. On Gradle, it’s the missing useJUnitPlatform() line.

You edited a test file and now nothing matches — Don’t edit the provided …Test.java files. The Project 13 and Chapter 13 reps say this explicitly: the tests are the contract. Change your implementation, not the test. If you changed a test by accident, re-download the original.

Stuck for more than 20 minutes on setup — Stop. There is no merit badge for fighting a build tool alone. Fall back to OnlineGDB for the implementation work, or open a Codespace (Path 2) and run mvn test in a clean environment that you didn’t have to configure. The point of the course is the testing discipline, not the plumbing.


“But test everything; hold fast what is good.” — 1 Thessalonians 5:21 (ESV)

That verse opens Chapter 4 for a reason. The whole point of these tools is to make “test everything” cheap — one command, a green checkmark, and you know. Pick the path that gets you to that checkmark with the least friction, and spend your real hours on the thing that matters: writing code worth trusting.


Up next: Back to whatever sent you here — most likely Chapter 4 — Testing as Discipline or one of the Project 13 reps in Chapter 13. And remember: if any of this stalls on a day a project is due, the OnlineGDB workflow in Coding 1’s Appendix D is always a valid place to land.