Although I am a Maven fanatic, that doesn’t mean I don’t get mad because of some missing features in Maven. One of those is general exclusion of dependencies.
Most projects still use either log4j or commons-logging for their logging needs. While there is nothing wrong with that, I prefer SLF4J. SLF4J also has these really nice adapters for libraries that use those other logging frameworks so everything gets logged through it anyway. And here lies the problem. When you want to use these adapter classes, you need to exclude the real log4j and commons logging libraries. When you only have like 3 dependencies, that’s not a big issue. If you have 50+, it is. So you’re left with 2 choices:
- Exclude log4j and commons logging from every dependency (you’ll need to look at the POM to see which one it uses)
- Use the somewhat ugly, but effective version 99-does-not-exist fix
When you have a corporate super POM, I would urge you to use the first option. But if you want a quick solution, use the latter. The version 99 fix works like this: you create a maven repo proxy that delivers only version 99-does-not-exist artifacts, being minimal POMs and (almost) empty JARs. Creating such a repo proxy is easy. I created a servlet for this:
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
import org.apache.commons.lang.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* This is a servlet mimicking a Maven 2 repository. It serves jar and POM's for any artifact in version
* 99.0-does-not-exist. The use of this servlet is when you want to exclude a jar throughout all transitive dependencies.
* For example, when using SLF4J, you'd be using the 99 version to avoid having to exclude commons-logging and log4j from
* each of your dependencies.
*
* @author Lieven DOCLO
*/
public class Version99MavenRepositoryServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
ExecutorService service = Executors.newCachedThreadPool();
Future future = service.submit(new Version99Request(req, resp));
while (!future.isDone()) {}
}
public static class Version99Request implements Runnable {
private HttpServletRequest request;
private HttpServletResponse response;
public Version99Request(final HttpServletRequest request, final HttpServletResponse response) {
this.request = request;
this.response = response;
}
@Override
public void run() {
try {
String path = request.getPathInfo();
List<String> pathList = Arrays.asList(path.split("/"));
String groupId = StringUtils.join(pathList.subList(1, pathList.size() - 3), ".");
String artifactId = pathList.get(pathList.size() - 3);
String version = pathList.get(pathList.size() - 2);
String requestedFile = pathList.get(pathList.size() - 1);
// we're requesting a version 99.0-does-not-exist artifact
if (version.equals("99.0-does-not-exist")) {
// we're requesting a jar file
if (requestedFile.endsWith(".jar")) {
deliverJar(groupId, artifactId, version, response);
}
// we're requesting a POM
else if (requestedFile.endsWith(".pom")) {
deliverPom(groupId, artifactId, version, response);
}
// we're requesting something else, send a 404 as we cannot handle this
else {
response.sendError(404);
}
} else {
// this servlet can only handle version 99.0-does-not-exist
response.sendError(404);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void deliverPom(final String groupId, final String artifactId, final String version, final HttpServletResponse resp) throws IOException {
String minimalPom = "<project>\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
" <groupId>" + groupId + "</groupId>\n" +
" <artifactId>" + artifactId + "</artifactId>\n" +
" <version>" + version + "</version>\n" +
"</project>";
resp.setContentType("application/xml");
resp.setCharacterEncoding("UTF-8");
resp.getOutputStream().print(minimalPom);
}
private void deliverJar(final String groupId, final String artifactId, final String version, final HttpServletResponse resp) throws IOException {
resp.setContentType("application/java-archive");
JarArchiveOutputStream os = new JarArchiveOutputStream(resp.getOutputStream());
JarArchiveEntry entry = new JarArchiveEntry("VERSION99");
os.putArchiveEntry(entry);
os.closeArchiveEntry();
os.close();
}
}
}
You simply deploy this to, for example, a Tomcat instance, so the repo is located at http://host:port/warfile/version99 (assuming the servlet mapping is set to version99/*).
Now you can simply use this repository in your POM like this:
<repository> <id>version99</id> <url>http://host:port/warfile/version99</url> <name>Version 99 Repository</name> </repository>
For general exclusion of (the contents of) a jar, in this case the unwanted logging frameworks, you simply put this in your dependency management.
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
Maven will override the jars pulled in transitively by your dependencies, as the version you defined is higher than the ones defined (unless the version is higher than 99, but I have yet to see a jar like that in the maven repo). The jar will be in the classpath (so it’s not really excluded), but since it’s an empty JAR (except for a VERSION99 file, otherwise the site plugin will freak out), it’s of no value. It may not be the prettiest solution, but it sure works…
Update
Another way is to put this in your dependencies:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>provided</scope>
</dependency>
...
Which will exclude the dependency at runtime, but it will however show up in your IDE… When you come to think of it, this approach actually makes sense, as the log4j API is “provided” by the SLF4J adapter.

