Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I feel like Maven is doomed by its history. When Maven was created, expressing dependencies with version ranges was encouraged. But there was no lockfile concept, so that did not work well. Instead of adding lockfiles, they decided to leave the tool as it was but encourage people not to use dependency ranges. But dependency ranges are still supported, so you need a plugin to check your POM files and make sure you aren't using them. Dependency resolution is recursive, so indirect dependencies might still use version ranges, so you need a plugin to detect those ambiguities so you can pin those versions yourself in your own POM. If, despite your best efforts, you end up with an indirect dependency being specified with two different versions, Maven doesn't mind that at all (by default, if will package both, and leave it up to chance which one gets loaded at runtime)[0] so you need a plugin to detect that situation as well.

To sum up, we realized the default behavior was wrong over a decade ago, but rather than change the default behavior of the tool, maybe add a new version of POM files where sanity is the default (it's a versioned format! c'mon!) or a new non-XML file format that triggers a new mode of operation, they stuck with the legacy behavior, forcing every new project to include a bunch of XML boilerplate just to get the behavior that they realized should have been the default 15+ years ago.

That was the situation last time I used Maven, anyway. I'd love to hear that Maven has since added a new mode of operation that is sane by default, without needing extra plugins and configuration, and doesn't use XML.

I can't comment on Gradle. I've worked on a couple of Gradle projects, and the build files were a mess, but I don't know if they needed to be a mess or it was just bad luck.

[0] Actually I don't remember if the default is to pick a version at build time or to package both, but I know the latter is possible because I saw it cause many production issues before we configured a plugin to prevent it.




Also, Mavens metadata model is recursive. You want need a dependency? You're going to have to pull in the parent, which is probably an Uber pom. Take a look at your M2 cache, it's probably got a kubernetes pom even if you've never touched it before because your logging library uses an Uber pom...


Gradle can be quite good, but it's also really easy to make a completely unmaintainable Gradle file.


Gradle is expressive and extensible, but unfortunately, that encourages programmers to express themselves and extend it.


> expressing dependencies with version ranges was encouraged

In years of Java development, I've seen that only once, maybe twice.

And this was 8+ years ago.


The v1 release of Maven was in 2004. But it's unfortunate that Maven moved away from ranges. The "problem" with ranges was that they created non-reproducible builds, because different dependency resolutions could change the build. Every new release of a third-party dependency had the potential to invalidate a tagged and tested version of your application.

Other build tools in other languages decided to use lockfiles to achieve stable builds with dependency ranges. If your application depends on libraries A and B, and A and B depend on library C, then the build tool can check that the lockfile specifies a version of C that fits the ranges specified by A and B, or, if library C isn't in the lock file, find an appropriate version of library C and add it to the lock file.

But not Maven. Maven's solution is for A and B to declare dependencies on exact versions of library C, and then to pick one or the other depending on many degrees of transitivity separate your project from A or B. Seriously:

"Maven picks the 'nearest definition'. That is, it uses the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins." [0]

So if A depends on C 1.2 and B depends on C 1.5, and A appears before B in your pom file, Maven will bundle 1.2 with your application and not fail the build.

I don't know the history of how anybody ever thought that was a good idea. Anyway, they quickly realized that pinning versions in the build was the right solution after all, but instead of adding a separate lockfile, they decided to make you list all the versions in the project file itself. Which is exactly where you want all your transitive dependencies listed, right in your build file, taking up half a dozen lines each because it's XML, right?

Of course Maven is still happy to fall back its "pick the first nearest" algorithm if you fail to pin one of your transitive dependencies, which means your builds might not be reproducible. My boss (quite sensibly) said our builds had to be reproducible no matter what Maven allowed or encouraged, so I had to write a plugin to check for that.

The real tragedy is that because library publishers no longer use dependency ranges, you get to debug and discover violations of semver yourself. Does library A, which specifies C 1.2, also work with C 1.5? The publishers of library A might know that it doesn't, but they don't publish that fact with their library. Jackson plugins were especially prone to semver-unexpected breakage because Jackson didn't have any stable API for plugins. Jackson plugins typically had to use private implementation details of Jackson to work at all, so they sometimes broke on patch releases of Jackson. Library publishers could have encoded knowledge about this kind of breakage in their dependency declarations, but "best practices" said to specify a single version, so that's what they did.

[0] https://maven.apache.org/guides/introduction/introduction-to...




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: