57

I have a Java project that I build using an Ant script. I am trying to convert the project to Maven.

One of the tasks generates a Java source file called Version.java that contains a static String representation of the compilation timestamp, as follows:

package com.foo.bar;
public final class Version {
 public static String VERSION="100301.1046";
}

The Ant task is very simple:

<target name="version" depends="init" description="Create Version.java">
    <echo file="src/${package.dir}/Version.java" message="package ${package.name};${line.separator}" />
    <echo file="src/${package.dir}/Version.java" append="true" message="public final class Version {${line.separator}" />
    <echo file="src/${package.dir}/Version.java"
          append="true"
          message=" public static String VERSION=&quot;${buildtime}&quot;;${line.separator}" />
    <echo file="src/${package.dir}/Version.java" append="true" message="}${line.separator}" />
    <echo message="BUILD ${buildtime}" />
</target>

Is it possible to do something similar in Maven, using generate-sources, or some other simple method?

Joeri Hendrickx
  • 16,947
  • 4
  • 41
  • 53
Ralph
  • 31,584
  • 38
  • 145
  • 282

9 Answers9

80

I don't think this is the good way to solve this kind of issue.

A better way is to put the version information in a properties file that will be read by your Java program:

Your properties file will contain the following line:

myapp.version=${project.version}

Then, in your pom.xml, indicate that the file will be filtered by Maven :

<resources>
    <resource>
        <directory>the/directory/that/contains/your/properties/file</directory>
        <filtering>true</filtering>
    </resource>
</resources>

When Maven will build your application, it will replace all ${...} by their value. By default, ${project.version} defines the version of the pom.xml (i.e. the value of the <version> tag).

Then, in your Java code, you will just need to load the properties file and retrieve the myApp.version property value.

Note that you can use the Build Number plugin to set something more "complex" than just your current version (for example if you want to put the build time in your property).

Romain Linsolas
  • 79,475
  • 49
  • 202
  • 273
  • I think that approach is great but not applicable when you have an annotation such as @PersistentUnit(value="myPU"). What do you think about this case? – Guido May 31 '11 at 16:03
  • If using maven 3.x, replace `${pom.version}` (now deprecated) with `${project.version}`. http://docs.codehaus.org/display/MAVENUSER/MavenPropertiesGuide – lukestevo Nov 24 '13 at 22:26
  • 5
    Why is it not the good way to solve it? I have multiple modules with disjoint sets of properties files. I believe that storing to multiple properties files is the wrong way. – mirelon May 06 '14 at 07:38
  • @mirelon For many reasons, maintainability is one of them. Creating a Java file with concatenation of String from an Ant task is a little bit weird, and you cannot be sure you will not write unvalid code. Maven filter has exactly this purpose, so why not using it? – Romain Linsolas May 06 '14 at 08:03
  • 1
    Properties files can be modified or lost. It seems best if that this is embedded in the binary. – kervin Jan 22 '15 at 00:12
  • @kervin I disagree. Some properties files contains information that is dependent of the environment (for example database credentials). Thus, you cannot embed them in the binary (otherwise, you will have to create one different binary per environment, which is also a bad practice). – Romain Linsolas Jan 22 '15 at 07:59
  • @romaintaz of course property files are extremely useful. I'm saying they're not useful for storing a client application's version information specifically. – kervin Jan 22 '15 at 12:19
  • @kervin indeed, some properties files, like the one that will store the application version, can be embedded in the binary, as they are not dependent of the environment. But that's exactly the point of my answer: you have a properties file in `src/main/properties` with a placeholders, and during the packaging, Maven will replace the placeholder with the value, and embed the properties file in the .jar. – Romain Linsolas Jan 26 '15 at 07:36
  • This is great, except when the constant needs to be used in an annotation attribute, where only compile-time constants are allowed. – VGR Aug 25 '15 at 16:00
30

You can also use maven-replacer-plugin if you feel ant is a little bit ugly: The pom entry might be:

<project>
  ...
  <properties>
    <version.template.file>src/main/java/com/stackoverflowVersion.java.template</version.template.file>
<version.file>src/main/java/com/stackoverflow/Version.java</version.file>
  </properties>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>com.google.code.maven-replacer-plugin</groupId>
            <artifactId>maven-replacer-plugin</artifactId>
            <version>1.4.0</version>
            <executions>                
                <execution>
                    <phase>process-sources</phase>
                    <goals>
                        <goal>replace</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <file>${version.template.file}</file>
                <outputFile>${version.file}</outputFile>
                <replacements>
                    <replacement>
                        <token>@buildnumber@</token>
                        <value>${svn.revision}</value>
                    </replacement>
                    <replacement>
                        <token>@buildtime@</token>
                        <value>${maven.build.timestamp}</value>
                    </replacement>
                    <replacement>
                        <token>@pomversion@</token>
                        <value>${project.version}</value>
                    </replacement>
                </replacements>                        
            </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

The Version.java.template might be:

package com.stackoverflow;

public final class Version {

    public static final String build_number="@buildnumber@";

    public static final String build_time="@buildtime@";

    public static final String pomversion="@pomversion@";

}
MartyIX
  • 27,828
  • 29
  • 136
  • 207
spupek
  • 452
  • 7
  • 12
24

This is an old question, but there is another solution that does a great job this perfectly (in the Maven sense): Templating Maven Plugin.

Using this plugin results in the processed Java file being put into the target/generated-sources folder, as you would expect. And it adds the folder under generated-sources to the build path. You will no longer check-in the processed file by mistake.

How to use

First put the following under src/main/java-templates/com/foo/bar/Version.java:

package com.foo.bar;
public final class Version {
    public static final String VERSION = "${project.version}";
}

Then add the following to your POM:

<build>
    <plugins>
    ...
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>templating-maven-plugin</artifactId>
            <version>1.0.0</version>
            <executions>
                <execution>
                    <id>filtering-java-templates</id>
                    <goals>
                        <goal>filter-sources</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    ...
    </plugins>
</build>

The folder target/generated-sources/java-templates is added to the build path by Maven.

vegemite4me
  • 6,621
  • 5
  • 53
  • 79
  • This works for my but I still have to manually start templating:filter-sources. I'm new to Maven. How to execute this plugin before changing profiles or starting each build? – Roel Jul 04 '16 at 12:23
  • Yes you do need to call Maven in order to generate the source if you change a template. But you could simply call `mvn install` and not have to bother remember the specific goal you have used. – vegemite4me Jul 04 '16 at 15:51
  • 1
    @Roel this plugin is gonna [bind itself automatically](https://github.com/mojohaus/templating-maven-plugin/blob/d0175ba4a728060318d1d00b4dbcf83a98faafae/src/main/java/org/codehaus/mojo/templating/FilterSourcesMojo.java#L33) to one of the first phases of the Maven lifecycle: `generate-sources`. So, you actually don't have to call it manually as also said vegemite4me – Baptiste Mathus Nov 21 '16 at 09:11
12

After more Googling, I came up with this (in the pom.xml):

<plugins>
  ...
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.3</version>
    <executions>
      <execution>
        <goals>
          <goal>run</goal>
        </goals>
        <phase>generate-sources</phase>
        <configuration>
          <tasks>
            <property name="src.dir" value="${project.build.sourceDirectory}" />
            <property name="package.dir" value="com/foo/bar" />
            <property name="package.name" value="com.foo.bar" />
            <property name="buildtime" value="${maven.build.timestamp}" />

            <echo file="${src.dir}/${package.dir}/Version.java" message="package ${package.name};${line.separator}" />
            <echo file="${src.dir}/${package.dir}/Version.java" append="true" message="public final class Version {${line.separator}" />
            <echo file="${src.dir}/${package.dir}/Version.java" append="true"
              message=" public static String VERSION=&quot;${buildtime}&quot;;${line.separator}" />
            <echo file="${src.dir}/${package.dir}/Version.java" append="true" message="}${line.separator}" />
            <echo message="BUILD ${buildtime}" />
          </tasks>
        </configuration>
      </execution>
    </executions>
  </plugin>
  ...
</plugins>

It seems to work well and produces this Java file:

package com.foo.bar;
public final class Version {
 public static String VERSION="100318.1211";
}
Ralph
  • 31,584
  • 38
  • 145
  • 282
  • 10
    This is a really bad solution. Better use the solution given by romaintaz. Generating sourcecode for this kind of stuff leads to unnecessary compiles as the java file is recreated each time and this triggers a cascade of compilations. Please do yourself and your coworkers a favor and don't do this! – Patrick Cornelissen Jul 24 '12 at 14:28
  • 2
    I was looking for a solution to create a text file version.txt inside `webapp` root, and I guess this is the best one. – Kerb Jan 31 '13 at 08:07
12

Here is another solution that will produce the same as Ralph's own answer, using pom properties filtering and a template file:

The template file (VersionJava.template placed in src/main/resources/version):

package ${ver.package.name};
public final class ${ver.class.name} {
    public static String VERSION="${ver.buildtime}";
}

The pom:

<properties>
    ...
    <ver.package.dir>com/foo/bar${project.artifactId}</ver.package.dir>
    <ver.package.name>com.foo.bar${project.artifactId}</ver.package.name>
    <ver.class.name>Version</ver.class.name>
    <ver.buildtime>${maven.build.timestamp}</ver.buildtime>
    <ver.template.dir>src/main/resources/version</ver.template.dir>
    <ver.template.file>VersionJava.template</ver.template.file>
</properties>
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>version/*</exclude>
            </excludes>
        </resource>
        <resource>
            <directory>${ver.template.dir}</directory>
            <includes>
                <include>*.java</include>
            </includes>
            <filtering>true</filtering>
            <targetPath>${basedir}/src/main/java/${ver.package.dir}</targetPath>
        </resource>
    </resources>        
    <plugins>
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <phase>generate-sources</phase>
                    <configuration>
                        <tasks>
                            <copy file="${ver.template.dir}/${ver.template.file}" tofile="${ver.template.dir}/${ver.class.name}.java" />
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
                <execution>
                    <phase>compile</phase>
                    <configuration>
                        <tasks>
                            <delete file="${ver.template.dir}/${ver.class.name}.java" />
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Now this may seem excessive, but it is extremely versatile, and what I like most about it is that I have the template file in a readable format (rather than echo statements in the pom). This also allows me to modify the version class without having to change the pom

Superole
  • 1,329
  • 21
  • 29
  • This is aweseome! I would put the target file in /target/source/${ver.package.dir} and then (assuming Eclipse) add /target/source/${ver.package.dir} as a source folder. That way you exclude it from source control – John Oxley Feb 25 '12 at 06:42
  • 1
    @john-oxley I agree. The generated file should have been in the target folder rather than src. More over, I think if it was put in 'target/generated-sources/pom/${ver.package.dir}' then (assuming NetBeans) it would be included as a source automagically. I haven't tried this yet but I will, and if it works as expected I should edit my answer :) – Superole Feb 27 '12 at 12:50
  • This solution causes an endless loop in Eclipse 4.2 with the m2e plugin. I used, however, the solution without the ant task. I currently don't see the reason for the latter: The filtering solution just generates the Version.java with the correct fields. – koppor Nov 26 '13 at 13:50
10

Based on the answer by @superole. This is a simplified version without the need to set extra properties. Just the project's version is copied into Version.java.

Put Version.java into src/main/templates:

package thepackage;

public final class Version {

 public static String VERSION="${project.version}";

}

Instruct maven to replace the tokens in Version.java

<resources>
    <resource>
        <directory>src/main/templates</directory>
        <includes>
            <include>*.java</include>
        </includes>
        <filtering>true</filtering>
        <targetPath>${project.build.directory}/generated-sources/java/thepackage</targetPath>
    </resource>
</resources>

Instruct maven to know generated-sources/java as build path:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
             <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>${project.build.directory}/generated-sources/java/</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

Finally, let Eclipse m2e

  • be aware of the new build path
  • and not to fall into an endless loop build.

The second point is achieved by disabling using the maven-resources-plugin during the incremental build of eclipse.

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.eclipse.m2e</groupId>
            <artifactId>lifecycle-mapping</artifactId>
            <version>1.0.0</version>
            <configuration>
                <lifecycleMappingMetadata>
                    <pluginExecutions>
                        <pluginExecution>
                          <pluginExecutionFilter>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>build-helper-maven-plugin</artifactId>
                            <versionRange>[1.0,)</versionRange>
                            <goals>
                              <goal>parse-version</goal>
                              <goal>add-source</goal>
                              <goal>maven-version</goal>
                              <goal>add-resource</goal>
                              <goal>add-test-resource</goal>
                              <goal>add-test-source</goal>
                            </goals>
                          </pluginExecutionFilter>
                          <action>
                            <execute>
                              <runOnConfiguration>true</runOnConfiguration>
                              <runOnIncremental>true</runOnIncremental>
                            </execute>
                          </action>
                        </pluginExecution>
                        <pluginExecution>
                            <pluginExecutionFilter>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-resources-plugin</artifactId>
                                <versionRange>[1.0.0,)</versionRange>
                                <goals>
                                    <goal>resources</goal>
                                </goals>
                            </pluginExecutionFilter>
                            <action>
                                <execute>
                                    <runOnConfiguration>true</runOnConfiguration>
                                    <runOnIncremental>false</runOnIncremental>
                                </execute>
                            </action>
                        </pluginExecution>
                    </pluginExecutions>
                </lifecycleMappingMetadata>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

thepackage needs to be replaced by your package: Also adjust the targetPath accordingly. I found it easier to set the path in targetpath instead of having many subfolders in src/main/templates.

Community
  • 1
  • 1
koppor
  • 19,079
  • 15
  • 119
  • 161
4

I'm doing it using the Maven WAR Plugin adding information to the MANIFEST.MF file and later reading this MANIFEST.MF file in Java:

     <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
           <archive>
              <manifest>
                 <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                 <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
              </manifest>
              <manifestEntries>
                 <Build-Time>${maven.build.timestamp}</Build-Time>
              </manifestEntries>
           </archive>
        </configuration>
     </plugin>

This configuration generates the following MANIFEST.MF file:

Manifest-Version: 1.0
Implementation-Title: MyApp
Implementation-Version: 2.11.0-SNAPSHOT
Built-By: niestroj
Specification-Title: MyApp
Implementation-Vendor-Id: com.mycompany
Build-Time: 2017-01-09 15:30
Created-By: Apache Maven 3.0.5
Build-Jdk: 1.8.0_40
Specification-Version: 2.11

And later i'm reading this in Java like this:

  try {
     Manifest manifest = new Manifest(getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF"));
     Attributes attributes = manifest.getMainAttributes();
     attributes.getValue("Implementation-Version");
     attributes.getValue("Build-Time");
  } catch (IOException ex) {
     LOGGER.debug("Error reading manifest file information", ex);
  }
Robert Niestroj
  • 15,299
  • 14
  • 76
  • 119
3

As suggested by @Romain, you could read the version from a property file (either /META-INF/maven/groupId/artifactId/pom.properties if you can wait until the packaging or roll your own filtered file if you can't or if it doesn't provide everything you need).

And is you want to stick with your actual Version class, then have a look at this thread on the maven users list which is precisely proposing a solution for this (based on the antrun plugin that you'll bind on the generated-sources phase).

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
2

The standard way to do just that with very few lines of XML code is now to use the templating-maven-plugin.

See my answer in Filtering source code in Maven

In general, the Maven way is to describe what you want to do. Then figure how. When how requires tens or hundreds of lines of XML, either find the right plugin that does that, or write it. That was the rationale that created the templating-maven-plugin :-).

Community
  • 1
  • 1
Baptiste Mathus
  • 888
  • 2
  • 11
  • 18