- COMP.CS.140
- 8. Packages
- 8.3 Managing a Java code project with Maven
Managing a Java code project with Maven¶
We will now move away from basic features of Java towards higher level tools for implementing and maintaining Java programs. We will start by considering a tool for managing the build process and external library dependencies of a Java project. There are several such tools. We have chosen to focus on a tool called Maven, which is probably the most popular Java project build management tool. Maven allows us to e.g. define how thee project should be compiled and what kind of external libraries it needs. Another especially recently quite popular such 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.
Netbeans readily contains Maven, but you may also install Maven separately by e.g. following the
instructions provided on 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
issuing the command mvn
on 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
subdirectory src
. Java source files actually reside in subdirectory src/main/java
in a
usual subdirectory hierarchy based on packages. E.g. a code file SomeClass.java
defined to be
in the package com.example
would have file path src/main/java/com/example/SomeClass.java
.
Like the file suffix suggests, the project file pom.xml
describes the project properties in 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 end 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 (in a bit similar manner how packages group code files).
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 and 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. The end also includes a properties
element that declares that source files use UTF-8 character encoding, Java source files are
compatible with Java version 17, and that the Java compiler will generate class files compatible
with 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).
If you create a new Java with Maven/Java Application project in Netbeans, it will automatically create a project file that is quite similar to the example file given above. You can find the project file e.g. as an entry under the “Project files” folder of the project view pane in the left side of the Netbeans main window.
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 listed phases are always executed in
succession in such manner that executing a phase entails executing also 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
. Java
class filess will follow the usual directory hierarchy based on their packages. 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
, also it will 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 sigle 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 jar
that can package already compiled
class files etc. into a JAR file. We will not discuss this program further. 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
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 from all its internal directories and additionally from the JAR file
library.jar
located in the directory /some/directory
. A class path definition can incude
also several paths and/or JAR files by separating them with colon (Linux, Mac) or semicolon
(Windows) characters. E.g. on Linux the execution
java -cp /another/directory/:.:/some/directory/library.jar MyClass
would direct the Java
virtual machine to search class files from the directory /another/directory
, the current
working directory .
, and the JAR file library.jar
in the directory /some/directory
.
Fetching external libraries with Maven¶
One of the leading motives for using Maven is its capability to fetch external class libraries from
the internet. Java’s own class libraries are extensive but naturally cannot cover all our potential
needs. If there exists an external Java library that offers some functionality we would like to
use, it is higly likely that the library can be found from Maven’s central repository that provides
access to even millions of libraries. If this is the case, then we can import the library into a
maven project by simply adding a corresponding dependency definition into the project file
pom.xml
. When we then build the project, Maven will automatically download the defined package
from the internet and the project can then use its classes and interfaces in a very similar manner
as how Java’s own class libraries are used.
A little further below is an example project file that contains a dependency definition: the project
file now contains also a dependencies
element that in turn contains dependency
elements that
list the dependencies of the project. The example below defines only one dependency: the jdom2
library that offers tools for processing XML data. A dependency definition specifies the
groupId
, artifactId
and version
of the library. These three values allow Maven to
identify the exact library and version that should be fetched. This also is the reason why all
Maven projects have to specify these three values: if our own project would be published as a
library in Maven’s central repository, end users could import our library by specifying the three
values groupId
, artifactId
and version
in a dependency definition in their own Maven
project files.
Maven central repository
You may wonder how we can know what dependency definition values should be used in order to import a certain library? Quite often the documentation of the library (which we perhaps found via an internet search engine etc.) describes the required Maven dependency definition. In any case we can also search the library from Maven’s central repository, which offers a search page https://search.maven.org/. The central repository e.g. lists different available library versions, and the information for a certain version also describes the corresponding Maven dependency definition.
<?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>
<dependencies>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>fi.tuni.prog3.example.ExampleMain</mainClass>
<onejarVersion>0.97</onejarVersion>
<attachToBuild>true</attachToBuild>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Importing jdom2 as a dependency is not the only new addition in the above example: it now also
includes a build
element that contains definitions of plugins that we want Maven to use during
the build process. The example includes one such plugin
element, which sets the project to use
a plugin called onejar. This 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 part “one-jar” into the default JAR file
name. E.g. if we would use the preceding 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, that
is, the class from whose main
funtion program execution will start. E.g. the above example
defined the project’s main class as fi.tuni.prog3.example.ExampleMain
. This definition is
required in order to be able to execute the program simply as java -jar program.jar
, that is,
without a separate parameter that specifies the class from whose main
function 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 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 program execution
from the class SomeClass
that is contained in the JAR file program.jar
and belongs to the
package com.example
.