Break New Ground

Mastering Maven: Build Properties

Welcome to the next installment of the Mastering Maven series. Today we'll explore the build properties feature and how we can use it to our advantage. Recall from part 4 (Dependency Basics) where a POM was shown with a set of dependencies; 2 of them shared the same groupId and version because they belong to the same library, in this case, Slf4j.

It's clear that both dependencies must be kept in sync, which means that the POM file must be updated in two places when a new version upgrade is warranted. That's not much of a problem for a trivial POM file like this one, but it may be so when a more complex POM is involved. One option that we have to solve this problem is to refactor the POM such that the Slf4j version is defined at a single location, for this, we'll make use of build properties.

Properties can be defined using a <properties> block in a POM file. This block lets you declare key/value pairs, the key being the name of the property and the value, well it's self-explanatory. Key names are free text although for obvious reasons the use of '>' and '<' is discouraged. Once a property is declared you may use it anywhere a value may be applied; and the way you apply the property is by surrounding it ${}. Thus if the name of the property happens to be slf4j.version then the places where its value is required can be replaced for ${slf4j.version}. Let's rewrite the previously shown POM with this knowledge in tow.

Now, when invoking mvn dependency:tree we get the following output

It appears to be working as expected, both dependencies have the same version. Now let's see what happens if we update the value of slf4j.version to a different number, say 1.7.29, and run the same Maven command once again

The versions of both dependencies continue to match! Now, there's no rule or convention that stipulates all dependency versions should be declared as properties but as projects grow it might be a good idea to keep all dependencies in one place, as you've seen, dependency definitions take at least 5 lines of XML, browsing a big POM to find the appropriate version to update may be difficult and error-prone, better put all versions under one roof, the <properties> block. Be aware that some developer teams prefer to keep version numbers exactly at the location where they are needed, which is in the dependency declaration block.

Now here is a neat trick that we can apply given that we have refactored the POM to use a property for the Slf4j version: any property value can be overridden from the command line using the -D flag. This is what happens if we invoke the previous command once more and append -Dslf4j.version=1.7.25

Very well, here's another example of how properties can be used, some plugins expose their configuration via properties, such that you only have to provide an explicit value for the target property thus avoiding configuring the plugin itself using a <plugin> element. What do I mean by this? Let's say we have Java 8 configured as the default JDK in the system then we compile the current POM shown in this blog

We can see on the first command that Java 8 (8u232 to be exact) is the selected Java version. The second command instructs Maven to compile a class named com.acme.Sample. The third command decompiles the class file, showing the major Java version used in said class file. However, something strange happened as the major version is 49 (that maps to Java 5) but we were expecting it to be 52 (which maps to Java 8). What's going on here? Recall from Mastering Maven: Adding Plugins that some plugins are available with pre-configured settings; the maven-compiler-plugin is set to target Java 5 by default. Hmm, this means we need to change this default setting and instruct the compiler plugin that we want to target Java 8 bytecode; one way to make it happen is by explicitly configuring the maven compiler plugin like it's shown next

Running the previous sets of commands once again yields the expected result, now the compiled class file targets Java 8 (major version 52).

However, if you browse the plugin's documentation site and look for the properties that can be used to configure the compile goal you'll notice there are two properties that control the source (maven.compiler.source) and target (maven.compiler.target) values. Let's modify the POM one more time by removing the explicit configuration of the maven-compiler-plugin and adding these two properties to the <properties> block

One more time, we run the previous commands to verify that the compiled class file has the expected major Java version (52).

Great! We're done here, or are we? Remember that any exposed property can be overridden using the -D flag; we verified this fact with a self-defined property such as slf4j.version but will this work for the compiler properties as well? Let's give it a try by switching Java versions to the latest major release available today: Java 14 (released on March 17th, 2020) and specifying source/target to be 14

The first command does output that the selected Java version is 14 (14.0.0). The second command takes 2 additional arguments as inputs (-Dmaven.compiler.source=14 and -Dmaven.compiler.target=14). The third and final command shows the major version found in the compiled class file, it's version 58 which matches Java 14. Yay, it works!

We've reached the fifth stop of the Mastering Maven series, concluding with the basics. You may have noticed this is the second time I mentioned pre-configured plugins and you may be wondering from where these settings may be coming, I'm also sure you've had experience dealing with more than one project in a single build, or having to investigate dependency resolution troubles. These and other concerns will be discussed in the next block of this series. Stay tuned!

Photo by Susanne Jutzeler.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.