I’ve gone all in on them these days, the feedback from sonarlint in the IDE is usually useful.
To be fair, the language needs that tooling to be practical, and the quantity of it isn't helped by its long history so it isn't necessarily the tool makers that are to blame.
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.
In years of Java development, I've seen that only once, maybe twice.
And this was 8+ years ago.
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...
Libraries don't declare version ranges for their transitive dependencies (like they do e.g. in Ruby), instead they just depend on a specific version. Of course, because two different libraries may depend on the same subdependency but in different versions, you will just get one version and you have no way of knowing whether it will work correctly.
That isn't a problem in 90% of cases, but sometimes it is and you'll notice it when you're suddenly getting ClassNotFound exceptions.
There are some solutions to this, e.g. bigger frameworks like Spring publish BOMs, which are just sets of library versions known to work together, but they don't cover everything.
That's the behavior that Maven itself recommends, so Maven is at least part of the problem.
Maven could also sanity-check your build for these issues by default, but it doesn't; it leaves that up to plugins, which you have to configure (in XML) in your build file every time you set up a new project.
You can do so much with Gradle, but 90% of learning it is figuring out what knobs you need to twist; there's a lot of feature overlap and second-system-effect going on.