Maven comes with a possibility to generate projects based on pre-defined templates. [1] Let's generate a basic Java application using this feature.
By executing goal archetype:generate maven starts the interactive mode of the application archetyping and suggests to select a template to use as a base:
$ mvn archetype:generate
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.1.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.1.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.1.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> am.ik.archetype:maven-reactjs-blank-archetype (Blank Project for React.js)
2: remote -> am.ik.archetype:msgpack-rpc-jersey-blank-archetype (Blank Project for Spring Boot + Jersey)
3: remote -> am.ik.archetype:mvc-1.0-blank-archetype (MVC 1.0 Blank Project)
…
It lists all known templates. It is possible to filter them by typing a part of the name of a desired archetype (below). Let's try to filter by full name of the archetype:
2427: remote -> ws.osiris:osiris-archetype (Maven Archetype for Osiris)
2428: remote -> xyz.luan.generator:xyz-gae-generator (-)
2429: remote -> xyz.luan.generator:xyz-generator (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1377: maven-archetype-quickstart
It turns out, there are two available archetypes with this name:
Choose archetype:
1: remote -> com.haoxuer.maven.archetype:maven-archetype-quickstart (a simple maven archetype)
2: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 2:
The second one is what we are looking for, so just press enter key. Immediately after, we are given a choice to select a version of the archetype:
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
7: 1.3
8: 1.4
Choose a number: 8:
Yes, we are interested in the last version so press enter one more time. Then we need to enter some more values to finish generating the application:
Define value for property 'groupId': de.natucci
Define value for property 'artifactId': Hello
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' de.natucci: :
Confirm properties configuration:
groupId: de.natucci
artifactId: Hello
version: 1.0-SNAPSHOT
package: de.natucci
Y: :
Now the app is generated and placed into Hello directory:
$ tree Hello
Hello
├── pom.xml
└── src
├── main
│ └── java
│ └── de
│ └── natucci
│ └── App.java
└── test
└── java
└── de
└── natucci
└── AppTest.java
9 directories, 3 files
Let's read pom.xml file. Our project is indeed quite basic — there is only one dependency:
…
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
…
This dependency is needed to execute a test file included in the archetype AppTest.java:
package de.natucci;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
}
As you can see, it is pretty basic. To run the tests, use mvn test goal:
…
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running de.natucci.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s - in de.natucci.AppTest
…
Everything works as expected. Now, let's see what the main class of the program in the only non-test file there is looks like:
$ cat src/main/java/de/natucci/App.java
package de.natucci;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
It contains everything there is to be to execute: public class, and public static main function with args to operate on command-line arguments.
Since we have got no other dependencies, we could execute this class by compiling it and then asking java to execute class de.natucci.App, for which the class files can be found in target/classes/ directory:
java -cp target/classes/ de.natucci.App
Hello World!
Again, works as expected. On the other hand, this archetype comes with a plugin that packages all your classes in a jar file — maven-jar-plugin, so when you execute package goal you will see a jar file in target directory:
$ ls -alhn target/*.jar
-rw-rw-r--. 1 1000 1000 2.5K Jun 6 17:17 target/Hello-1.0-SNAPSHOT.jar
However, this plugin needs to be configured such that it puts a manifest file inside that specifies what the main class of the package is. It is done by adding a configuration section to the plugin definition in pom.xml file [2]:
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<mainClass>de.natucci.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
Now we can package and run a jar file like this:
$ mvn package
…
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ Hello ---
[INFO] Building jar: /home/eugen/Work/Hello/target/Hello-1.0-SNAPSHOT.jar
…
[INFO] BUILD SUCCESS
…
$ java -jar target/Hello-1.0-SNAPSHOT.jar
Hello World!
By the way, jar files can be managed with jar cli utility and it feels very similar to tar:
$ jar tvf target/Hello-1.0-SNAPSHOT.jar
124 Thu Jun 06 17:24:02 GET 2019 META-INF/MANIFEST.MF
0 Thu Jun 06 17:24:02 GET 2019 META-INF/
0 Thu Jun 06 17:24:02 GET 2019 de/
0 Thu Jun 06 17:24:02 GET 2019 de/natucci/
0 Thu Jun 06 17:24:02 GET 2019 META-INF/maven/
0 Thu Jun 06 17:24:02 GET 2019 META-INF/maven/de.natucci/
0 Thu Jun 06 17:24:02 GET 2019 META-INF/maven/de.natucci/Hello/
535 Thu Jun 06 17:24:02 GET 2019 de/natucci/App.class
2847 Thu Jun 06 17:23:52 GET 2019 META-INF/maven/de.natucci/Hello/pom.xml
88 Thu Jun 06 17:24:02 GET 2019 META-INF/maven/de.natucci/Hello/pom.properties
Let's try to extract it and see what we can find in that MANIFEST.MF file:
$ cd target/ && jar xvf Hello-1.0-SNAPSHOT.jar
inflated: META-INF/MANIFEST.MF
created: META-INF/
created: de/
created: de/natucci/
created: META-INF/maven/
created: META-INF/maven/de.natucci/
created: META-INF/maven/de.natucci/Hello/
inflated: de/natucci/App.class
inflated: META-INF/maven/de.natucci/Hello/pom.xml
inflated: META-INF/maven/de.natucci/Hello/pom.properties
$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Built-By: eugen
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_212
Main-Class: de.natucci.App
Indeed, now we see that it includes information about the main class of the package.