google-site-verification: google61fe8ba583a51912.html
Efficient CI/CD: Mastering GitHub Actions Matrix Builds for Java Projects

Article

GitHub Actions Matrix builds are a powerful feature that allows you to run multiple jobs in parallel across different configurations. For Java teams, this means testing across multiple Java versions, operating systems, and dependency combinations simultaneously, significantly speeding up CI/CD pipelines while ensuring comprehensive compatibility.


What are Matrix Builds?

Matrix builds enable you to create multiple job configurations by specifying variables. GitHub Actions automatically generates and runs jobs for each combination, providing parallel execution and comprehensive testing coverage.

Key Benefits for Java Projects:

  • Multi-Version Testing: Test against multiple Java versions (8, 11, 17, 21)
  • Cross-Platform Validation: Ensure compatibility across Windows, Linux, macOS
  • Dependency Matrix Testing: Test with different dependency versions
  • Parallel Execution: Reduce overall build time
  • Comprehensive Coverage: Catch version-specific issues early

Basic Matrix Build Configuration

1. Simple Java Version Matrix

# .github/workflows/java-matrix.yml name: Java Matrix Build on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: name: Test on Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [8, 11, 17, 21] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Build with Maven run: mvn -B clean compile - name: Run tests run: mvn -B test

2. Multi-Dimensional Matrix

name: Multi-Dimensional Java Matrix on: [push, pull_request] jobs: build: name: Build - Java ${{ matrix.java }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: java: [11, 17, 21] os: [ubuntu-latest, windows-latest, macos-latest] # Exclude incompatible combinations exclude: - java: 21 os: windows-latest # Temporary exclusion for Java 21 on Windows steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' - name: Validate Java version run: java -version - name: Build with Maven run: mvn -B clean compile - name: Run tests run: mvn -B test

Advanced Matrix Strategies

1. Matrix with Different Build Tools

name: Multi-Build Tool Matrix on: [push, pull_request] jobs: test: name: Test - ${{ matrix.tool }} on Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17] tool: [maven, gradle] include: - tool: maven command: mvn cache-key: maven - tool: gradle command: gradle cache-key: gradle steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: ${{ matrix.cache-key }} - name: Build with ${{ matrix.tool }} run: ${{ matrix.command }} -B build - name: Test with ${{ matrix.tool }} run: ${{ matrix.command }} -B test

2. Database Integration Testing Matrix

name: Database Integration Matrix on: push: branches: [ main ] pull_request: branches: [ main ] env: MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 jobs: integration-test: name: DB Tests - Java ${{ matrix.java }} with ${{ matrix.database }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17] database: [postgresql-15, mysql-8, mariadb-10] include: - database: postgresql-15 db-image: postgres:15 db-port: 5432 db-name: testdb db-user: testuser db-password: testpass - database: mysql-8 db-image: mysql:8.0 db-port: 3306 db-name: testdb db-user: root db-password: password - database: mariadb-10 db-image: mariadb:10.11 db-port: 3306 db-name: testdb db-user: root db-password: password services: database: image: ${{ matrix.db-image }} ports: - ${{ matrix.db-port }}:${{ matrix.db-port }} env: POSTGRES_DB: ${{ matrix.db-name }} POSTGRES_USER: ${{ matrix.db-user }} POSTGRES_PASSWORD: ${{ matrix.db-password }} MYSQL_DATABASE: ${{ matrix.db-name }} MYSQL_ROOT_PASSWORD: ${{ matrix.db-password }} MARIADB_DATABASE: ${{ matrix.db-name }} MARIADB_ROOT_PASSWORD: ${{ matrix.db-password }} options: >- --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Wait for database run: | echo "Waiting for database to be ready..." sleep 30 - name: Run integration tests run: | mvn -B verify -Pintegration-test \ -Ddatabase.url=jdbc:postgresql://localhost:${{ matrix.db-port }}/${{ matrix.db-name }} \ -Ddatabase.username=${{ matrix.db-user }} \ -Ddatabase.password=${{ matrix.db-password }} env: DB_TYPE: ${{ matrix.database }}

Spring Boot Specific Matrix Configurations

1. Spring Profile Matrix Testing

name: Spring Profile Matrix on: [push, pull_request] jobs: spring-test: name: Spring Tests - ${{ matrix.profile }} on Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17, 21] profile: [dev, test, prod] include: - profile: dev active-profiles: dev,h2 - profile: test active-profiles: test,postgresql - profile: prod active-profiles: prod,mysql,metrics steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Test with profile ${{ matrix.profile }} run: | mvn -B test \ -Dspring.profiles.active=${{ matrix.active-profiles }} \ -Dskip.integration.tests=true - name: Integration tests if: matrix.profile == 'test' run: | mvn -B verify -Pintegration-test \ -Dspring.profiles.active=${{ matrix.active-profiles }}

2. Spring Boot Version Compatibility Matrix

name: Spring Boot Version Matrix on: push: branches: [ main ] schedule: - cron: '0 2 * * 1' # Weekly on Monday at 2 AM jobs: compatibility-test: name: Spring Boot ${{ matrix.spring-boot }} on Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17, 21] spring-boot: [3.1.0, 3.0.0, 2.7.0] exclude: - java: 21 spring-boot: 2.7.0 # Spring Boot 2.7 doesn't support Java 21 - java: 8 spring-boot: 3.1.0 # Spring Boot 3+ doesn't support Java 8 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Update Spring Boot version run: | mvn -B versions:set -DnewVersion=1.0.0-SNAPSHOT mvn -B versions:update-parent -DparentVersion=${{ matrix.spring-boot }} mvn -B versions:set-property \ -Dproperty=spring-boot.version \ -DnewVersion=${{ matrix.spring-boot }} - name: Build and test run: mvn -B clean verify

Gradle-Specific Matrix Configurations

1. Gradle Build Matrix

name: Gradle Matrix Build on: [push, pull_request] jobs: gradle-test: name: Gradle - Java ${{ matrix.java }} with ${{ matrix.gradle }} runs-on: ${{ matrix.os }} strategy: matrix: java: [11, 17, 21] gradle: [8.4, 8.3, 8.2] os: [ubuntu-latest, windows-latest] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'gradle' - name: Setup Gradle ${{ matrix.gradle }} uses: gradle/actions/setup-gradle@v3 with: gradle-version: ${{ matrix.gradle }} - name: Make gradlew executable if: matrix.os == 'ubuntu-latest' run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build - name: Run tests run: ./gradlew test - name: Run integration tests run: ./gradlew integrationTest

2. Gradle Feature Variant Matrix

name: Gradle Feature Variant Matrix on: [push, pull_request] jobs: variant-test: name: Variant ${{ matrix.variant }} - Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17] variant: [free, pro, enterprise] include: - variant: free build-args: -Pvariant=free - variant: pro build-args: -Pvariant=pro -PenablePremium=true - variant: enterprise build-args: -Pvariant=enterprise -PenablePremium=true -PenableEnterprise=true steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'gradle' - name: Build variant ${{ matrix.variant }} run: ./gradlew build ${{ matrix.build-args }} - name: Test variant ${{ matrix.variant }} run: ./gradlew test ${{ matrix.build-args }}

Advanced Matrix Patterns

1. Dynamic Matrix Generation

name: Dynamic Matrix from JSON on: [push, pull_request] jobs: generate-matrix: name: Generate Build Matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Generate matrix configuration id: set-matrix run: | MATRIX_JSON=$(cat <<EOF { "java": [11, 17, 21], "os": ["ubuntu-latest", "windows-latest"], "include": [ { "java": 11, "os": "ubuntu-latest", "distribution": "temurin", "experimental": false }, { "java": 17, "os": "ubuntu-latest", "distribution": "temurin", "experimental": false }, { "java": 21, "os": "ubuntu-latest", "distribution": "temurin", "experimental": true } ] } EOF ) echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT build: name: Build - Java ${{ matrix.java }} on ${{ matrix.os }} needs: generate-matrix runs-on: ${{ matrix.os }} strategy: matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: ${{ matrix.distribution || 'temurin' }} cache: 'maven' - name: Build run: mvn -B clean compile - name: Test if: ${{ !matrix.experimental || matrix.experimental == false }} run: mvn -B test

2. Conditional Matrix Execution

name: Conditional Matrix Builds on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: analysis: name: Static Analysis runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: SpotBugs analysis run: mvn -B spotbugs:check - name: PMD analysis run: mvn -B pmd:check test: name: Tests - Java ${{ matrix.java }} runs-on: ubuntu-latest needs: analysis if: ${{ always() && needs.analysis.result == 'success' }} strategy: matrix: java: [11, 17, 21] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Run unit tests run: mvn -B test -Dskip.integration.tests=true - name: Run integration tests if: matrix.java == '17' # Only run integration tests on Java 17 run: mvn -B verify -Pintegration-test deploy: name: Deploy runs-on: ubuntu-latest needs: test if: ${{ github.ref == 'refs/heads/main' && needs.test.result == 'success' }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Deploy to production run: mvn -B deploy -DskipTests env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Performance Optimization

1. Caching Strategies

name: Optimized Matrix with Caching on: [push, pull_request] jobs: test: name: Test - Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [8, 11, 17, 21] fail-fast: false # Don't fail all if one fails steps: - name: Checkout code uses: actions/checkout@v4 - name: Cache Maven dependencies uses: actions/cache@v3 with: path: | ~/.m2/repository **/target key: maven-${{ hashFiles('**/pom.xml') }}-${{ matrix.java }} restore-keys: | maven-${{ hashFiles('**/pom.xml') }} maven- - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' - name: Build and test run: | mvn -B clean test \ -Dmaven.test.failure.ignore=true \ -Dsurefire.rerunFailingTestsCount=2 - name: Upload test results uses: actions/upload-artifact@v3 if: always() with: name: test-results-java-${{ matrix.java }} path: | **/target/surefire-reports **/target/failsafe-reports retention-days: 7

2. Parallel Test Execution

name: Parallel Test Matrix on: [push, pull_request] jobs: parallel-tests: name: Parallel Tests - Shard ${{ matrix.shard }} runs-on: ubuntu-latest strategy: matrix: java: [17] shard: [1, 2, 3, 4] total-shards: [4] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Run parallel tests run: | mvn -B test \ -DtestShard=${{ matrix.shard }}/${{ matrix.total-shards }} \ -Dparallel=classes \ -DthreadCount=4 - name: Merge test reports if: matrix.shard == 1 run: mvn -B surefire-report:report-only

Security and Compliance Matrix

1. Security Scanning Matrix

name: Security Matrix Scan on: push: branches: [ main ] schedule: - cron: '0 2 * * *' # Daily at 2 AM jobs: security-scan: name: Security Scan - Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [11, 17, 21] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' - name: OWASP Dependency Check run: | mvn -B org.owasp:dependency-check-maven:check \ -Dformat=HTML \ -Dformat=JSON - name: Upload security report uses: actions/upload-artifact@v3 with: name: security-report-java-${{ matrix.java }} path: target/dependency-check-report.* - name: SonarQube analysis if: matrix.java == '17' run: | mvn -B sonar:sonar \ -Dsonar.projectKey=my-java-project \ -Dsonar.host.url=${{ secrets.SONAR_HOST_URL }} \ -Dsonar.login=${{ secrets.SONAR_TOKEN }}

Complete Enterprise Example

name: Enterprise Java Matrix Pipeline on: push: branches: [ main, develop, release/* ] pull_request: branches: [ main ] schedule: - cron: '0 6 * * 1-5' # Weekdays at 6 AM env: MAVEN_OPTS: -Djava.awt.headless=true -Dmaven.repo.local=/tmp/m2/repository jobs: quality-gates: name: Quality Gates runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Check formatting run: mvn -B spotless:check - name: Check dependencies run: mvn -B versions:display-dependency-updates - name: License compliance run: mvn -B license:check compatibility-matrix: name: Compatibility - ${{ matrix.java }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: quality-gates strategy: matrix: java: [11, 17, 21] os: [ubuntu-latest, windows-latest, macos-latest] exclude: - java: 21 os: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Build run: mvn -B clean compile - name: Unit tests run: mvn -B test -Dskip.integration.tests=true - name: Integration tests if: matrix.java == '17' && matrix.os == 'ubuntu-latest' run: mvn -B verify -Pintegration-test performance-matrix: name: Performance - Java ${{ matrix.java }} runs-on: ubuntu-latest needs: compatibility-matrix strategy: matrix: java: [11, 17, 21] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Performance tests run: | mvn -B verify -Pperformance-test \ -Dperf.test.duration=300s \ -Djava.version=${{ matrix.java }} - name: Upload performance results uses: actions/upload-artifact@v3 with: name: perf-results-java-${{ matrix.java }} path: target/performance-reports/ security-matrix: name: Security - Java ${{ matrix.java }} runs-on: ubuntu-latest needs: compatibility-matrix strategy: matrix: java: [11, 17] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' - name: Security scan run: | mvn -B org.owasp:dependency-check-maven:check \ -DsuppressionFile=security-suppressions.xml - name: Upload security report uses: actions/upload-artifact@v3 with: name: security-report-java-${{ matrix.java }} path: target/dependency-check-report.* deploy: name: Deploy to Environment runs-on: ubuntu-latest needs: [compatibility-matrix, performance-matrix, security-matrix] if: ${{ github.ref == 'refs/heads/main' && contains(needs.*.result, 'success') }} environment: production strategy: matrix: environment: [staging, production] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Java 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Deploy to ${{ matrix.environment }} run: | mvn -B deploy -DskipTests \ -Ddeploy.env=${{ matrix.environment }} \ -Ddeploy.token=${{ secrets.DEPLOY_TOKEN }}

Best Practices

1. Matrix Configuration Tips

# .github/workflows/best-practices.yml name: Matrix Best Practices on: [push, pull_request] jobs: optimized-matrix: runs-on: ubuntu-latest strategy: matrix: # Use semantic version names java-version: [11, 17, 21] # Limit matrix size for PRs include-pr: - java-version: 17 full-test-suite: false # Use fail-fast appropriately fail-fast: ${{ github.event_name == 'pull_request' }} # Set maximum parallel jobs max-parallel: ${{ github.event_name == 'pull_request' && 2 || 10 }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: java-version: ${{ matrix.java-version }} distribution: temurin cache: maven

2. Resource Optimization

name: Resource Optimized Matrix on: [push, pull_request] jobs: resource-aware: runs-on: ${{ matrix.runner }} strategy: matrix: java: [11, 17, 21] runner: [ubuntu-latest] include: - java: 21 runner: ubuntu-latest resource-profile: high-memory steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Java with optimized memory uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: temurin cache: maven java-package: jdk check-latest: true

Conclusion

GitHub Actions Matrix builds provide Java teams with:

  • Comprehensive Testing: Validate across multiple Java versions, OS platforms, and configurations
  • Faster Feedback: Parallel execution reduces CI/CD pipeline duration
  • Early Issue Detection: Catch compatibility issues before production
  • Resource Efficiency: Optimize cloud resource usage
  • Scalable Pipelines: Handle complex testing scenarios efficiently

By implementing matrix builds, Java teams can ensure their applications work reliably across all supported environments while maintaining development velocity and code quality.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper