Skip to content
compiler.dev

Maven GitHub Actions Workflow Template

Last updated

This is a working .github/workflows/ci.yml for a Maven project. It installs Java 21 with the Maven dependency cache turned on, builds and runs unit and integration tests with mvn verify, and keeps test reports on failure.

The workflow

name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        java: [17, 21]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: ${{ matrix.java }}
          cache: maven

      - run: mvn --batch-mode --no-transfer-progress verify

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: surefire-reports-${{ matrix.java }}
          path: "**/target/surefire-reports/"

What the non-obvious parts do

  • cache: maven on setup-java caches ~/.m2/repository, keyed on the hash of all pom.xml files. No separate cache step is needed. When a pom.xml changes, a new cache is saved.
  • --batch-mode removes interactive prompts and colour codes. --no-transfer-progress hides the download progress lines that otherwise flood the log with thousands of lines.
  • verify runs everything up to integration tests (test, package, integration-test). Use mvn test for a faster unit-test-only check on pull requests.
  • Matrix on Java 17 and 21. Test the versions you support. If you ship on one version only, remove the matrix to halve the minutes. See matrix builds.
  • Artifact name includes the matrix value. Since upload-artifact v4, names must be unique within a run. See artifacts.
  • Wrapper. If your repo has mvnw, run ./mvnw so the Maven version is pinned.

Variants

Parallel module builds

Add -T 1C to build modules in parallel, one thread per core. This works only if modules and plugins are thread-safe.

Integration tests with Testcontainers

Docker is available on ubuntu-latest, so Testcontainers runs with no extra setup. The first image pull is the slow part; see Docker layer caching.

Database service container

Use a services: block with Postgres, as in the Django template, and pass the URL as an environment variable.

Deploy or publish job

Add a second job with needs: build on main only. For releases, use setup-java with server-id and server-password so Maven can read credentials from secrets.

macOS or Windows

Add an os axis only if the library must work on them.

Run it on faster machines

Maven can use -T and many cores for tests. Change one line:

    runs-on: [self-hosted, Linux, compiler-ci]

The job runs on a fresh 8 vCPU machine from compiler.dev, with automatic fallback to your original runner. compiler.dev's benchmark of a Spring Boot (Maven) app with Kafka, Postgres and Testcontainers went from 1m54s to 1m12s; see benchmarks. The tuning list is in speed up Maven on GitHub Actions, and prices on pricing.

FAQ

Do I need actions/cache for Maven?

No. setup-java with cache: maven does the work.

Why does the cache not restore after I edit a pom.xml?

The key includes every pom.xml, so a change creates a new entry. The old cache is not used, though Maven may re-download only what changed.

Can I cache Maven build output?

Maven has no built-in task cache. A remote build cache needs the Maven Build Cache Extension.

How do I run only changed modules?

Use -pl with -amd and a changed-files step, or move to a build tool with task caching.