Maven was briefly described in my previous post. In this post I will do a few small examples for actually using Maven.
One of the main things about Maven is the idea of the artifact. The artifact is a file, which is usually a jar file. This might be a jar that is a dependency or a jar, war or ear file that is generated by Maven. All artifacts are described by three different key values.
- artifactId
- groupId
- version
The artifactId is just a name. The group id is also just a name but it is usually it is the reverse domain of the company. The version number is just the version of that particular object.
Hello world – manual
The directory structure is fairly simple. The program code will be under the src/main directory structure while the unit tests will be under the main/test directory.
mkdir helloworld cd helloworld mkdir src mkdir src/main mkdir src/main/java mkdir src/test mkdir src/test/java
The structure is simple but must be strictly adhered to. The structure is well defined so the project object model (pom) file doesn’t need to contain much at all.
<project> <modelVersion> 4.0.0</modelVersion> <groupId> de.acmesoft </groupId> <artifactId> helloworld </artifactId> <version> 1.0.0 </version> </project>
It is important to have the artifact keys in the pom file and they should be different than any other artifact keys. This is important in case this artifact should end up in the repository.
The modelVersion field is refers to the internal structures for Maven. In future releases of Maven this version may change.
The executable from Maven is mvn. Simply pass in the phase that you are interested in and Maven will execute the lifecycle up to that point.
For this sample I could simply compile the class files or go one step further and generate a jar file.
#: /tmp/mvn mvn compile [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building helloworld 1.0.0 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.3:resources (default-resources) @ helloworld --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory /home/dock/working/content/43-blog/maven/helloworld3/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ helloworld --- [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 1 source file to /home/dock/working/content/43-blog/maven/helloworld3/target/classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.974s [INFO] Finished at: Sun Dec 11 23:27:27 CET 2016 [INFO] Final Memory: 10M/119M [INFO] ------------------------------------------------------------------------
Running hello world
Maven creates all of it’s output files into the target directory. This is true for both the normal class files as well as the jar files (for simple java programs) that get created.
If you want a quick test simply set the class path to point to the new jar file.
java -cp target/helloworld-1.0.0.jar com.acmesoft.helloworld
If you look closely at the pom file above you will see that it is inconsistent from the pom file. The package in the source code actually does reflect the test command. At the end of the day, the code will indeed have the final say.
package com.acmesoft; public class helloworld { public helloworld() { System.out.println("hello world!"); } public static void main (String[] args) { new helloworld(); } }
Hello world – template
In my opinion the most convenient feature of Maven is the ability to create an empty project. There is a list of thousands of different types of template projects. With a simple command it is possible to create the entire project from scratch – just add code.
mvn archetype:generate -DgroupId=de.acmesoft -DartifactId=helloworld -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Stub Project (No POM) 1 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom >>> [INFO] [INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom <<< [INFO] [INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom --- [INFO] Generating project in Batch mode [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0 [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: basedir, Value: /tmp [INFO] Parameter: package, Value: de.acmesoft [INFO] Parameter: groupId, Value: de.acmesoft [INFO] Parameter: artifactId, Value: helloworld [INFO] Parameter: packageName, Value: de.acmesoft [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] project created from Old (1.x) Archetype in dir: /tmp/helloworld [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.132s [INFO] Finished at: Sun Dec 11 23:46:15 CET 2016 [INFO] Final Memory: 13M/223M [INFO] ------------------------------------------------------------------------
The generate command not only creates the directory structure but also creates the pom.xml file. You can see how the command line parameters end up in the appropriate location in the pom file, which is also created automatically.
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.acmesoft.secondexample</groupId> <artifactId>secondexample</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>secondexample</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
This pom file is actually also a very basic file. The very minimum is the same as the manually created pom file in the manual section. Maven automatically added Junit in order to allow the unit tests that it also created to compile.
package de.acmesoft; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * Unit test for simple App. */ public class AppTest extends TestCase { /** * Create the test case * * @param testName name of the test case */ public AppTest( String testName ) { super( testName ); } /** * @return the suite of tests being tested */ public static Test suite() { return new TestSuite( AppTest.class ); } /** * Rigourous Test :-) */ public void testApp() { assertTrue( true ); } }
The template also includes the source code for our hello world program as well.
package de.acmesoft; /** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } }
One small difference between the manual project that I created is that using the template creates the package, the pom file and the directory structure all consistent with each other.
Hello world – log4j
The generic hello world prints the output to the standard output and doesn’t use external libraries for any task. Some applications or utilities might be this simple but it is uncommon for any but the most trivial application to not use external libraries for some purpose.
To simulate such a case, I have modified the hello world application to use log4j for all output.
package com.acmesoft.secondexample; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.Logger; public class App { public App() { String cfgFileName = "log4j.properties"; PropertyConfigurator.configure(cfgFileName); Logger logfile = Logger.getLogger(App.class.getName()); System.out.println("hello world!"); logfile.info("hello world"); } public static void main (String[] args) { new App(); } }
Running hello world
My first test of the application failed for obvious reasons.
java -cp target/helloworld-1.0.0.jar com.acmesoft.secondexample.App
The reason was NoClassDefFoundError was thrown. The application uses log4j so it will not work unless it is part of the classpath.
java -cp target/secondexample-1.0-SNAPSHOT.jar:lib/log4j-1.2.17.jar com.acmesoft.secondexample.App
Maven is just another way to compile an application, the Java basics don’t change. Simply make sure that when running the program that all necessary dependencies are provided. However, Java programs cannot be compiled unless the code that it is dependent on are provided. This is done by adding dependencies to the pom file.
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.acmesoft.secondexample</groupId> <artifactId>secondexample</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>secondexample</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>2.0.1</version> </dependency> </dependencies> </project>
The dependencies block contains both a dependency for JUnit and Log4j. The JUnit dependency was automatically added when the application was created by Maven (in order to support the automated testing) but I manually added the Log4j block as it would be necessary for the program.
Hello world – log4j revised
The good news is that Maven can be configured to include the dependencies as part of the “package”. This way it is possible to create the equivalent of a statically linked application that can simply be copied from directory to directory and it will still work.
The list of dependencies don’t change but what does change is the new “build” block that embeds the dependencies into the resulting jar.
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.acmesoft.firstexample</groupId> <artifactId>firstexample</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>firstexample</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.acmesoft.firstexample.App</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Once the dependencies are embedded inside the jar file, you can simply run the application without worrying about the rest of the dependencies.
java -cp target/firstexample-1.0-SNAPSHOT.jar com.acmesoft.secondexample.App