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’unpushou d’unepull_requestciblant la branchemain.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’appelermvn.mvn -B clean verify: le drapeau-B(batch mode) désactive la sortie interactive, approprié pour un environnement CI.clean verifyreconstruit à partir de zéro et exécute les tests (la phaseverifyincluttest).
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 selonmvnwau 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 :

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-versionpour qu’il corresponde àmaven.compiler.source: le build local réussit mais CI échoue avec l’erreurrelease version X not supported. Synchronisez toujours la version JDK entrepom.xmlet le workflow. - Utiliser
mvn testau lieu demvn verify: si votre projet a des tests d’intégration s’exécutant à la phaseverify(par exemple plugin Failsafe),mvn testles 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’inclurepom.xmldans la clé hash, l’ancien cache peut faire en sorte que le build utilise des dépendances obsolètes. Utilisezcache: 'maven'desetup-javapour 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.