Aller au contenu
CI/CD & Automatisation medium

Cache Java/Maven/Gradle dans GitHub Actions

14 min de lecture

Les projets Java ont souvent des dépendances volumineuses. Un build Maven ou Gradle sans cache peut télécharger des centaines de Mo de JARs. Le cache réduit ce temps de plusieurs minutes à quelques secondes.

  • Activer le cache intégré de setup-java pour Maven et Gradle
  • Configurer actions/cache pour un contrôle fin des dépôts locaux
  • Utiliser gradle/actions/setup-gradle, l'action officielle Gradle
  • Construire un workflow complet Maven ou Gradle, matriciel
  • Gérer les multi-modules et déboguer un cache corrompu

L'action setup-java sait gérer le cache toute seule : l'option cache: déclenche la sauvegarde du dépôt local de dépendances en fin de job et sa restauration au job suivant. La clé de cache est calculée à partir des fichiers de description du projet, vous n'avez donc ni chemin ni empreinte à écrire. C'est la méthode à privilégier tant que vous n'avez pas de besoin particulier, car elle suit les évolutions de l'outil sans intervention de votre part.

- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven' # ou 'gradle'
- run: mvn verify

Trois valeurs sont acceptées, une par gestionnaire de build. Le fichier indiqué entre parenthèses sert au calcul de la clé : sa modification invalide le cache et provoque un nouveau téléchargement complet des dépendances.

ValeurGestionnaire
mavenMaven (détecte pom.xml)
gradleGradle (détecte build.gradle ou build.gradle.kts)
sbtSBT (détecte build.sbt)

Passer par actions/cache devient utile quand vous devez maîtriser exactement les chemins sauvegardés, ajouter un répertoire que setup-java ignore, ou partager un cache entre plusieurs workflows. Deux paramètres portent tout le comportement : key, l'empreinte exacte recherchée, et restore-keys, la liste des préfixes de repli utilisés quand cette empreinte n'existe pas encore. Sans restore-keys, la moindre modification d'un pom.xml fait repartir de zéro.

- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
- name: Cache Maven packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2/repository
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-${{ runner.os }}-
- run: mvn verify

Le wrapper Maven télécharge sa propre distribution de Maven dans ~/.m2/wrapper, en plus des dépendances du projet. Ajoutez ce répertoire au cache et maven-wrapper.properties au calcul de la clé, sinon chaque exécution récupère à nouveau l'archive de Maven.

- name: Cache Maven
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.m2/repository
~/.m2/wrapper
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml', '**/maven-wrapper.properties') }}
restore-keys: |
maven-${{ runner.os }}-

Si vous n'utilisez pas l'action officielle Gradle, cachez à la main les dépendances et le wrapper Gradle. Les deux répertoires sont distincts : ~/.gradle/caches contient les artefacts téléchargés, ~/.gradle/wrapper la distribution de Gradle elle-même, plusieurs centaines de mégaoctets à chaque version. Le motif **/*.gradle* couvre à la fois la syntaxe Groovy et la syntaxe Kotlin des fichiers de build.

- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- run: ./gradlew build

Cacher les répertoires .gradle et build conserve aussi les résultats de compilation, ce qui accélère les exécutions successives sur une même branche. La clé inclut ici github.sha, donc une entrée par commit : c'est voulu, un cache d'artefacts compilés ne doit pas être partagé entre deux états du code. Gardez en tête qu'une entrée de cache est immuable, une clé déjà écrite ne sera jamais mise à jour, et que le quota de 10 Go par dépôt se remplit d'autant plus vite que les clés sont volatiles.

- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
.gradle
build
key: gradle-build-${{ runner.os }}-${{ github.sha }}
restore-keys: |
gradle-build-${{ runner.os }}-
gradle-${{ runner.os }}-

Maintenue par l'équipe Gradle, gradle/actions/setup-gradle remplace l'écriture manuelle des blocs de cache. Elle sauvegarde les répertoires utiles, restaure l'état entre les exécutions et ajoute un compte rendu de build dans le résumé du job. Placez-la après setup-java, car elle a besoin d'un JDK déjà installé pour lancer Gradle.

- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- run: ./gradlew build

Cette action :

  • Cache automatiquement les dépendances Gradle
  • Génère un rapport de build
  • Supporte le build scan

Voici l'assemblage complet, prêt à être copié dans .github/workflows/. Il combine le cache intégré, une matrice qui rejoue le build sur deux versions de JDK, et la publication des rapports de tests même en cas d'échec grâce à if: always(). La matrice lance les deux jobs en parallèle, ce qui allonge peu la durée totale mais double la consommation de minutes.

name: Java CI with Maven
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions: {}
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
matrix:
java: [17, 21]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
cache: 'maven'
- name: Build and test
run: mvn -B verify
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results-java${{ matrix.java }}
path: target/surefire-reports/

L'équivalent Gradle repose sur l'action officielle plutôt que sur un bloc de cache écrit à la main. Les étapes de build et de test sont séparées pour obtenir deux durées distinctes dans l'interface, ce qui facilite le diagnostic quand un job ralentit. Comme pour Maven, le workflow part de permissions: {} et n'accorde que contents: read au job, le strict nécessaire pour cloner le dépôt.

name: Java CI with Gradle
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions: {}
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Build
run: ./gradlew build
- name: Test
run: ./gradlew test
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results
path: build/reports/tests/

Au-delà du cache, quelques options accélèrent encore les builds Java, incrémental, parallélisme, tests conditionnels.

L'option --build-cache autorise Gradle à réutiliser le résultat des tâches dont les entrées n'ont pas changé, au lieu de les réexécuter. Le gain n'apparaît qu'à partir de la deuxième exécution, une fois le cache de build restauré par l'action.

- name: Build with incremental
run: ./gradlew build --build-cache

Les runners hébergés par GitHub disposent de plusieurs cœurs, que les deux outils n'exploitent pas par défaut. Vérifiez d'abord que vos tests supportent l'exécution simultanée : des tests qui partagent un fichier temporaire ou un port fixe deviennent instables dès qu'ils tournent en parallèle.

# Maven
- run: mvn -B verify -T 4 # 4 threads
# Gradle
- run: ./gradlew test --parallel

Cette expression ajoute -DskipTests en dehors des push, pour réserver la suite complète aux branches intégrées. Utilisez-la avec parcimonie : un artefact construit sans tests ne doit jamais être publié, sous peine de perdre la garantie apportée par le pipeline.

- name: Build
run: mvn -B package ${{ github.event_name == 'push' && '' || '-DskipTests' }}

Sur un projet multi-modules, ne reconstruisez que les modules réellement modifiés : le cache des dépendances reste partagé, le build cible le strict nécessaire.

- name: Cache Maven modules
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2/repository
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-${{ runner.os }}-
# Build uniquement les modules modifiés
- name: Build changed modules
run: |
MODULES=$(git diff --name-only HEAD~1 | grep pom.xml | xargs dirname | tr '\n' ',')
if [ -n "$MODULES" ]; then
mvn -B -pl "$MODULES" -am verify
fi

Trois problèmes typiques touchent les builds Java en CI. Voici leur symptôme et la correction.

Un artefact partiellement téléchargé reste dans le dépôt local et casse toutes les exécutions suivantes, puisque le cache est restauré tel quel. Comme une entrée de cache ne peut pas être écrasée, la seule issue consiste à la supprimer avec la commande gh cache delete, qui accepte un motif.

Fenêtre de terminal
# Symptôme : erreurs de checksum
Could not resolve dependencies for project
# Solution : supprimer le cache
gh cache delete maven-linux-* --repo owner/repo

Le daemon est un processus Gradle qui survit entre deux invocations pour garder la JVM chaude. Sur un runner détruit à la fin du job, il ne sert à rien et provoque parfois des blocages en fin d'exécution ; --no-daemon supprime le problème.

# Désactiver le daemon dans CI
- run: ./gradlew build --no-daemon

Publier sur un dépôt d'artefacts privé demande un settings.xml contenant des identifiants, que setup-java génère pour vous à partir des options server-*. Le point important est que ces options attendent des noms de variables d'environnement, pas les valeurs elles-mêmes : le secret reste dans le bloc env: et n'apparaît jamais dans le fichier versionné.

- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
server-id: github
server-username: MAVEN_USERNAME
server-password: MAVEN_TOKEN
- run: mvn deploy
env:
MAVEN_USERNAME: ${{ github.actor }}
MAVEN_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Le cache intégré de setup-java (cache: 'maven' / 'gradle' / 'sbt') est la méthode recommandée.
  • actions/cache manuel donne le contrôle des chemins (~/.m2/repository, ~/.gradle/caches) quand le cache intégré ne suffit pas.
  • Pour Gradle, gradle/actions/setup-gradle gère le cache automatiquement et ajoute un rapport de build.
  • Désactivez le daemon Gradle en CI (--no-daemon) : il n'apporte rien sur un runner éphémère.
  • Un cache corrompu se purge avec gh cache delete, pensez-y face à des erreurs de checksum.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn