How to update the version of child modules in Maven?

19,380

Solution 1

Question 1)

The best way to manage application lifecycle and releases is to use the release plugin.

As you may know, Maven philosophy is convention over configuration. Maven convention is to use snapshot versions (those ending with -SNAPSHOT) during development and assigning a non snapshot version only for releases.

Say you're developing version 1.1.1. While under development, you just use 1.1.1-SNAPSHOT. Maven will take care of updates of the snapshot. If using an artifact repository, you can use -U to make sure you always have the last version of the snapshots.

When the release is ready, release plugin generates and deploys version 1.1.1 and updates POM's with the new development version, such as 1.1.2-SNAPSHOT.

About multimodule projects, there are two scenarios: modules are related but independent (such as several web applications) or they are modules of a single big application or library and they share versions. You seem interested in the latter.

The best way in this case is just to inherit the same parent (maybe also root) module, including its version. You reference parent group:artifact:version and you do not specify a version for the children. Generally you also inherit group, so your child pom can look like:

<parent>
   <groupId>com.mycompany.myproject</groupId>
   <artifactId>myproject-parent</artifactId>
   <version>1.1.1-SNAPSHOT</version>
   <relativePath>../myproject-parent</relativePath>
</parent>
<artifactId>myproject-module1</artifactId>

Now you just need to take care of children pointing to the right version of the parent with the help of the release plugin.

To help it know about children, you should make your parent pom also a root pom by including the modules section as shown later.

Question 2) I usually declare properties in the parent with all versions of all artifacts which may be referenced. If several modules share version, you just need one property. The parent can look like:

<groupId>com.mycompany.myproject</groupId>
<artifactId>myproject-parent</artifactId>
<version>1.1.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
   <myproject.version>1.1.1-SNAPSHOT</myproject.version>
</properties>

.......

<modules>
   <module>../myproject-module1</module>
   ...
</modules>

Children can reference other modules using

<version>${myproject.version}</version>

It is a very bad practise to declare dependencies using LATEST. Say you do this for version 1.1.1. Now you're working with version 1.1.2-SNAPSHOT and probably you have artifacts with this version installed in your local repo.

Now say for some reason you need to rebuild version 1.1.1, for instance because of a bug in production. Your build will use the new version. If you're lucky, this will break the build. If you're unlucky it may even go unnoticed to production.

Last but not least, some people like using property values to declare children versions. This is strongly discouraged and will be reported as a warning by maven. I personally don't ever do it. The reasons are also related to reproducibility of builds and the fact that maven assumes that a release build will never change. Having a module version be externally tweakable is not really a good idea.

EDIT:

Case when module versions are not aligned.

Actually both scenarios can be mixed. You can have, for instance:

Parent

---Component1

---Component2

---Component3

------Comp3Module1

------Como3Module2

------Comp3Module3

Where parent and the three component versions are different and the three modules of component3 share its same version as explained before.

Question 1) In this case each module has its version independently specified. As said before, it's a bad practise to use a property to specify module version, reason why I can only recommend to literally specify versions. As already said, to manage versioning, the best way is to use release plugin, and integrate it with the version control system, such as SVN. Other answers give details on how to use it, so I won't elaborate it further, unless requested.

Question 2) The recommended approach is the same explained for the case of sharing the same version, only that you need several properties. The parent can look like:

<properties>
   <myproject.group>com.mycompany.myproject</myproject.group>
   <component1.version>1.1.1-RC1</component1.version>
   <component2.version>1.1.1-RC2</component2.version>
   <component3.version>2.0.0</component3.version>
<properties>

Then you can use dependency management to centralise version management in the parent.

For instance, in parent pom,

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>component1</artifactId>
         <version>${component1.version}</version>
      </dependency>
      <dependency>
        <groupId>${myproject.group}</groupId>
         <artifactId>component2</artifactId>
         <version>${component2.version}</version>
         <type>war</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module1</artifactId>
         <version>${component3.version}</version>
        <type>ejb</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module1</artifactId>
         <version>${component3.version}</version>
        <type>ejb-client</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module2</artifactId>
         <version>${component3.version}</version>
        <type>war</version>
      </dependency>
   </dependencies>
</dependencyManagement>

Now, to reference any module from any other module, it's as easy as:

<dependency>
   <groupId>${myproject.group}</groupId>
   <artifactId>component1</artifactId>
</dependency>
<dependency>
   <groupId>${myproject.group}</groupId>
   <artifactId>comp3module1</artifactId>
   <type>ejb-client</type>
</dependency>

Versions are automatically managed from the parent. You don't need to maintain them in children dependencies, which become also less verbose.

Solution 2

1) I've also tried the version:set in the past, but never got it working right. It's supposed to be doing the same process as release:prepare, but it actually doesn't. So what you could try is mvn release:prepare -DautoVersionSubmodules -DdryRun. That is supposed to make all the updates without checking anything into the repo and without making any tags.

2) I believe the ClearTK project once followed a similar strategy as you do: they maintained a multi-module project with each module having its own release cycle. To stay on top of the situation, they implemented a custom maven plugin to warn them about dependency version inconsistencies.

https://github.com/ClearTK/cleartk/tree/master/consistent-versions-plugin

While such a plugin would not make the updates that you request, it should at least notify you when updates are necessary. To really fix your problem, you might consider following a the same route as ClearTK did and implement your own Maven plugin (or you do what ClearTK eventually ended up doing: switching to a synced release cycle ;) )

Solution 3

Ok this is what I came up with. This is based on this continuous-releasing-of-maven-artifacts article.

Parent POM:

<properties>
    <!-- versions of modules -->
    <main.version>1.0</main.version>
    <revision>SNAPSHOT</revision> <!-- default revision -->
    <Module1.revision>${revision}</Module1.revision>
    <Module2.revision>${revision}</Module2.revision>
    <Module3.revision>${revision}</Module3.revision>
    <Module4.revision>${revision}</Module4.revision>
    <Module5.revision>${revision}</Module5.revision>
    <Module1.version>${main.version}-${Module1.revision}</Module1.version>
    <Module2.version>${main.version}-${Module2.revision}</Module2.version>
    <Module3.version>${main.version}-${Module3.revision}</Module3.version>
    <Module4.version>${main.version}-${Module4.revision}</Module4.version>
    <Module5.version>${main.version}-${Module5.revision}</Module5.version>
</properties>

Sample child POM with inter project dependency:

    <groupId>com.xyz</groupId>
    <artifactId>Module4</artifactId>
    <packaging>jar</packaging>
    <version>${Module4.version}</version>

    <parent>
        <groupId>com.xyz</groupId>
        <artifactId>ParentProject</artifactId>
        <version>1.0</version>
    </parent>   

    <dependencies>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module1</artifactId>
            <version>${Module1.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module2</artifactId>
            <version>${Module2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module3</artifactId>
            <version>${Module3.version}</version>
            <type>jar</type>
        </dependency>
    <dependencies>

1) At the beginning of the cycle how can I set the parent and modules to the same version in one maven command?

You no longer need to. The parent POM can remain at the same version, only changing if the parent POM changes. In which case you can use mvn version:set -DnewVersion=1.1.1. But you don't need to for this approach.

Instead you can dynamically set the version using the property main.version. E.g. mvn clean deploy -Dmain.version=1.1.1 Also, to force dynamic passing of the version number, you can leave out the default main.version property that I included in my parent POM above.

2) How do I update both mod1's version and mod2's reference to mod1's version in one step?

This basically boils down to how to manage revisions. If I do not set the revision property in the mvn command, then all the modules will use SNAPSHOT as the revision. If I set the revision property to RC1 then all the modules will get that revision. Furthermore, if I set revision to RC1 but Module4.revision to RC2 then Module4 gets RC2 and all the other modules get RC1. This fulfills the clients request to have dynamic revisions on a per module basis.

Here are some examples:

  • mvn clean deploy -Dmain.version=1.1.1 Sets all the modules to version 1.1.1-SNAPSHOT.
  • mvn clean deploy -Dmain.version=1.1.1 -Drevision=RC1 Sets all the modules to version 1.1.1-RC1.
  • mvn clean deploy -Dmain.version=1.1.1 -Drevision=RC1 -DModule4.revision=RC2 Sets all the modules to version 1.1.1-RC1 except for Module4 which is set to version 1.1.1-RC2.

There is a caveat that must be mentioned. If you increment the version of a dependent module, for example Module1, to RC2, then you must also increment the version of all the modules that use it, for example Module4 must not be incremented to RC2 (or the next version) also. This is something that the client has been made aware of and it is also why I would prefer all the modules to have the same version. But I really do like how dynamic it came out. Essentially the version is now set via command line with no updates to POM files required.

Solution 4

1) As @rec said, the maven release plugin would do the trick

$ mvn release:prepare -DautoVersionSubmodules=true
$ mvn release:perform

2) You can define dependency from mod2 to mod1 like:

<dependency>
    <groupId>com.xxx</groupId>
    <artifactId>mod1</artifactId>
    <version>LATEST</version>
</dependency>

More info: How do I tell Maven to use the latest version of a dependency?

I tested both solutions and it works! Hope it helps you :)

Share:
19,380
Jose Martinez
Author by

Jose Martinez

Java, REACT developer.

Updated on June 06, 2022

Comments

  • Jose Martinez
    Jose Martinez almost 2 years

    How to update the version of child modules? There are a lot of Stackoverflow questions like this but I was not able to find one that fit this exact scenario... would love if this is a duplicate.

    Consider the following project.

    parent
      --mod1
      --mod2
    

    At the beginning of a dev release cycle I need to update the parent and modules to the same version. If the version of the parent and modules stayed the same throughout the release then I would would just omit the <version> tag from the modules and execute versions:set -DnewVersion=1.1.1 to kick off the dev cycle. But as it turns out the modules do not all end the cycle with the same version. As bugs and fixes materialize only those modules with the bugs and such get updated. For example the parent and mod2 might be at version 1.1.1-RC1, but mod1 might be at 1.1.1-RC2.

    As such I need to:
    1) Include a <version> tag in the modules to track each modules version independently.

    2) If mod2 requires mod1 as a dependency I need to make sure mod2 references the latest version of mod1.

    This leads to the following two questions.

    1) At the beginning of the cycle how can I set the parent and modules to the same version in one maven command? I tried version:set -DnewVersion=1.1.1, but this only updates the parent's version across all the POM's but not the module's version. I also tried -N versions:update-child-modules, but I think I am using it wrong because it does nothing, just shows skipped for all the modules.

    2) This is a bit harder and matches to item 2 above. How do I update both mod1's version and mod2's reference to mod1's version in one step? I know how to do it in 2 steps:

    parent pom:

    <properties>
        <!-- update this manually if mod1's version no longer matches parent -->
        <mod1.version>${project.version}</mod1.version>
    </properties>
    

    mod2 pom:

        <dependency>
            <groupId>com.xxx</groupId>
            <artifactId>mod1</artifactId>
            <version>${mod1.version}</version>
        </dependency>
    

    When mod1 bumps up to 1.1.1-RC2 I update the parent POM and mod1 POM to reflect this. These are two steps. Anyway to turn it into one step?

    My example was small but in real life there are many modules which makes this important time saver, plus I'm curious.

  • Jose Martinez
    Jose Martinez over 7 years
    Juan thanks for the detailed response. Question 1 refers to each module having their own version. In your answer all the modules have the same version. Please note that we use -SNAPSHOT but at the end of a dev cycle when we go into UAT we use the non-SNAPSHOT versions. Same with question 2, it assumes each module will have the same version. The variance of versions within the child modules is at the root of the question.
  • Juan
    Juan over 7 years
    Ok, I had understood that at the end of the cycle you realigned all versions to start new cycle with same version for all modules. I'll clarify the differences in this case.
  • Juan
    Juan over 7 years
    Added info about independent versions. Hope it helps.
  • Jose Martinez
    Jose Martinez over 7 years
    Have you seen my answer?
  • Juan
    Juan over 7 years
    I didn't notice you answered your own question :) There are some differences in my proposal. Everything is managed from the parent pom, but you should change it, increment its version and let children point to the right parent version. You can make it easier through release plugin, but I don't recommend setting versions by changing properties when building. With dependency management, you don't need to touch anything else in children.
  • Jose Martinez
    Jose Martinez over 7 years
    What you have in DependencyManagement accomplishes the same as what I have for properties, as far as centralization is concerned, which is why I pointed it out. Regardless of whether I put the child's version in properties or DependencyManagement, it is centrally controlled from the parent POM.
  • Juan
    Juan over 7 years
    Yes, there are usually several ways to achieve the same results. I just share what can be achieved through best practices and from my own experience with maven. Neverthless, I don't quite get yes why you would like to maintain different versions across modules if you are making them equal for the next cycle. Say you just change one module and you only change its version to RC2. Only to update references from other modules, you nee to also update their version, as they are not the same if they depend on different module versions. Why not just update all them to RC2 even without changes?
  • Juan
    Juan over 7 years
    I believe keeping versions aligned all the time would ease your life quite a lot.
  • Jose Martinez
    Jose Martinez over 7 years
    I agree. For future projects for that client I was able to convince them of that. But for that older legacy project my hands were tied.
  • Juan
    Juan over 7 years
    Oh yes, I know this situation. Hope you can take profit of any of the things I've shared :)
  • Ed Randall
    Ed Randall about 3 years
    An issue with versions:set is that, if you have lots of submodules with different combinations selected by different profiles - you need to use a profile that selects all submodules in order to ensure that they are all updated. Having such a profile activeByDefault, this command then works fine: mvn -DnewVersion=1.2.3-SNAPSHOT versions:set versions:commit