Construction & packaging Java¶
La chaîne de build Java repose historiquement sur Maven, avec Gradle comme alternative performante. Au-delà du JAR classique, GraalVM native-image permet de compiler en binaire natif pour les microservices a démarrage ultra-rapide. Cette section couvre les outils de build, le packaging, la containerisation et un exemple de pipeline CI/CD.
Maven vs Gradle¶
| Critère | Maven | Gradle |
|---|---|---|
| Syntaxe | XML (verbeux mais predictible) | Kotlin DSL ou Groovy DSL (concis) |
| Performance | Build incremental limite | Build incremental, cache de build avance |
| Conventions | Très strictes (convention over conf.) | Flexibles mais necessitent discipline |
| Écosystème plugins | Très large, mature | Large, croissant |
| Multi-modules | Hérité, fonctionne bien | Natif, plus performant |
| Android | Non | Oui (outil officiel Android) |
| Adoption entreprise | Très haute | Haute, en progression |
Quel outil choisir
Pour un nouveau projet Spring Boot enterprise, Maven reste le choix le plus safe (meilleure documentation, plugins Spring matures). Gradle est preferable pour les projets multi-modules complexes ou les contraintes de performance de build (monorepos, projets Android/JVM mixtes).
Structure Maven (pom.xml)¶
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Heritage Spring Boot — gere les versions des dependances -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
<testcontainers.version>1.20.0</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Plugin Spring Boot — package en fat JAR executable -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Commandes Maven essentielles¶
# Compiler le projet
mvn compile
# Executer les tests
mvn test
# Package en JAR (compile + test + package)
mvn package
# Package sans tests
mvn package -DskipTests
# Installer dans le repository local (~/.m2)
mvn install
# Nettoyer les artefacts de build
mvn clean
# Pipeline complet
mvn clean verify
# Afficher l'arbre de dependances
mvn dependency:tree
Structure Gradle (build.gradle.kts)¶
// build.gradle.kts — Kotlin DSL (recommande pour les nouveaux projets)
plugins {
java
id("org.springframework.boot") version "3.4.0"
id("io.spring.dependency-management") version "1.1.6"
}
group = "com.example"
version = "1.0.0-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<Test> {
useJUnitPlatform()
}
# Commandes Gradle equivalentes
./gradlew build # compile + test + package
./gradlew test # tests uniquement
./gradlew bootRun # demarrer l'application
./gradlew dependencies # arbre de dependances
JAR executable et WAR¶
Fat JAR (Uber JAR)¶
Spring Boot produit par défaut un fat JAR (ou uber JAR) qui contient l'application et toutes ses dépendances, executable directement.
mvn package
java -jar target/demo-1.0.0-SNAPSHOT.jar
# Avec profil de configuration
java -jar target/demo-1.0.0-SNAPSHOT.jar --spring.profiles.active=prod
# Avec variables d'environnement
SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/mydb \
java -jar target/demo-1.0.0-SNAPSHOT.jar
WAR pour serveur d'applications¶
<!-- pom.xml — changer le packaging en WAR -->
<packaging>war</packaging>
<dependencies>
<!-- Exclure Tomcat embarque (fourni par le serveur externe) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
GraalVM native-image¶
GraalVM permet de compiler une application Java en binaire natif : pas de JVM nécessaire à l'exécution, démarrage en quelques millisecondes, empreinte mémoire réduite.
Configuration (pom.xml)¶
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<!-- Inclus par le parent Spring Boot 3 -->
</plugin>
# Compilation native (necessite GraalVM JDK installe)
mvn -Pnative native:compile
# Executer le binaire natif
./target/demo
# Sans GraalVM local : build dans un conteneur Docker
mvn -Pnative spring-boot:build-image
Réflexion et configuration native¶
GraalVM native-image analyse statiquement le code et ne peut pas gérer la réflexion dynamique par défaut. Spring Boot 3 configure automatiquement la réflexion pour ses composants, mais des annotations sont parfois nécessaires.
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
// Enregistre la classe pour la reflexion en mode natif
@RegisterReflectionForBinding(CommandeCreeeEvent.class)
@Configuration
public class NativeConfig {
// Configuration specifique native si necessaire
}
Limitations native-image
Certaines fonctionnalités Java dynamiques (réflexion, proxies dynamiques, chargement de classes à chaud) necessitent une configuration explicite pour GraalVM. Spring Boot 3 géré la majorité des cas, mais des bibliotheques tierces peu connues peuvent poser problème. Testez toujours le binaire natif en CI.
Dockerfile multi-stage¶
Un Dockerfile multi-stage séparé la phase de build (JDK complet) de la phase d'exécution (JRE léger), reduisant la taille de l'image finale.
# ---- Stage 1 : Build avec Maven et JDK complet ----
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
# Copier les fichiers de dependances en premier (cache Docker)
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -B
# Copier les sources et compiler
COPY src src
RUN ./mvnw package -DskipTests -B
# ---- Stage 2 : Runtime avec JRE leger ----
FROM eclipse-temurin:21-jre-alpine AS runtime
# Utilisateur non-root pour la securite
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
# Copier uniquement le JAR depuis le stage builder
COPY --from=builder /app/target/*.jar app.jar
# Port expose par Spring Boot
EXPOSE 8080
# Point d'entree — options JVM recommandees
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
# Build et run de l'image
docker build -t demo-app:1.0 .
docker run -p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/demo \
demo-app:1.0
Jlink — JRE personnalise¶
Jlink (depuis Java 9) permet de créer un JRE minimal contenant uniquement les modules nécessaires à l'application.
# Identifier les modules requis par l'application
jdeps --multi-release 21 --ignore-missing-deps \
--print-module-deps target/demo.jar
# Creer un JRE minimal avec uniquement les modules necessaires
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.logging,java.net.http,java.sql \
--output jre-custom \
--strip-debug \
--compress=2 \
--no-header-files \
--no-man-pages
# Taille comparee
du -sh $JAVA_HOME # ~300 MB — JDK complet
du -sh jre-custom # ~50-80 MB — JRE minimal
Exemple de pipeline CI/CD (GitHub Actions)¶
# .github/workflows/ci.yml
name: CI/CD Java
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
services:
# PostgreSQL pour les tests d'integration Testcontainers
# (ou laisser Testcontainers gerer Docker directement)
postgres:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven # Cache du repository Maven (~/.m2)
- name: Build et tests
run: mvn clean verify -B
- name: Rapport de couverture JaCoCo
uses: codecov/codecov-action@v4
with:
files: target/site/jacoco/jacoco.xml
- name: Build image Docker
if: github.ref == 'refs/heads/main'
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}