Generate a Version.java file in Maven

JavaMaven 2Code Generation

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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).

Solution 2 - Java

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@";
	
}

Solution 3 - Java

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.

Solution 4 - Java

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

Solution 5 - Java

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";
}

Solution 6 - Java

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.

Solution 7 - Java

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);
  }

Solution 8 - Java

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).

Solution 9 - Java

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 https://stackoverflow.com/questions/4106171/filtering-source-code-in-maven/18452939#18452939

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 :-).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRalphView Question on Stackoverflow
Solution 1 - JavaRomain LinsolasView Answer on Stackoverflow
Solution 2 - JavaspupekView Answer on Stackoverflow
Solution 3 - Javavegemite4meView Answer on Stackoverflow
Solution 4 - JavaSuperoleView Answer on Stackoverflow
Solution 5 - JavaRalphView Answer on Stackoverflow
Solution 6 - JavakopporView Answer on Stackoverflow
Solution 7 - JavaRobert NiestrojView Answer on Stackoverflow
Solution 8 - JavaPascal ThiventView Answer on Stackoverflow
Solution 9 - JavaBaptiste MathusView Answer on Stackoverflow