Keeping Maven project files tidy
When I complained in my previous post about how hard it is to set up Java projects again and again, I felt a bit like I was crying over spilled milk. To make up for it, I've decided to share some rules for keeping Maven project files tidy that I've found work in practice. If you'd like to add tips of your own, I'd be glad to read them in the comments.
Dependencies
Scope
The first thing to think about for every dependency is its scope. If you're wondering where the hell all those dependencies in your WAR came from, the problem is often that they have the wrong scope. A secondary effect is bloated configuration for the WAR or Assembly plugin, which then has to remove those dependencies from the final binary distribution.
Use runtime scope for dependencies you need at runtime but not for compilation. Use test for dependencies needed only in tests. Use provided for dependencies the runtime environment supplies, such as the Servlet API. More about dependency scope.
One place for dependencies
Define all dependencies, including your own, in the dependencyManagement section of the central POM from which all other POMs inherit. That section doesn't say whether a dependency is used. If you don't have a central POM, add one. Always specify the default scope as well. This has several advantages, first of all that all dependencies are in one place.
In child POMs, refer to dependencies by groupId and artifactId alone. Changing a library version then means editing a single file. Another benefit is that you can pin the library version across all modules, including where it comes in as a transitive dependency. You won't end up with some other version. More about dependency management
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cz.sweb.pichlik</groupId>
<artifactId>example</artifactId>
<version>0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency>
<dependency> <groupId>cz.sweb.pichlik</groupId> <artifactId>example</artifactId> </dependency>
Shared dependencies
Shared dependencies are those defined in the dependencies section of the parent POM. I suggest steering well clear of them with three exceptions: logging, testing and validation of input arguments. That’s the minimal set all modules have in common.
Excluding dependencies
If you want to exclude an unnecessary dependency, typically one pulled in by a poorly defined third-party dependency, the best way is to define it in dependencyManagement with provided scope. Of course, only use this trick if nobody really needs the dependency.
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
Analyzing dependencies
To look more closely at dependencies, for example to see what your module depends on, use mvn dependency:tree. It shows where every dependency came from in text form.
If you want to know whether your module uses any dependencies it hasn’t declared, as can happen with transitive dependencies, or whether it declares any it doesn’t use, run mvn dependency:analyze -DignoreNonCompile. This command analyzes the bytecode directly, so the dependency must be in the local Maven cache. Just run mvn install -DskipTests.
Everyday practices
- If you use the name element in the POM, make it the same as artifactId. There’s a practical reason. Maven prints a summary by module after each command. In a multi-module project, if you leave name up to everyone’s imagination, it’s hard to work out which module the summary is referring to.
- A related good habit is to keep artifactId unique.
- Don’t check IDE-specific files into version control. Most Java IDEs worth mentioning can read POMs directly. IDE-specific files are usually tied to a particular location and IDE version and aren’t portable.
- If dependencies form between modules where they make no sense, often with unwanted dependencies turning up elsewhere, it’s because developers are reluctant or too lazy to make dedicated modules. A typical example is a module I call test-support. I put test support such as mock objects or specialized base classes there. That module then has test scope, so its dependencies don’t leak elsewhere.
Medium version · Archived original page
Related articles
Related CZ Podcast episodes
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio