CI pour Java avec Maven sur GitHub Actions

1. Vue d’ensemble

L’intégration continue (CI) vous aide à détecter les erreurs dès que vous poussez du code, plutôt que d’attendre le merge et de tout casser. Cet article vous guide pour construire un pipeline CI pour votre projet Java utilisant Maven sur GitHub Actions : build, exécuter les tests, mettre en cache les dépendances, une matrice build pour plusieurs versions JDK, et publier les rapports. Après l’avoir lu, vous pouvez copier le fichier workflow dans votre vrai projet et le lancer en quelques minutes.

2. Préparation du projet exemple

Supposons que vous ayez un simple projet Spring Boot avec une structure Maven standard. Voici un fichier pom.xml minimaliste :

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.nonladev</groupId>
    <artifactId>order-service</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.25.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
            </plugin>
        </plugins>
    </build>
</project>

Les dépendances junit-jupiter et assertj-core servent à écrire des tests. Le plugin maven-surefire-plugin est responsable d’exécuter les tests lors de l’appel à mvn test.

Une petite classe métier à tester :

package com.nonladev.order;

public class OrderCalculator {

    public double calculateTotal(double unitPrice, int quantity) {
        if (quantity <= 0) {
            throw new IllegalArgumentException("Quantity doit être supérieure à 0");
        }
        return unitPrice * quantity;
    }
}

Et le test correspondant :

package com.nonladev.order;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class OrderCalculatorTest {

    private final OrderCalculator calculator = new OrderCalculator();

    @Test
    void shouldCalculateTotalCorrectly() {
        double total = calculator.calculateTotal(50_000, 3);

        assertThat(total).isEqualTo(150_000);
    }

    @Test
    void shouldThrowWhenQuantityIsInvalid() {
        assertThatThrownBy(() -> calculator.calculateTotal(50_000, 0))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("Quantity doit être supérieure à 0");
    }
}

shouldCalculateTotalCorrectly vérifie que le total est calculé correctement. shouldThrowWhenQuantityIsInvalid vérifie que l’exception est levée quand quantity n’est pas valide. Ce sont les tests que CI exécutera à chaque fois que vous poussez du code.

3. Workflow CI basique

GitHub Actions lit les fichiers YAML du répertoire .github/workflows/. Créez le fichier .github/workflows/ci.yml :

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest

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

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

      - name: Build and run tests
        run: mvn -B clean verify

Voici l’explication de chaque partie :

  • on : le workflow s’exécute lors d’un push ou d’une pull_request ciblant la branche main.
  • actions/checkout@v4 : clone le code source dans le runner.
  • actions/setup-java@v4 : installe JDK 21 Temurin — vous avez toujours besoin de cette étape avant d’appeler mvn.
  • mvn -B clean verify : le drapeau -B (batch mode) désactive la sortie interactive, approprié pour un environnement CI. clean verify reconstruit à partir de zéro et exécute les tests (la phase verify inclut test).

Poussez un commit, allez dans l’onglet Actions sur GitHub, vous verrez le job s’exécuter avec un log similaire :

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.nonladev.order.OrderCalculatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Si un test échoue, le job aura un statut rouge et vous verrez la stack trace directement dans le log — pas besoin d’SSH sur une machine.

4. Mettre en cache les dépendances Maven pour un build plus rapide

Chaque fois que le job s’exécute sur un nouveau runner (éphémère), Maven doit re-télécharger toutes les dépendances depuis .m2/repository. Avec un projet Spring Boot, cela peut prendre 1 à 2 minutes à chaque fois. setup-java supporte déjà le cache :

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

La propriété cache: 'maven' met automatiquement en cache le répertoire ~/.m2/repository en fonction du hash du fichier pom.xml. La prochaine build, si pom.xml n’a pas changé, Actions restaure le cache immédiatement, en passant l’étape de re-téléchargement de chaque jar.

Vous pouvez vérifier l’efficacité en comparant le temps de l’étape « Build and run tests » avant et après l’ajout du cache — généralement une réduction de 30 à 60 %.

Note : Si votre projet utilise Maven Wrapper (./mvnw), vous devriez plutôt mettre en cache selon mvnw au lieu de faire confiance à la version de Maven pré-installée sur le runner, car chaque fois que GitHub met à jour l’image, la version par défaut de Maven peut changer.

5. Matrice build avec plusieurs versions JDK

Si vous maintenez une bibliothèque qui doit être compatible avec plusieurs versions de Java, utilisez strategy.matrix pour exécuter plusieurs jobs en parallèle :

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java-version: [ '17', '21' ]

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

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

      - name: Build and run tests
        run: mvn -B clean verify

matrix.java-version énumère la liste des versions à tester. GitHub Actions crée automatiquement 2 jobs s’exécutant en parallèle : l’un avec JDK 17, l’autre avec JDK 21. La variable ${{ matrix.java-version }} est substituée dans chaque job correspondant.

Le résultat dans l’onglet Actions affiche 2 jobs distincts, par exemple build (17) et build (21), vous permettant de savoir immédiatement à quelle version de Java votre projet échoue.

6. Publier les rapports de test et vérifier la couverture

Par défaut, les logs de test s’affichent uniquement en texte dans Actions. Pour un rapport plus facile à lire, utilisez l’action dorny/test-reporter :

      - name: Build and run tests
        run: mvn -B clean verify

      - name: Publish test report
        uses: dorny/test-reporter@v1
        if: success() || failure()
        with:
          name: Maven Tests
          path: '**/surefire-reports/*.xml'
          reporter: java-junit

if: success() || failure() : cette étape s’exécute même quand les tests échouent (mais ne s’exécute pas si le job est annulé), afin que vous voyiez toujours le rapport. path pointe vers le fichier XML généré par Surefire après l’exécution des tests. Le rapport apparaît comme une vérification distincte dans la PR, énumérant chaque cas de test réussi/échoué.

Si vous voulez mesurer la couverture, ajoutez le plugin JaCoCo à pom.xml :

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.12</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Exécuter mvn verify génère automatiquement un rapport HTML à target/site/jacoco/index.html. Vous pouvez le télécharger comme artefact :

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: jacoco-report
          path: target/site/jacoco/

Cet artefact peut être téléchargé à partir de la page de résumé de l’exécution du workflow et consulté directement dans le navigateur.

7. Badge d’état de build

Après que le workflow s’exécute de manière stable, ajoutez un badge à README.md pour que tout le monde connaisse l’état du CI sans avoir besoin d’aller sur Actions :

![CI](https://github.com/<org>/<repo>/actions/workflows/ci.yml/badge.svg)

Remplacez <org>/<repo> par le vrai nom GitHub. Le badge se met automatiquement à jour en vert/rouge selon le résultat du dernier lancement sur la branche par défaut.

8. Erreurs courantes

  • Oublier de définir java-version pour qu’il corresponde à maven.compiler.source : le build local réussit mais CI échoue avec l’erreur release version X not supported. Synchronisez toujours la version JDK entre pom.xml et le workflow.
  • Utiliser mvn test au lieu de mvn verify : si votre projet a des tests d’intégration s’exécutant à la phase verify (par exemple plugin Failsafe), mvn test les contournera, créant une fausse sensation de sécurité.
  • Mise en cache incorrecte de la clé lors du changement de dépendances : si vous écrivez manuellement une action de cache (actions/cache) et oubliez d’inclure pom.xml dans la clé hash, l’ancien cache peut faire en sorte que le build utilise des dépendances obsolètes. Utilisez cache: 'maven' de setup-java pour l’éviter.
  • Exécuter CI sur un runner auto-hébergé sans Docker/réseau stable : Maven a besoin de télécharger les dépendances depuis le dépôt central ; si le runner est bloqué par le réseau, le build va traîner lors de l’étape de téléchargement. Vérifiez le proxy ou utilisez un miroir Maven interne.

Note : Si votre projet est très petit, avec seulement quelques tests simples et pas de déploiement fréquent, un workflow CI complet avec matrice + rapport de couverture pourrait être trop. Commencez par le workflow basique de la section 3, puis développez progressivement à mesure que le projet grandit.

9. Conclusion

Avec quelques dizaines de lignes YAML, vous avez un pipeline CI qui compile automatiquement, teste, met en cache les dépendances et publie les rapports pour votre projet Maven. Commencez par un workflow simple, puis ajoutez matrice JDK, couverture JaCoCo, badge README selon les besoins réels. L’aspect le plus important n’est pas un workflow complexe, mais un CI stable et fiable à chaque fois que vous poussez du code.

github-actionsjavamavenci-cdtests