As an open source enthusiast, I've used dozens (hundreds?) of libraries that are well beyond my skill or interest as a programmer. For a lot of people, including me, the available libraries are one of the things that makes a particular programming language worth using.
An open source library represents hours and days you don't have to work on a problem that's either not central to your project or, conversely, so central that your project would be otherwise out of reach. Better still, it's code you don't have to maintain—unless it's so important to you that you decide to contribute to it.
Because open source libraries are a vital component of open source programming, most programming languages have a convenient way to ensure they're easy to include in your codebase.
There are several ways to manage libraries in Java, but the one I use is Maven.
Maven is a code management framework that helps you keep track of dependencies, build targets, reporting, and documentation, all from a central location. That central location is a Project Object Model (POM) file, which is written in XML to describe the requirements and expectations of your project.
When you decide to update a library, you can update just your pom.xml
file.
Should you add a debug build to your project, add it as a target in pom.xml
.
When you upgrade to a new Java Virtual Machine (JVM), do it in pom.xml
.
If that sounds easy, you'll be pleased to know that it's just as easy to get started.
Install Maven
Maven is available on most Linux distributions from your package manager.
On Fedora, Mageia, and similar distributions:
$ sudo dnf install maven
On Elementary, Mint, and other Debian-based distributions:
$ sudo apt install maven
On macOS, use MacPorts or Homebrew.
On Windows, use Chocolatey.
Configuring
On most systems, Maven is already configured after installation.
If you have a complex Java setup, however, with different versions installed for different projects, you may need to set your JAVA_HOME
environment variable.
If you don't know what that is, read my article about finding and setting your JAVA_HOME.
First, verify that Maven is installed and is using the version of Java you intend:
$ mvn -version
Apache Maven x.y.z
Maven home: /usr/share/maven
Java version: X.Y.Z, vendor: Red Hat, Inc.,
runtime: /usr/lib/jvm/java-XX-openjdk-XX
Your first POM file
Maven bases everything it does on a project's POM file. You can think of pom.xml
as Maven's equivalent of Autotool's Makefile or CMake's CMakeLists.txt file. You put instructions that are meaningful to Maven in pom.xml
, and it executes those instructions when prompted.
A good IDE, like Eclipse or NetBeans, might provide a boilerplate pom.xml
file when creating a Maven project.
Depending on how comfortable you are in XML, this process might look more complex than it actually is. There's a distinct advantage to using XML here, so have a look even if you're not familiar with the language.
<?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.example</groupId>
<artifactId>example4Maven</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
The first elements (<?xml?>
and <project>
) contain information about the file itself, identifying the document as XML written in accordance with the Maven POM schema. The next group of elements corresponds to the Maven schema used and the project settings I provided to NetBeans.
The <packaging>
element specifies that this project is meant to be packaged as a JAR file.
The <properties>
element tells Maven about the (as-yet non-existent) source code in my project. It's written in UTF-8 encoding for OpenJDK version 11.
All of that was autogenerated by NetBeans for me when I created an example project.
If you don't use an IDE that integrates with Maven, or you simply prefer to write yours from scratch, you can find the skeleton outline of a POM file at maven.apache.org/pom.html#Quick_Overview. It's clearly commented and has the XML declarations already populated.
Adding a dependency to your POM
Long ago, when you wrote a Java application and decided to use an external library, you could download a JAR of the library, save it to your codebase, then use an import
statement to reference it in your Java code. It was a manual system, and while it worked as well as using, say, a C or C++ library, it could be a lot of work to maintain.
Maven uses repositories, much like Linux uses repositories for software installation, and Python uses pip. The default repository is Sonatype's Central Repository, which you can search from search.maven.org. From Central, you can find libraries and the appropriate metadata you need to fill in the required dependency fields for Maven. In practice, however, most Java libraries provide that information in their README files or documentation.
Suppose you have a simple Java script that lists files in a directory, but you decide you want it to list only files ending in jpg
or png
. The Apache fileUtils
library offers a filename filter for precisely that kind of operation, but it's not included in the Java distribution.
package com.example.example4maven;
import java.io.File;
import java.util.Iterator;
import java.util.List;
// external libs
import org.apache.commons.io.FileUtils;
public class Main {
private static final File myDir = new File("/home/tux/img");
private static final String[] ext = new String[] { "jpg","png" };
public static void main(String[] args) {
List<File> images = (List<File>) FileUtils.listFiles(myDir, ext, false);
for (Iterator<File> i = images.iterator(); i.hasNext();) {
System.out.println(i.next().getName());
}
}
}
You could search Central for the library, or you can visit the project's website and get the dependency info there.
Dependency information looks like this:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
Enter that between <dependencies>
and </dependencies>
tags in your pom.xml
file, and you've added the Apache commons.io
library to your 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>com.example</groupId>
<artifactId>example4Maven</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<type>jar</type>
</dependency>
</dependencies>
</project>
Now when you build your project, Maven can automatically download any libraries you don't have in your local codebase.
Build a JAR with Maven
You can create JAR files with tools like fastjar or gjar, but Maven has plugins that enable it to perform common build tasks, including packaging. For a standard JAR package, which contains your source code, you can use maven-jar-plugin
.
To have Maven create an "Uber JAR" for you, which contains your code plus all the dependent libraries your code requires, you can use maven-assembly-plugin
. The two plugins are similar in both purpose and configuration, and they're both build plugins, so they're entered into a <build>
section of your pom.xml
.
As with libraries, Maven metadata for plugins is available from the plugin's homepage. You include maven-assembly-plugin
by listing its groupId
, artifactId
, and version
between <plugin>
tags.
There are additional configuration options, including:
- a set of preset behaviors
- the ability to change the name of the output based on project name rather than your project's codename
- the ability to append your own entries into
MANIFEST.MF
.
This is useful when your main class isn't called Main.class
.
To tell the plugin when it's meant to be invoked, you must also define which keyword in your Maven build triggers it.
For maven-assembly-plugin
, you probably want it to activate when you use mvn package
.
<build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>Lister-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF \
</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Test your build:
$ mvn clean package
After your project builds, you can find your Uber JAR in the autogenerated targets
directory:
$ java -jar targets/Lister-1.0.jar
foo.png
foo.jpg
bar.jpg
bar.png
It works as expected.
Build with Maven
Maven makes managing your Java project easy and resilient. With Maven, contributors can bootstrap their environment quickly, getting all the libraries they need, and create predictable builds for their users. Its XML configuration keeps syntax simple and lintable, and it makes adding new options easy.
Try Maven with your next Java project and see what you think.
Comments are closed.