La gestione della versione in un progetto Maven multimodulo è un tema fondamentale quando un’applicazione è composta da più artefatti: librerie, moduli core, moduli web, servizi, WAR, EAR, JAR o componenti condivisi.
In Maven, ogni progetto viene identificato principalmente da tre coordinate:
groupIdartifactIdversion
Queste tre informazioni identificano in modo univoco un artefatto Maven. Per esempio:
<groupId>it.javaboss</groupId>
<artifactId>module-a</artifactId>
<version>1.5.2</version>
Se non viene configurato diversamente il nome finale dell’artefatto prodotto nella fase di package segue normalmente una forma simile a:
${artifactId}-${version}
Quindi un modulo chiamato module-a con versione 1.5.2 produrrà un artefatto come:
module-a-1.5.2.jar
Il problema nasce quando un progetto Maven contiene molti moduli e vogliamo mantenere una versione unica e coerente per tutti gli artefatti prodotti.
In breve: in un progetto Maven multimodulo è consigliabile centralizzare la versione nel parent POM, evitare versioni duplicate nei moduli e usare strumenti moderni come
${revision},versions-maven-pluginoflatten-maven-pluginquando il progetto lo richiede.
Indice dei contenuti
- Coordinate Maven: groupId, artifactId e version
- Cos’è un progetto Maven multimodulo
- Il problema della versione nei moduli
- Parent POM, aggregator POM e moduli
- Soluzione classica: versione nel parent POM
- I moduli ereditano la versione dal parent?
- Soluzione moderna: Maven CI Friendly Versions con ${revision}
- revision, changelist e sha1
- Cambiare versione con versions-maven-plugin
- Quando usare flatten-maven-plugin
- Nome finale dell’artefatto
- SNAPSHOT e release
- Best practice
- Errori comuni
- FAQ sulla versione in Maven multimodulo
Coordinate Maven: groupId, artifactId e version
Ogni progetto Maven è identificato dalle sue coordinate. Le coordinate principali sono groupId, artifactId e version.
| Elemento | Significato | Esempio |
|---|---|---|
groupId |
Identifica l’organizzazione, il dominio o il gruppo del progetto. | it.javaboss |
artifactId |
Identifica il singolo artefatto o modulo. | module-a |
version |
Identifica la versione dell’artefatto. | 1.5.2 |
Queste coordinate sono usate da Maven per:
- costruire l’artefatto;
- installarlo nel repository locale;
- pubblicarlo in repository remoti;
- risolverlo come dipendenza da altri progetti;
- identificare correttamente una versione specifica del software.
Un artefatto Maven completo può essere rappresentato così:
it.javaboss:module-a:1.5.2
Cos’è un progetto Maven multimodulo
Un progetto Maven multimodulo è un progetto composto da più moduli Maven gestiti da un POM principale.
La struttura tipica è questa:
maven-version
├── pom.xml
├── module-a
│ └── pom.xml
└── module-b
└── pom.xml
Il POM principale ha solitamente packaging di tipo pom e dichiara i moduli all’interno della sezione <modules>.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
<packaging>pom</packaging>
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
</project>
Quando eseguiamo un comando Maven dalla root del progetto:
mvn clean package
Maven costruisce il reactor e compila i moduli nell’ordine corretto, tenendo conto delle dipendenze tra i moduli.
Il problema della versione nei moduli
Supponiamo di avere due moduli:
module-amodule-b
Se ogni modulo definisce la propria versione manualmente, potremmo avere una situazione come questa:
module-a → 1.5.2
module-b → 1.5.1
Il risultato del package potrebbe essere:
module-a-1.5.2.jar
module-b-1.5.1.jar
Questo può essere corretto in alcuni casi, ma se i moduli fanno parte della stessa applicazione e devono essere rilasciati insieme, versioni diverse possono creare confusione.
Perché è un problema?
- Diventa più difficile capire quale versione dell’applicazione è deployata.
- Aumenta il rischio di dimenticare un modulo durante il cambio versione.
- Il deploy può contenere artefatti con versioni incoerenti.
- Le pipeline CI/CD diventano più fragili.
- La tracciabilità delle release peggiora.
In molti progetti enterprise, è preferibile avere una sola versione per tutto il progetto multimodulo.
module-a-1.5.2.jar
module-b-1.5.2.jar
module-web-1.5.2.war
module-ear-1.5.2.ear
Obiettivo: cambiare la versione una sola volta e applicarla automaticamente a tutti i moduli del progetto.
Parent POM, aggregator POM e moduli
Prima di vedere la soluzione, è importante distinguere due concetti Maven spesso confusi: parent POM e aggregator POM.
Aggregator POM
Un aggregator POM è un POM che dichiara più moduli nella sezione <modules>.
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
Serve a costruire più moduli insieme.
Parent POM
Un parent POM è un POM da cui i moduli ereditano configurazioni comuni: proprietà, plugin, dependency management, repository, versioni e altre impostazioni.
Un modulo dichiara il parent così:
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
</parent>
Nella pratica, spesso il POM root è sia aggregator sia parent.
Root POM
↓
Aggregator: contiene <modules>
↓
Parent: viene dichiarato nei POM figli
Nota importante: aggregazione e inheritance sono due meccanismi diversi. Un POM può aggregare moduli senza essere il loro parent, anche se nei progetti reali spesso coincide.
Soluzione classica: versione nel parent POM
La soluzione più semplice è definire la versione nel parent POM e farla ereditare ai moduli.
Parent POM
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
<packaging>pom</packaging>
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
</project>
Modulo module-a
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
</parent>
<artifactId>module-a</artifactId>
</project>
Modulo module-b
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
</parent>
<artifactId>module-b</artifactId>
</project>
In questo caso i moduli non dichiarano un proprio tag <version> fuori dal parent. Di conseguenza, ereditano la versione del parent.
Risultato del package
module-a-1.5.2.jar
module-b-1.5.2.jar
Questa è la forma più leggibile e tradizionale per molti progetti Maven.
I moduli ereditano la versione dal parent?
Una precisazione importante: un modulo Maven può ereditare la versione dal parent se non dichiara una propria versione.
Questo significa che nel modulo figlio è generalmente meglio evitare questo:
<artifactId>module-a</artifactId>
<version>1.5.2</version>
e preferire questo:
<artifactId>module-a</artifactId>
In questo modo il modulo usa la versione ereditata dal parent.
Attenzione alla versione del parent
Il punto delicato è che il blocco <parent> nei moduli deve comunque indicare la versione del parent.
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>1.5.2</version>
</parent>
Questo può sembrare una duplicazione, perché la versione 1.5.2 compare in più POM. Proprio per risolvere meglio questo problema, Maven ha introdotto il supporto alle CI Friendly Versions.
Regola pratica: i moduli possono ereditare la versione del parent, ma devono comunque sapere quale parent usare. Per evitare duplicazioni moderne, si può usare
${revision}.
Soluzione moderna: Maven CI Friendly Versions con ${revision}
Da Maven 3.5.0-beta-1 è possibile usare alcune proprietà speciali per gestire la versione in modo più flessibile. La più importante è ${revision}.
Questa tecnica è nota come Maven CI Friendly Versions.
Parent POM con revision
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<properties>
<revision>1.5.2</revision>
</properties>
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
</project>
Modulo module-a con revision nel parent
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>${revision}</version>
</parent>
<artifactId>module-a</artifactId>
</project>
Modulo module-b con revision nel parent
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>it.javaboss</groupId>
<artifactId>maven-version</artifactId>
<version>${revision}</version>
</parent>
<artifactId>module-b</artifactId>
</project>
Ora la versione reale è centralizzata nella proprietà:
<revision>1.5.2</revision>
Per cambiare versione, basta modificare una sola proprietà nel POM principale.
Override da linea di comando
Uno dei vantaggi di ${revision} è che può essere valorizzato anche da linea di comando, per esempio in una pipeline CI/CD.
mvn clean package -Drevision=1.5.3
In questo modo puoi costruire una versione specifica senza modificare manualmente i POM.
Perché usare proprio revision?
È importante usare ${revision} perché Maven supporta in modo specifico alcune proprietà per le CI Friendly Versions. Usare una proprietà arbitraria come ${my.project.version} nella versione del parent può creare problemi di risoluzione, specialmente nei POM figli.
Consiglio moderno: per progetti multimodulo nuovi, usa
${revision}invece di proprietà personalizzate come${my.project.version}dentro<version>.
revision, changelist e sha1
Oltre a ${revision}, Maven supporta anche altre proprietà speciali per versioni CI-friendly:
${revision}${changelist}${sha1}
Un esempio comune è:
<version>${revision}${changelist}</version>
<properties>
<revision>1.5.2</revision>
<changelist>-SNAPSHOT</changelist>
</properties>
In questo modo la versione finale diventa:
1.5.2-SNAPSHOT
Build snapshot
mvn clean package
Produce:
module-a-1.5.2-SNAPSHOT.jar
module-b-1.5.2-SNAPSHOT.jar
Build release da linea di comando
mvn clean package -Drevision=1.5.2 -Dchangelist=
Produce:
module-a-1.5.2.jar
module-b-1.5.2.jar
Questo approccio è molto utile nelle pipeline CI/CD perché permette di separare versione base, suffisso snapshot e release.
Cambiare versione con versions-maven-plugin
Un altro modo molto diffuso per gestire le versioni in Maven è usare il versions-maven-plugin.
Questo plugin permette di aggiornare la versione del progetto e propagare il cambio ai moduli figli.
Comando base
mvn versions:set -DnewVersion=1.5.3
Questo comando aggiorna la versione corrente del progetto e dei moduli dove necessario.
Confermare le modifiche
mvn versions:commit
Annullare le modifiche
mvn versions:revert
Questo approccio è molto utile quando vuoi modificare materialmente i POM, per esempio durante una release tradizionale.
Quando usare versions-maven-plugin
- quando vuoi aggiornare fisicamente le versioni nei POM;
- quando lavori con processi di release tradizionali;
- quando non vuoi usare
${revision}; - quando devi uniformare versioni già presenti in molti moduli;
- quando vuoi automatizzare il cambio versione.
Nota pratica:
versions-maven-pluginè molto utile per progetti legacy dove le versioni sono già scritte esplicitamente in molti POM.
Quando usare flatten-maven-plugin
Quando usi ${revision} nei POM, potresti voler pubblicare artefatti con un POM “risolto”, cioè senza placeholder nella versione.
Qui entra in gioco il flatten-maven-plugin.
Il flatten plugin genera un POM semplificato e risolto per la pubblicazione, utile soprattutto quando il progetto usa proprietà CI-friendly come ${revision}, ${sha1} o ${changelist}.
Esempio di configurazione
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.3</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Quando è utile
- quando pubblichi artefatti in repository remoti;
- quando usi
${revision}nella versione; - quando vuoi evitare placeholder nei POM pubblicati;
- quando costruisci librerie consumate da altri progetti;
- quando vuoi POM più puliti per la distribuzione.
Regola pratica: se usi CI Friendly Versions e pubblichi artefatti, valuta seriamente
flatten-maven-plugin.
Nome finale dell’artefatto
Per impostazione predefinita, Maven usa artifactId e version per costruire il nome dell’artefatto finale.
module-a-1.5.2.jar
Questo comportamento è utile perché consente di capire rapidamente quale versione è stata generata o deployata.
Personalizzare finalName
È possibile personalizzare il nome finale nella sezione <build>.
<build>
<finalName>my-application</finalName>
</build>
In questo caso il file generato potrebbe non contenere la versione nel nome.
my-application.jar
Quando evitare finalName senza versione
Rimuovere la versione dal nome dell’artefatto può essere comodo in certi deploy automatici, ma può ridurre la tracciabilità.
Per esempio, se in un application server trovi:
app.war
non sai immediatamente quale versione è deployata. Se invece trovi:
app-1.5.2.war
la versione è visibile subito.
Best practice: mantieni la versione nel nome dell’artefatto quando la tracciabilità manuale del deploy è importante.
SNAPSHOT e release
In Maven è comune distinguere tra versioni snapshot e versioni release.
Versione SNAPSHOT
1.5.2-SNAPSHOT
Indica una versione in sviluppo, ancora non definitiva.
Versione release
1.5.2
Indica una versione stabile, pubblicata e normalmente immutabile.
Uso con revision e changelist
<version>${revision}${changelist}</version>
<properties>
<revision>1.5.2</revision>
<changelist>-SNAPSHOT</changelist>
</properties>
Per una build di sviluppo:
mvn clean package
Per una build release:
mvn clean package -Dchangelist=
Questo evita di modificare manualmente il POM per togliere o aggiungere -SNAPSHOT.
Strategie possibili per gestire la versione
Non esiste una sola strategia valida per tutti. La scelta dipende dal tipo di progetto, dalla pipeline e dal processo di release.
| Strategia | Quando usarla | Vantaggi | Limiti |
|---|---|---|---|
| Versione fissa nel parent | Progetti semplici o tradizionali | Facile da capire | Versione duplicata nei parent dei moduli |
${revision} |
Progetti moderni e CI/CD | Versione centralizzata e modificabile da pipeline | Può richiedere flatten plugin in pubblicazione |
versions-maven-plugin |
Release tradizionali | Aggiorna fisicamente i POM | Modifica molti file |
| Versioni diverse per modulo | Librerie indipendenti | Ogni modulo evolve autonomamente | Maggiore complessità gestionale |
Quale approccio scegliere?
Applicazione unica con più moduli
Se i moduli fanno parte della stessa applicazione e vengono rilasciati insieme, usa una versione unica.
app-parent:1.5.2
module-core:1.5.2
module-web:1.5.2
module-batch:1.5.2
In questo caso, ${revision} è spesso una buona scelta.
Librerie indipendenti
Se i moduli sono librerie indipendenti, rilasciate separatamente, può avere senso usare versioni diverse.
library-a:1.3.0
library-b:2.1.0
library-c:0.9.5
In questo caso la gestione diventa più simile a un monorepo con release indipendenti.
Progetto legacy
Se il progetto è vecchio e ha già versioni duplicate nei POM, può essere utile usare versions-maven-plugin per uniformarle.
mvn versions:set -DnewVersion=1.5.3
Progetto pubblicato su repository
Se pubblichi artefatti con ${revision}, valuta flatten-maven-plugin per generare POM risolti.
Best practice per la versione Maven multimodulo
- Usa una versione unica se tutti i moduli vengono rilasciati insieme.
- Evita di dichiarare
<version>nei moduli figli se devono ereditare dal parent. - Usa
${revision}nei progetti moderni con CI/CD. - Evita proprietà arbitrarie come
${my.project.version}dentro la versione del parent. - Usa
versions-maven-pluginper aggiornare versioni in progetti legacy. - Valuta
flatten-maven-pluginse pubblichi artefatti con CI Friendly Versions. - Mantieni la versione nel nome dell’artefatto quando serve tracciabilità.
- Distingui chiaramente versioni
-SNAPSHOTe release. - Non cambiare manualmente decine di POM senza automazione.
- Integra il cambio versione nella pipeline CI/CD.
- Documenta la strategia di versioning del progetto.
Errori comuni
Dichiarare versioni diverse senza volerlo
Uno degli errori più comuni è dimenticare di aggiornare la versione in uno dei moduli.
module-a → 1.5.2
module-b → 1.5.1
Se i moduli appartengono alla stessa release, questa incoerenza può creare problemi.
Usare una proprietà personalizzata nella versione del parent
Un esempio apparentemente comodo ma problematico è:
<version>${my.project.version}</version>
Questo può funzionare in alcuni contesti, ma non è la soluzione più sicura per la versione del parent nei moduli. È meglio usare ${revision}, che Maven supporta esplicitamente per le CI Friendly Versions.
Dichiarare la versione nel modulo figlio senza necessità
<artifactId>module-a</artifactId>
<version>1.5.2</version>
Se il modulo deve avere la stessa versione del parent, questa dichiarazione è ridondante.
Confondere parent e aggregator
Un POM può aggregare moduli senza essere il loro parent. Se vuoi ereditare configurazioni e versione, i moduli devono dichiarare il parent.
Pubblicare POM con placeholder non risolti
Se usi ${revision} e pubblichi artefatti, verifica che i POM pubblicati siano consumabili correttamente. In molti casi conviene usare flatten-maven-plugin.
Rimuovere la versione dal finalName
Personalizzare finalName può nascondere la versione dell’artefatto generato. Questo può rendere più difficile capire cosa è stato deployato.
Checklist per un progetto Maven multimodulo
| Controllo | Domanda da farsi |
|---|---|
| Versione unica | I moduli devono essere rilasciati tutti con la stessa versione? |
| Parent | I moduli dichiarano correttamente il parent POM? |
| Versione nei moduli | I moduli figli stanno duplicando inutilmente <version>? |
| CI/CD | La versione può essere passata da linea di comando? |
| revision | Il progetto potrebbe beneficiare di ${revision}? |
| Release | È chiara la differenza tra SNAPSHOT e release? |
| Pubblicazione | Serve flatten-maven-plugin? |
| Artefatti | Il nome finale contiene la versione? |
FAQ sulla versione in Maven multimodulo
Come gestire la versione in un progetto Maven multimodulo?
Il modo più comune è definire la versione nel parent POM e farla ereditare ai moduli. Nei progetti moderni è consigliabile valutare ${revision} per centralizzare la versione e integrarla con la pipeline CI/CD.
I moduli Maven ereditano la versione dal parent?
Sì, se il modulo non dichiara una propria versione, eredita la versione dal parent. Tuttavia, nel blocco <parent> del modulo deve essere indicata la versione del parent.
Posso evitare di scrivere la versione del parent in ogni modulo?
Non completamente, ma puoi usare ${revision} come placeholder supportato da Maven, centralizzando il valore reale nel POM principale o passandolo da linea di comando.
Meglio usare ${revision} o una proprietà personalizzata?
Per la versione del progetto è meglio usare ${revision}, perché è una proprietà supportata esplicitamente da Maven per le CI Friendly Versions. Proprietà arbitrarie possono creare problemi nella risoluzione del parent.
A cosa serve versions-maven-plugin?
Serve ad aggiornare le versioni nei POM Maven. È utile per automatizzare il cambio versione, soprattutto in progetti legacy o processi di release tradizionali.
A cosa serve flatten-maven-plugin?
Serve a generare un POM risolto e semplificato, utile quando si pubblicano artefatti che usano placeholder come ${revision}.
Devo usare la stessa versione per tutti i moduli?
Dipende. Se i moduli fanno parte della stessa applicazione e vengono rilasciati insieme, sì. Se sono librerie indipendenti che evolvono separatamente, possono avere versioni diverse.
Perché Maven genera file come module-a-1.5.2.jar?
Perché il nome finale dell’artefatto usa normalmente artifactId e version. Questo aiuta a identificare rapidamente la versione generata.
È una buona idea rimuovere la versione dal nome del file?
Dipende dal processo di deploy. Può essere comodo in alcuni ambienti, ma riduce la tracciabilità visiva dell’artefatto.
Come cambio versione da pipeline CI/CD?
Se usi ${revision}, puoi passare la versione da linea di comando:
mvn clean package -Drevision=1.5.3
Come gestire SNAPSHOT e release?
Puoi usare ${revision} insieme a ${changelist}:
<version>${revision}${changelist}</version>
e poi valorizzare changelist come -SNAPSHOT o lasciarlo vuoto per una release.
Conclusione
La gestione della versione in un progetto Maven multimodulo deve essere progettata con attenzione. In un progetto composto da molti moduli, cambiare manualmente la versione in ogni POM è scomodo, ripetitivo e soggetto a errori.
La soluzione tradizionale consiste nel definire la versione nel parent POM e farla ereditare ai moduli. Questa soluzione è semplice e funziona bene in molti scenari. Tuttavia, nei progetti moderni è spesso preferibile usare le Maven CI Friendly Versions con ${revision}, specialmente quando la versione viene gestita da una pipeline CI/CD.
Per progetti legacy, versions-maven-plugin permette di aggiornare le versioni in modo automatico. Per progetti che pubblicano artefatti usando placeholder come ${revision}, flatten-maven-plugin può aiutare a generare POM risolti e più adatti alla distribuzione.
La scelta migliore dipende dal progetto. Se tutti i moduli vengono rilasciati insieme, usa una versione unica. Se i moduli sono indipendenti, valuta versioni separate. In ogni caso, evita duplicazioni inutili, automatizza il cambio versione e mantieni chiara la relazione tra artefatti generati e release deployate.
Riassunto finale: per un progetto Maven multimodulo moderno, una buona strategia è usare un parent POM, evitare versioni duplicate nei moduli, centralizzare la versione con
${revision}e valutareversions-maven-pluginoflatten-maven-pluginin base al processo di release.