Chairperson and Program Coordinator for the Computer Science Technology Program at Dawson College Instructor and Program Consultant for the School of Extended Learning Computer Institute at Concordia University I have been passionate about programming since buying an Apple][+ in 1980. I paid the extra $450 to bring the RAM up to 48K! Ken has posted 17 posts at DZone. You can read more from them at their website. View Full User Profile

Universal Maven for Teaching Java Desktop Applications

08.18.2014
| 3633 views |
  • submit to reddit
Ken Fogel is the Program Coordinator and Chairperson of the Computer Science Technology program at Dawson College in Montreal, Canada. He is also a Program Consultant to and part-time instructor in the Computer Institute of Concordia University's School of Extended Learning. He blogs at omniprogrammer.com and tweets @omniprof. His regular columns about NetBeans in education are listed here.

Now that we have reviewed the pom.xml file that was created by the NetBeans JavaFX archetype, we can look at my new and improved version. This pom.xml was created with a few conditions in mind. These were:

  • Must be independent of any particular IDE

  • Usable for any type of desktop application in Java 8 be it Console, Swing or JavaFX

  • Represents a best practice for Maven in a teaching/learning environment

The last point is important to keep in mind. There are many ways to accomplish similar outcomes in Maven. My definition of a best practice is that the student should be able to easily understand what the pom is doing. This is likely not always the best approach in a commercial production environment. If a student can understand my best practice then they should be able to understand more complex systems they may encounter in the industry.

Let’s begin with these first lines are the same as what the archetype created.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation=
            "http://maven.apache.org/POM/4.0.0 
            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kenfogel</groupId>
    <artifactId>FXMavenArticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>FXMavenArticle</name>

Project Info

    <description>Example of super pom for Java application development</description>

    <developers>
        <developer>
            <id>9999999</id>
            <name>Student Name</name>
            <email>student@theirmail.com</email>
        </developer>
    </developers>
	
    <organization>
        <name>Dawson College</name>
    </organization>
Here we see three sections. The first, <description>, allows you to write a brief narrative about the program that is using this pom.

The second section, <developers>, identifies the programmers involved in the project. The documentation on Maven discusses the difference between someone who contributes code and someone responsible for committing code to a repository and there is a tag, <role> for this purpose. In a school situation the student fulfils both roles. The <id> is a tag used to create a unique identifier for a section and in the school environment I have the student use their student id number. Next is the <name> and then the <email>.

The last section we have already seen and is used if you plan to distribute the software using JNLP. I teach at two schools, so having the school name here is handy for me.

Properties

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- class that has the main method -->
        <mainClass>com.kenfogel.fxmavenarticle.MainApp</mainClass>
        <!-- name appended to standard jar name to indicate shaded/executable version -->
        <shadedClassifierName>executable</shadedClassifierName>
    </properties>

There is one new property in the <properties> section. We are configuring Maven to produce an executable JAR file, one that you can double-click on to run if the computer has a JRE installed on it. The plugin that makes this happen wants to append the text ‘-shaded’ to the name of the JAR file. The tag <shadedClassifierName> allows us to pick our own text.

Dependencies

    <dependencies>
        <!-- Everyone needs a logger, this is my preference -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>

        <!-- Unit testing is mandatory -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <!-- only to be used during test phase -->
            <!-- will not be included in executable jar -->
            <scope>test</scope>
        </dependency>
    </dependencies>

Before I adopted Maven in the classroom, a common problem my students encountered was properly acquiring the required libraries for their projects and placing them correctly in the build path. Even more annoying for me was loading a student project on my computer to evaluate it only to discover that it pointed to libraries stored on the student’s personal computer, usually in another project that I did not have access to. It’s difficult to remain objective when students do not follow instructions and then force me to spend time reconfiguring their project.

Maven dependencies eliminates all this. The libraries listed in the <dependencies> section will be added to the build of the project automatically. I know that they can only submit projects that work on any system. The documentation for most libraries and frameworks include the dependency section that you will need to add to your pom. You can also look up the dependency XML you need at maven.org.

My pom has two dependencies that I consider required in my classroom. The first is for JUnit 4 for unit testing. The original pom file used the maven-dependency-plugin to exclude test libraries from the final JAR file. You can accomplish the same result by declaring the <scope> of the library as test.

The second dependency is for a logger. My favourite of the week is slf4j that is really a façade for log4j. I will write an article about this. Suffice to say that System.out.println is not the way to display information while a program runs.

Build

Now we move on to the <build> section that is responsible for compiling, assembling and executing the project.

<build>
        <!-- Goals may be set in the IDE or the pom -->
        <!-- IDE goals override the defaultGoal -->
        <defaultGoal>clean compile package exec:java</defaultGoal>

The tag <defaultGoal> is used to define the Maven goals. Goals are actions that Maven must carry out. Most IDEs allow you to declare the goals that you want in their Run command. If you do that then it will override what you see here. I use this tag to show the students what the common actions of Maven are.

Clean deletes all class files and jar files.

Compile does what it means. If you do not use clean then only source code files newer than their compiled class files will be compiled. For student sized projects clean is not an issue but in large systems you will likely only clean at certain milestones.

Package means to create the JAR file. Normally the libraries listed in the dependency section are not included in the package. The plugin we will use to create an executable JAR will add the dependencies to the package.

Finally comes exec that means to execute the JAR. There are two flavours of exec, one is exec:java and the other is exec:exec. You must use one of these. The exec:exec runs the package as if it were being run from the command line. This means that it will run in its own instance of the JVM. The exec:java runs the package within the same JVM as the IDE. I prefer exec:java because logging and System.out appear in the IDE’s console while if you use exec:exec you do not see this information until the execution ends.

The next plugin deals with the compiler.

        <plugins>
            <plugin>
                <!-- Select the version of the compiler and binary file -->
                <!-- format of the classfiles -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- sometimes the IDE does not reveal all the important warning -->
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>

In the original pom, the compiler version was 1.7. In Java 1.7 the JavaFX library was not in the classpath and so it had to appear in the compiler plugin. Java 8 has JavaFX in the classpath. The three tabs, <compilerArgument>, <showWarnings> and <showDeprecation>, are used to have the compiler display any warnings it detects. NetBeans already shows us warnings right in the editor however this is configurable and not all the warning I like are turn on. The Xlint is a message to the compiler to display any issues it finds. Errors will always be displayed but we have to explicitly ask for warnings.

If you ever try to use the Date API, you will discover that much of it is deprecated. I describe this as an API that was extensively used before someone noticed that it sucked. Being deprecated means that it may disappear from a newer version of Java so stop using it. So far, in my experience, nothing that is deprecated has been dropped but since it sucks you should use the replacement. See the JavaDocs for the API to learn about the replacement. Sometimes the IDE will be able to tell you what you should be using.

Shade

The next plugin creates an executable jar.

            <plugin>
                <!-- shade creates an executable jar with the main class -->
                <!-- showing in the manifest file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>
                                ${shadedClassifierName}
                            </shadedClassifierName> 
                            <createDependencyReducedPom>
                                false
                            </createDependencyReducedPom>
                            <transformers>
                                <transformer implementation=
               "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

In Maven we call an executable JAR a shaded jar. Sometimes also called an uber-jar. This plugin assembles all the necessary files into a single JAR. JAR files contain a text file called MANIFEST.MF. The shade plugin adds the name of the <mainClass> to this file. The <shadedClassifierName> is what you want to use in place of ‘-shaded’. The <createDependencyReducedPom> set to false keeps the plugin from creating something called a reduced pom. I must admit that I am not sure what its purpose is so I don’t let it be created and I don’t have to tell my students I don’t know what it is (although after reading this they will know that I don’t know). The <transformer> is the object in Maven that creates the shaded JAR.

Look at the following screen image that shows the difference between a normal JAR and a shaded JAR.

In the original pom, the creation of an executable JAR was carried out by an external program called javafxpackager. This program is not required in Java 8 to create an executable JavaFx program. It does have an interesting new capability in Java 8 that I have not tried yet. It is capable of creating executable files that contain a JRE. These files can have an .exe extension for Windows systems. It can also create install packages, msi for Windows and dmg for Macs. This may revive interest in creating and distributing Java desktop applications.

Execute

The next plugin allows Maven to execute the program.

            <plugin>
                <!-- executes the program -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.1</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <!-- java runs the code from the main class -->
                            <goal>java</goal> 
                        </goals>
                        <configuration>
                            <!-- used by java goal -->
                            <mainClass>${mainClass}</mainClass>
                            <!-- used by exec goal -->
                            <executable>${java.home}/bin/java</executable>
                            <commandlineArgs>
                                -jar ${project.build.directory}/${project.build.finalName}-
                                ${shadedClassifierName}.jar
                            </commandlineArgs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

The <goal> can be either exec or java, my preference is java. In the configuration are tags for both possible execution methods. This is necessary because if you use the NetBeans Run command on the project rather than execute Maven then NetBeans will want tags associated with the exec:exc goal.

The <commandlineArgs> tag contains text that is used for exec:exec. A certain IDE that almost rhymes with ‘cake mix’ has a bad habit of splitting the text at the space after ‘–jar’ when formatting the xml (NetBeans does not do this). This causes an error when the exec:exec goal is used.

Surefire

Finally, comes the surefire plugin.

            <plugin>
                <!-- Executes JUnit tests and writes the results as an xml and txt file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
            </plugin>

The output from JUnit tests are written to the console and to a file. This is a simplified version as compared to the original pom because JavaFX is in the classpath of Java 8.

My last and much shorter article in this series will look at how you can use Maven projects in NetBeans.

Here is the complete pom.xml. Please let me know if there is anything you see that I have done wrong. Living in my ivory tower (actually it’s just a walk up) I sometimes make faulty assumptions.

The pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kenfogel</groupId>
    <artifactId>FXMavenArticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>FXMavenArticle</name>

    <description>Example of super pom for Java application development</description>

    <developers>
        <developer>
            <id>9999999</id>
            <name>Student Name</name>
            <email>student@theirmail.com</email>
        </developer>
    </developers>
	
    <organization>
        <name>Dawson College</name>
    </organization>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- class that has the main method -->
        <mainClass>com.kenfogel.fxmavenarticle.MainApp</mainClass>
        <!-- name appended to standard jar name to indicate shaded/executable version -->
        <shadedClassifierName>executable</shadedClassifierName>
    </properties>

    <dependencies>
        <!-- Everyone needs a logger, this is my preference -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>

        <!-- Unit testing is mandatory -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <!-- only to be used during test phase -->
            <!-- will not be included in executable jar -->
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <!-- Goals may be set in the IDE or the pom -->
        <!-- IDE goals override the defaultGoal -->
        <defaultGoal>clean compile package exec:java</defaultGoal> 
        <plugins>
            <plugin>
                <!-- Select the version of the compiler and binary file -->
                <!-- format of the classfiles -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- sometimes the IDE does not reveal all the important warning -->
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>

            <plugin>
                <!-- shade creates an executable jar with the main class -->
                <!-- showing in the manifest file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>${shadedClassifierName}</shadedClassifierName> 
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- executes the program -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.1</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <!-- java runs the code from the main class -->
                            <goal>java</goal> 
                        </goals>
                        <configuration>
                            <!-- used by java goal -->
                            <mainClass>${mainClass}</mainClass>
                            <!-- used by exec goal -->
                            <executable>${java.home}/bin/java</executable>
                            <commandlineArgs>-jar ${project.build.directory}/${project.build.finalName}-${shadedClassifierName}.jar</commandlineArgs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- Executes JUnit tests and writes the results as an xml and txt file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
            </plugin>

        </plugins>
    </build>
</project>
Published at DZone with permission of its author, Ken Fogel.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)