Managing a Java code project with Maven¶
In this course we will use a tool called Apache Maven for implementing and maintaining Java programs. There are other similar tools to achieve the same goal, but Maven is probably the most popular one. Maven allows us to, e.g. define how the project should be compiled and what kind of external libraries it needs. Another similar, especially recently quite popular tool is Gradle. Maven plays a similar role in a Java project as, e.g. the npm package management tool in Node.js-based JavaScript projects. We will only provide a small glimpse into Maven; just enough to fulfill the needs of the course. Maven will be used in all the coding exercises in this course.
NetBeans readily comes with Maven, but you may also install Maven separately by, e.g. following the
instructions provided on the Maven homepage or in Linux by using the
system’s package manager. If Maven has been installed properly, you should be able to run it by
entering the command mvn
in a command line prompt.
Maven project structure¶
A Maven project consists of mainly two things: a project configuration file named pom.xml
,
which is stored in the project’s root directory, and source files, which are stored under the
subdirectory src
. Java source files reside in the subdirectory src/main/java
.
E.g. a code file SomeClass.java
would have the file path src/main/java/SomeClass.java
.
Like the file extension suggests, the project file pom.xml
describes the project properties using the XML
format. If you are not yet familiar with XML, it is a good idea to learn its basic features, e.g. by
reading the Wikipedia article.
Below is an example of an almost minimal Maven project file:
<?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>fi.tuni.prog3</groupId>
<artifactId>example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
pom.xml
must contain at least the following parts:
A
project
root element (begins with a<project>
tag and ends with a</project>
tag). All elements listed below are inside the root element.A
modelVersion
element whose value should at present always be 4.0.0.This tells which Maven standard (or “model”) the project file corresponds to. The current standard is 4.0.0.
A
groupId
element that tells the group of the project.Maven uses this information to group projects in a hierarchical manner.
An
artifactId
element that tells the name of the project.A
version
element that tells the version of the project.This would usually be incremented as the program is developed further. E.g. version 1.0 could be followed by version 1.1 or version 2.0, and so on.
The preceding project file also included a packaging
element that defines what type of a
package Maven should generate; the value jar
specifies that the program’s compiled class files
and possible other resources will be packed into a so called JAR file (this would also be Maven’s
default behaviour). This is discussed further below. Towards the end there’s also a properties
element that declares that source files use the UTF-8 character encoding, that Java source files are
compatible with the Java version 17, and that the Java compiler will generate class files compatible
with the Java version 17. You should use Java version settings that are compatible with your installed Java
environment (the project cannot use a higher version than what the environment supports; lower
versions can be used).
NetBeans
A Maven project is created in NetBeans by either navigating to File | New Project...
or clicking
the folder icon with a small plus sign, followed by Java with Maven
and Java Application
.
NetBeans prompts you for the information to create the project and then creates a project file
roughly matching the example file given above, the src/main/java
directory and a class containing
the main
method, which you can delete if you so choose. The contents of the project can be
explored in the Projects
tab in the upper-left corner. The project file (pom.xml
) can be found
under Project files
, and the source code files under Source Packages
.
A project can be created without specifying a package (emptying the Package:
field). In this
case NetBeans won’t attached the source code files to any package and places the source code files
under Project Packages | <default packages>
. This is desirable with the exercises of the first couple
of weeks which don’t specify a package where the code should be placed.
NetBeans automatically appends the string -SNAPSHOT
to the version number which signifies that
the project is in development (unstable). In this course you can (and should) remove this string,
as the projects are small enough to give them the final version number from the start. E.g.
version 1.0-SNAPSHOT
becomes 1.0
. From now on it is assumed that version numbers are given
without the -SNAPSHOT
suffix.
Buildind (e.g. compiling) a Maven project¶
You may build a maven project either by using an IDE that supports Maven or manually on the command line. Some IDE’s (e.g. NetBeans) have built-in Maven support, but others (e.g. VS Code) require installing a separate plugin.
Maven’s build process consists of multiple phases. Some of the main phases are:
compile
: Compiles all source files under the project’ssrc
subdirectory.test
: Performs automated tests with the compiled program (if such tests have been defined).package
: Packages the program into a distributable format, such as a JAR file.install
: Stores the package into Maven’s local repository.E.g., in Linux Maven might store a package in the directory
.m2/repository
under the user’s home directory. Maven by default prints out information about the name and location of the stored package.
This was only a partial list of Maven’s build phases.
A maven project can be built on the command line by issuing a command of form mvn phase
, where
phase
specifies the phase we want to execute. The phases listed above are always executed in
succession so that executing a phase also implies executing all earlier phases. E.g.
mvn compile
compiles the project, mvn test
first compiles and then tests, and
mvn package
first compiles, then tests and finally packages it.
Maven by default places the compiled class files into the subdirectory target/classes
. If a project is
packaged, e.g. into a JAR file, Maven will place the package into the root of the target
subdirectory. The name of the created package is by default derived from the information defined in
the project file: it is typically named in the form artifactId-version.jar
.
The Run/Build project -command offered by NetBeans by default corresponds to issuing the command
mvn install
. Since install
is a later phase than package
, it will also package the
program into the target
subdirectory.
JAR files (Java Archive files)¶
Java compiler tools support packaging the files of a program (or a class library) into a single file
(a JAR file) that can then be conveniently distributed to end users. A JAR file contains all
compiled class files, and possibly also some other resources, of the program, and is actually just
a ZIP file that has been renamed to use the
file suffix “.jar
”. Therefore it is, e.g. possible to use general ZIP tools to inspect, extract
or modify the contents of a JAR file.
The Java compiler suite contains a command line program called jar
that can package already compiled
class files, etc., into a JAR file. We will not discuss this program further, as using Maven will
suffice in this course.
A program that has been packaged into a JAR file can be executed by giving the switch -jar
and
the JAR file name to the Java Virtual Machine. For example java -jar program.jar
would
execute a program that has been packaged into the JAR file program.jar
.
Java class libraries are practically always distributed as JAR files. If we want to execute a
program that relies on some classes or interfaces provided by some JAR file library.jar
, Java
needs to be able to find the library file. The file must be either placed into a directory that
Java would anyway inspect (its own internal class directories and the current working directory) or
we must separately specify with a “-cp
” switch a class path that leads to the file. For example
java -cp /some/directory/library.jar MyClass
would execute the class MyClass
in such manner that Java searches class files required during
program execution in all its internal directories and additionally in the JAR file
library.jar
located in the directory /some/directory
. A class path definition can include
several paths and/or JAR files by separating them with a colon (Linux, Mac) or a semicolon
(Windows) character. E.g. on Linux the execution
java -cp /another/directory/:.:/some/directory/library.jar MyClass
would direct the Java
virtual machine to search for class files in the directory /another/directory
, the current
working directory .
, and the JAR file library.jar
in the directory /some/directory
.
Using plugins with Maven¶
A Maven project file typically also includes a build
-element that contains definitions of
plugins that we want Maven to use during the build process. One such plugin that will be frequently
used in this course is a plugin called onejar. Here’s an example project file for including the onejar
plugin in a project:
<?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>fi.tuni.prog3</groupId>
<artifactId>example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>ExampleMain</mainClass>
<onejarVersion>0.97</onejarVersion>
<attachToBuild>true</attachToBuild>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The onejar plugin offers a convenient feature that packages also external
libraries into the JAR file. Maven would otherwise package only the project’s own class files, etc.,
into the JAR file, and then an end user would need to have both the JAR file of the project and
the JAR files of any required external libraries in order to be able to execute the program. When
we use onejar, the packaged JAR file will be self-sufficient. There will actually be two JAR files:
one is the default one without external libraries and the other one contains also external
libraries. The latter is by default named by adding the string “one-jar” into the default JAR file
name. E.g. if we would use the above project file, the command mvn package
would create the
JAR files target/example-1.0.jar
and target/example-1.0.one-jar.jar
.
A onejar plugin definition contains one value that we should usually define explicitly for
each different project: the mainClass
element. It defines the main class of the project, i.e.
the class from whose main
funtion the program execution will start. E.g. the above example
defined the project’s main class as ExampleMain
. This definition is
required in order to be able to execute the program simply as java -jar program.jar
, i.e.
without a separate parameter that specifies the class from whose main
function the program
execution should start. But note that a project does not have to include a main
function at all
if it is intended to be used as a class library.
If you use the onejar plugin without defining a main class, or if you want to start the program
execution from some other class of the project, it is possible to provide the main class with a
command line switch of form -Done-jar.main.class=MainClass
, where MainClass
is the main
class (including the package prefix, if the class has any). For example
java -Done-jar.main.class=com.example.SomeClass -jar program.jar
would start the program execution
from the class SomeClass
which is contained in the JAR file program.jar
and belongs to the
package com.example
.