JAVA Contents

CI steps, caching, artifacts

Build a production-grade CI pipeline for Java: caching, test stages, artifacts, security checks and failure isolation.

On this page

Why CI Is a Production System

CI is not a developer convenience. It is the production gatekeeper. If CI is slow, flaky, or incomplete, teams ship risk. Production symptoms of bad CI: - Developers bypass checks - Hotfixes skip gates - Deploys rely on manual trust CI must be fast, deterministic, and complete.

Incident Scenario: CI Allowed a Broken Artifact

Pipeline compiled code but skipped integration tests. The artifact deployed and failed on startup due to a migration mismatch. Rollback happened, but customers saw the outage. Root cause: Missing stage and weak gating.

Core CI Stages

A practical Java CI pipeline: 1. Checkout and dependency restore 2. Compile + unit tests 3. Static analysis baseline 4. Integration tests (Testcontainers) 5. Package artifact (jar) 6. Publish artifacts and reports 7. Optional security scans (dependencies, container) 8. Optional contract verification Stages must be explicit to isolate failures quickly.

Caching Done Right

Cache what is safe: - Maven local repository - Gradle caches Avoid caching compiled outputs across commits unless keys are strict. Bad caching creates heisenbugs. Use cache keys based on build files and lockfiles so cache invalidation is correct.

Artifacts and Traceability

Always produce immutable artifacts: - versioned jar - build metadata (commit hash) - test reports - coverage reports If you cannot trace what was deployed, you cannot debug incidents.

Developer Feedback

CI must surface failures clearly: - attach test reports - highlight failing tests - expose container logs when integration tests fail If developers cannot see why it failed, they will rerun until green.

Security Gates

At minimum: - dependency vulnerability scan - fail on critical vulnerabilities introduced by new changes Tune thresholds. Too noisy kills adoption. Too loose is meaningless.

Example: GitHub Actions Pipeline

This example runs: - unit tests - integration tests with Testcontainers - Gradle caching - artifact and test report upload Notes: - For integration tests, you typically need Docker available on the runner. - If your integration tests rely on containers, ensure they run in a dedicated stage so failures are obvious. - Keep unit tests fast, integration tests real.
name: CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"

      - name: Cache Gradle
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission for Gradle wrapper
        run: chmod +x ./gradlew

      - name: Unit tests
        run: ./gradlew test --no-daemon

      - name: Integration tests
        run: ./gradlew integrationTest --no-daemon

      - name: Build artifact
        run: ./gradlew build --no-daemon

      - name: Upload test reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-reports
          path: |
            **/build/reports/tests
            **/build/test-results

      - name: Upload build outputs
        if: success()
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: |
            **/build/libs/*.jar

Example: Maven Variant

If you use Maven, the equivalent stage separation looks like: - mvn -DskipITs test - mvn -DskipUTs verify Keep the same principles: explicit stages and deterministic artifacts.

Checklist

- Separate unit and integration test stages - Use safe caching with deterministic keys - Publish test reports and build artifacts - Include commit metadata in artifacts - Add baseline static analysis - Add dependency/security checks with sane thresholds - Keep CI fast and reliable to preserve trust CI is where quality becomes enforceable. Treat it like a product.