Skip to main content

Best Practices with Maven: OSS forks

Recently I came across a company that is forking several open source Java projects. I saw they were making a mistake that I also made a few years ago and have since learned from.

In Maven's distributed repository architecture project artifacts, like JAR files, are uniquely identified by a coordinate system composed of a group identifier, an artifact identifier, a version number, optionally a classifier, and a packaging type. For instance, the most recent version of the Apache Commons Lang project has a Maven coordinate (i.e.groupId:artifactId:version:classifier:type) of commons-lang:commons-lang:2.5::jar.

A few years ago, if I wanted to make custom changes to this project I would get the source, make my changes and then deploy the result to our private Nexus repository under a new groupId such as com.jaxzin.oss:commons-lang:2.5::jar. That might seem reasonable. Then a year later or so I tried something different and changed the artifactId like this commons-lang:commons-lang-jaxzin:2.5::jar.

Unfortunately there is a serious problem with both of these approaches. Maven supports transitive dependencies which means, if you include a dependency you get its dependencies 'for free'. But what happens when you depend on com.jaxzin.oss:commons-lang and indirectly include commons-lang:commons-lang? With either approach, Maven has lost all knowledge that these two artifacts are actually related. And when I say 'related' I mean they include different versions of the same classes. When Maven loses this relationship, it can't perform version conflict resolution and will include both versions in the output. It will compile against both in the classpath. If you are building a WAR file, it will include both in the WEB-INF/lib directory. If you are assembling or shading an "uber"jar, it will include the classes from both in your giant jar with all its dependencies. And unfortunately, the one that 'wins' is nearly indeterministic.

So what's the solution? How do you properly fork an open-source project privately?

The trick is to change the version, and leave the groupId and artifactId alone. That way, Maven still can detect the relationship and can perform version conflict resolution. So to complete the example I would fork Commons Lang 2.5 to a new coordinate commons-lang:commons-lang:2.5-jaxzin-1::jar.

Now I do have one further suggestion, but it's of questionable practice and I'm not sure how well it works. You might consider forking version 2.5 to version 2.6-jaxzin. This way, if Maven attempt to resolve version conflicts, it will know that your fork is 'newer/better' than 2.5. Maven sees version with qualifiers as being older than the unqualified version. I think the assumption is that if you are qualifying a version its a pre-release version like 1.0-alpha-1, 1.0-beta-1, or 1.0-rc-1. You can read more about how Maven version conflict resolution works and I know they have a major overhaul of this logic available in Maven 3.0 with the Mercury project.

But, in practice, when I've run into version conflicts like this I will add an exclusion clause where I depend on an artifact that is including the conflict transitively.

Comments

Popular posts from this blog

3D Photo Viewer for Looking Glass

The Looking Glass I created my first Chrome extension, which is now live on the Chrome Web Store ! It's built for the Looking Glass , a holographic display that let's you view three-dimensional objects without glasses. I've also opened the source to the extension on GitHub. The Chrome extension allows you to view Facebook's "3D Photos", a feature they added in 2018 for displaying photos that include a depth map like those from phones with dual cameras, such as Apple's "Portrait Mode". Getting Started To use the extension, connect your Looking Glass to your computer, navigate to Facebook and open the viewer from the extension's popup menu. This will open a browser window on the Looking Glass display's screen in fullscreen mode. Opening the Viewer Once the viewer is open, the extension watches for any 3D Photo files being downloaded, so browse around Facebook looking for 3D Photos.  I recommend some of the Facebook groups de

Simplifying logging with Maven and SLF4J

UPDATE: Ceki commented below which prompted me to rewrite the third paragraph. UPDATE 2: I have a better way of configuring Maven and SLF4J now. The mismatch between logging frameworks always seems to come up in projects I've developed over the years. Little-by-little I've learned and relearned how to navigate the nest of runtime logging that occurs in non-trivial applications. With my latest project I think I finally converged on a solution that I'll carry forward to future projects. So what am I really talking about? Have you ever been stumped, even for a short time, about where a certain log message is going and why it might not appear in your log? Often this happens when you are trying to debug an issue with a third-party library that's using a different logging implementation them your application. If you are nodding from familiarity, skip the next paragraph. Let's start from the beginning. There are several logging implementations available for Java, th

Simplifying logging with Maven and SLF4J (Part 2)

So in my  previous post  I explained how to simplify your logging with Maven and SLF4J. If you haven't read it yet, please do before reading more.  Since then I've discovered an easier and cleaner way to remove the secondary frameworks from your Maven dependency tree. Here's a revised overview of the steps: Decided which logging framework will be your primary, aka who will actually write to your log file. Define the dependency scope of all the secondary frameworks to be ' provided '. Configure your project to depend on drop-in replacements of each secondary framework from SLF4J. Define secondary frameworks as provided Use the dependencyManagement section for this. Its used when you might have a dependency transitively. Add dependency on SLF4J Add the following to your pom.xml Conclusion So now in only 3 steps you can redirect all your logging to your primary logging framework without changing a line of code!