Hacker News new | past | comments | ask | show | jobs | submit login
Modern Java/JVM Build Practices (github.com/binkley)
161 points by djha-skin 9 months ago | hide | past | favorite | 106 comments



I formerly used gradle extensively, and ultimately concluded it's chalk full of footguns. Since any step can do anything it wants, things easily become a mess. Extremely flexible, and prone to slow or problematic stuff making it into build.gradle. It's too generic-generic and meta.

New major gradle releases tend to break a lot of stuff which is a lot of work for the full-time expert build engineer you're going to need on hand to maintain the thing for medium or large sized projects.

Now I prefer to stick with Maven, it's simpler, safer, super predictable, and gets the job done just fine. Actually, the job is done better because it involves less headaches and time wasted on figuring out the damn build system.

Case in point: The length of the README in this "Modern gradle" reference repo - that's a lot of complexity just to get dependencies, compilation, tests, and packaging. I prefer to copy-pasta some XML fragments and move on to solving the high ROI business-value issues.

Edit: @bcrosby95 sir, if it were Maven only, you could shrink the document by ~75%, because most of the bullet points are trying to explain the sharp edges of.. gradle.

All my love to all of you folks, goodnight!


> Now I prefer to stick with Maven

yep. This is the conclusion i end up with as well.

There's three stages to maven (and generally, build systems too). First, you use a tool like maven, and find that it sucks, because it didn't do exactly what you wanted. Then, second is you find a new tool to do _exactly_ what you wanted, which is usually something similar to make or ant (and gradle, in this case). It works, and you think you've reached nirvana. Then, third stage is you find out how much maintenance, headaches and gotchas you hit after using that tool. You curse and finally, having had enough, you switch back to maven (or use maven for a different/next project).

Now every time i see a build too in java that's not maven, i sigh. But only people who have reached stage three above truly understand, and accept maven, and the rest are always itching to move to something that "works better".


Snap! You describe my experience exactly. Funny thing is I was originally on ant so I've moved before when I found a real improvement. Gradle wasn't any better than maven.


Exactly my path as well. Was on ant, ant got cumbersome to maintain especially in big projects. Maven came up, "oh let's try that"! "But it's so painfully restrictive!", tried Gradle, eventually noticed that it's the same gunk as ant, just looking less like "programming in XML". Then ended up diving deep enough into Maven to learn how to make it behave and do what I want it to do.

Nowadays, in the most extreme case I write a new Maven plugin. But in 99% of cases I can come up with a solution using existing stuff. It's not always the straightforward solution, but in the long term it's usually better to maintain than what I'd cooked up with an imperative system like Gradle.


Me too. From Ant to Maven long ago. In the beginning it involved still quite some work, making plugins, running Nexus. But since Spring Boot, Maven is for me just a copy/paste activity.


Gradle is weird. Like if you use it correctly, you can do some genuinely impressive things with it. I've got Marginalia's code base set up as a multi-modular build which has an average compile+test time (in use) of 4-5s (as opposed to several minutes for a clean build). That brings a lot of value.

That's what it can do, and it's honestly pretty neat.

Now using Gradle, on the other hand, is a serious pain in the ass for all the reasons you listed. Breaking changes every release, weird dependence on JVM versions despite Java being a very backward compatible language, error messages that make gcc's c++ errors in 2007 look like the model of clarity, it's way up there with the most frustrating software I'm aware of.

Strap me to a chair and slowly pull out my fingernails, you're never getting me to admit I know anything about Gradle in a professional setting. I don't want to be the Gradle guy anymore than I want to be the guy who knows how to fix your broken Jenkins pipelines.


It’s gratifying to read so many people come to the same conclusion about gradle in this thread. There was an article a long time ago that made the case that most all build tools conflate two basic things; dependency management and compilation. Wish I had a link; maybe someone here is familiar? It explained the situation well.

> you're never getting me to admit I know anything about Gradle in a professional setting. I don't want to be the Gradle guy anymore than I want to be the guy who knows how to fix your broken Jenkins pipelines.

I feel this.


A big part of the problem is just the instability of the API.

If it was more stable, it would be possible to use e.g. stackoverflow to build a base of solutions to common problems, a canonical way of doing things, but since gradle keeps shifting and changing with every other release, that really isn't possible.


You can search "how to do X Gradle" and come away with a dozen implementations. There often seems to be at least 3 different APIs to accomplish a simple task, one ancient that's deprecated but kept around for compatibility reasons, one "new' that the Gradle devs later decided was bad, and then the proper "new" one which doesn't have all the features that people needed.


I have to agree. The moment I laid eyes on Gradle, I knew the Java ecosystem was going to continue its decline by the community’s adopting it as the new defacto standard. I have never adopted Gradle and have only used maven in all my projects.

There is a point I want to make about the excessive complexity of projects funded by Enterprise support/training revenue models. However, I cannot do so because I have no good data to back up any claims I want make.

If someone has the data, please enlighten us.


> I knew the Java ecosystem was going to continue its decline

Has it?


I always felt that Gradle combined all the negatives of ant and maven in one convenient package.


Gradle's unconstrained and unstructured scripts are a major step downward compared to the predictable XML of Ant and Maven.

For example, I have a very standard "build.gradle" for a React Native app that, in about 60 lines,

  - defines debug, release etc. build variants
  - sets SDK versions (to values from elsewhere)
  - contains passwords in plain text (for Java key stores)
  - sets the name of the app
  - conditionally chooses arcane linking instructions
  - includes freshly downloaded stuff from node_modules
It's a classic instance of "they were so excited that they could, that they didn't stop to think whether they should".


That is a great way to put it.


The longer I'm in the industry, the more I hate build systems that use languages.

Build systems should be declarative rather than imperative. If you need complex stuff doing, it should be done outside of the build system. make was sufficient (subject to a few minor changes).

Rust - cargo.toml. That removes an entire tranche of problems with the language. Versus typical google queries - C++ "modern cmake" or java "how do I create a fat jar".

No developer should have to spend their time messing around with build systems.


Interestingly, the README's author seems to agree with you, at least in part[1].

[1] https://blog.frankel.ch/final-take-gradle/


I originally hated Gradle due to its sheer complexity, but there was an incident that I was put off by Maven. I realized that even with the simplest configuration possible, Maven tried to re-create a `.jar` file on `mvn package` every time even when I didn't change any files. Maven is famous for not utilizing incremental builds well and this shows. The strange thing is that incremental builds in Maven sometimes work. I could not find the rules behind this. These unpredictable behaviors make me nervous when I use Maven. Gradle is extremely complex and its behavior changes between versions, which makes my life harder, but at least at a specific version, Gradle's behavior has been stable. Just my two cents.


Maven’s added incremental builds as a full feature in recent versions and it seems to work pretty well.


Nice. This is literally the one reason I preferred gradle to maven. For our bigger projects maven simply took too much time.


Incremental builds and test caching (plus that nifty UI) were always two things that made me strongly consider switching to Gradle.


There are only two reasons to use Gradle.

Android ecosystem, and those that failed to learn from Ant.


> Case in point: The length of the README in this "Modern gradle" reference repo - that's a lot of complexity just to get dependencies, compilation, tests, and packaging. I prefer to copy-pasta some XML fragments and move on to solving the high ROI business-value issues.

The repo is a reference Gradle and Maven repo. The author, in his linked opinion piece, agrees with you:

> Gradle brought the two above great features that Maven integrated, proving that competition is good. Despite this, I still find no benefit of Gradle.

It looks like most people commenting here, would be better off reading that article and commenting. I would (like another commenter) like to see a comparison to bazel as well - and maybe a discussion of how/if Maven could be reasonably used with Kotlin/android...

https://blog.frankel.ch/final-take-gradle/


I prefer Gradle but I do agree with this assessment. Maven is really rock solid and dependable and once you have a POM that does what you want it will keep doing that for the next decade.

I think if you have a project in an enterprise setting where you want a dependable build on a codebase that has a lot of people with relatively low investment contributing then you either use Maven or you have to be very strict and disciplined with how you setup your Gradle build(s).

BUT. For myself I wouldn't trade Gradle for the world. I can setup my projects to develop in a live coding style even though I am working in a compiled language.

Right now I am hacking on a Kotlin Spring Boot project with JOOQ and HTMX and I have set it up so that I can use JOOQ code in buildSrc[1] to output SQL that is then used by JOOQ codegen[2] to generate Records, POJOS, DAOs and so on. I just run 'gradle build -t' and my and 'gradle run' in parallel and because of Spring Hotreload and using the LiveReload Chrome extension I can see my changes taking effect in almost real time. When I make a change that leads to a compile error then my build script will give the Star Trek computer error chirp. I don't think I can convey how happy and productive this setup makes me.

It would be extremely hard to do all of that in maven but it's so simple in gradle. And if you do it right, which is not hard if you follow the docs, then you automatically have dirty checking / lazy behavior. SQL is only generated when buildSrc changes. JOOQ artifacts are only generated when the SQL changes. Spring only reloads the application when code or resources change. Everything is quite fast too. For my project it's currently around 3 seconds from changing the code to seeing my changes reflected on the frontend. And everything from the DDL (schema definition) to DML (sql queries), backend and frontend is in my favorite language: Kotlin!

I hope that everyone's build tool makes them as happy as gradle does me.

[1] buildSrc let's you write custom Groovy/Kotlin/Java build tasks that you can then use in your project's build

[2] with the artifacts that JOOQ codegen generates it's possible to use SQL in a fully typesafe, database independent and refactorable way. Here is a good one page overview of how to set it up and what you can do with it: https://www.baeldung.com/kotlin/jooq


Definitely not happy with gradle, but using gradle.build.kts makes it at least somewhat bearable.


Unfortunately, maven can get into a state where it will fail to be correct without a clear step first. This never happens with gradle (unless you almost deliberately break something), and correctness is above all else.

I often end up defending gradle because I do think that people fail to understand it still (which is partially on gradle, I agree).

A build system is basically “just” creating a directed graph. If we want to get to target node X, you just execute every transitive dependency of it. To make it fast, you do it in parallel, and only do work that is not currently up-to-date. Gradle is really good at these “behind-the-scene” steps.

The imperative part is only about creating this graph. You can use some imperative code to generate this graph, but otherwise it is completely static, after config phase.


It's not just a gradle reference repo. It's also a maven reference repo. Your "case in point" makes no sense.

Edit: most of the readme has nothing to do with either maven or gradle, but the tools and principles both of them are using.


I'm still with Gradle, but my feeling is the same. Gradle comes with some very good things, but the typical Gradle configuration is an insane mess!

However, not everything is magically better with Maven. It is slower (although the Maven daemon is an improvement), the XML config is a bit verbose and modules are not as straightforward as with Gradle. But the convention over configuration approach (without exceptions like in Gradle) makes maintenance way easier.


It is as if those pushing for Gradle never got the point of why Maven over Ant.


The reference repo even refers to this in the Grade Vs Maven link at the top.

Completely agree with your sentiments, it reminds me of a similar topic that was here on CI tools, and that everyone has a phase of finding their usecase not covered, switching to Jenkins to write their own Groovy workflow and then realising the footguns they've unleashed.


I feel the same. Gradle seems to be more powerful and flexible but it becomes harder to maintain specially if you have several projects since you cando things in different ways the projects end up with different config styles. With Gradle you need to commit to be an expert. Maven in the other hand there is generally only one way and it is easier since projects tend to be more homogeneous.


And with Polyglot Maven [1] you don't even have to deal with all the XML markup either.

[1] - https://github.com/takari/polyglot-maven


That is dead afaik. Unfortunate, the Zoomer loves to hate on XML.


For the same reason, I refuse to start another SBT project. Scala is fine, but only through the Maven Scala compiler plugin.


Try Mill!


I took a look but I don’t think I’m the target audience. I’m happy with Maven. All I’m doing is importing Java dependencies to my Scala apps. I’m not going all in on the Scala library and build ecosystem.


Spring Boot recently made Gradle the default build platform. Mad!


My suggestion is:

1. Use maven.

2. If you use spring, use spring boot parent, you don't really need to read the rest.

3. By default maven uses ancient plugin versions (for reasons I don't understand). So you have to define every single plugin you're using with latest version, including standard ones like maven-compiler-plugin.

4. Do not specify versions in <version> tag. Use properties for every single dependency and plugin.

5. Use "versions" maven plugin, it supports properties.

6. Learn maven, it's tiny. Learn about <dependencyManagement>, <pluginManagement> tags.

7. Do not use multi-project mono-repo setups.

8. If you really want to use multi-project setup, use Gradle. It sucks, but maven multi project configuration is hardly usable.


> Use properties for every single dependency and plugin.

I see this a lot, but... why? Feels like putting far apart information that should be together (name of a dependency and its version).

It makes sense I guess when multiple artifacts have the same version, like a number of spring deps will all have the same verson, and then you only need to change it in one place, and you are certain they are synchronized. But when there is only one thing with its own version number, why define a property and put its value in a different place in the file?


The reason is that a sane format for dependencies (i.e. not XML) would be something like:

    "dependency-x": "1.0"
    "dependency-y": "3.2.1"
This is basically what Cargo.toml, package.json, even requirements.txt give you.

Compare that to the 6 lines minimum that each maven dependency is, plus the fact that you sometimes need to specify the same dependency again in the <dependencyManagement> section for what other languages's tools do with lockfiles, which could be several pages away because of the aforementioned bloat. So it ends up being a pain to find the version information, and to update it.

Then you add in the multi-module dependencies, where you're supposed to keep the versions in sync. Sure, there's parent POMs to help with this, but you can only use one at a time, and ultimately someone still needs to maintain the parent POM.

So then rather than specify the same version number 8 or so times in very distant locations, people put a bunch of properties with version numbers to simulate a less clunky dependency specification format.


You don't have to define the version multiple times. If you've specified it in dependencyManagement, you don't have to add a <version> under <dependencies>. If you do it in the parent POM, you won't have to specify versions in the child POMs either.

The only time you have to repeat the versions of dependencies is when there are related dependencies that should be the same version. E.g. dependencies with separate packages for modules, like Jackson or Spring. And if the dependency has a BOM pom, that's not needed either.


Because versions plugin works with this format perfectly. You just type something like `mvn versions:display-property-updates` and get the full list of all updated dependencies and plugins. I can't remember why exactly it doesn't work this way with inline version strings, but I remember that there was a reason I started to use this approach (and I generally prefer not to introduce unnecessary complexity).

Another reason is uniformity. You'll inevitably have some group of dependencies with identical version, so it certainly makes sense to move that info to the property. And then you'll have your versions information spread around. Not very nice.


Never understood this either, it increases complexity at a seemingly little to no benefit.

Many collection of artifacts that need to have the same version provide a BOM to define the version in a single place. And you can always use local variables anyway


Agree. Also, I wish maven allowed dependencies in one line. I am fine with using XML.


I'm a fan of Gradle, it is super powerful and ultimately flexible and I can code my way into and out of trouble with it.

I like maven for its simplicity, although I do dislike XML for the build configuration language, seems to verbose and outdated. Mavens plugins are elsewhere (eg published jars), but with gradle I can also specialize and have local plugins kept with the code as well as use published plugins. I like that flexibility.

All said, Gradle has changed a lot of over the years and with that its best practices, so many of my projects are way behind the new standard way of doing things. But with either tool once its all up and running maintaining it is relatively simple.

Perhaps the new Amper project by Jetbrains (Gradle configuration via YAML) will simplify things for the 95% of cases and still allow users to extend and fall back to kotlin / groovy configuration where specialization is needed.


Oh no, more yaml? It must be strictly worse than xml and groovy?

Who are these people that enjoy yaml, and why? It's so easy to get indentation and lists wrong (yes, with editor support).


I like yaml. It's easy and readable. It needs a linter, of course, or you end up with unescaped strings and whatnot, but it does its job well. Plus, for the strangely passionate yaml-haters out there, the fact you can feed any modern yaml parser JSON and still make it work is a benefit for those that want to avoid yaml at all costs.

I like XML as well, especially if combined with a clear schema so it's easy to write correct markup.

I can't say I've ever used Groovy. It seems like Kotlin's Gradle DSL has completely replaced it in practice, so I can't really comment on it.

Every configuration format has its pros and cons. It all depends on what you're using it for. I'm not a fan of the endless unstructured yaml in Kubernetes (I'd much rather have something that can be schema checked easily for config that huge) but I wouldn't use Groovy for that either.


> I'm not a fan of the endless unstructured yaml in Kubernetes (I'd much rather have something that can be schema checked easily for config that huge) but I wouldn't use Groovy for that either.

Wait, there's yaml with schema support? Do you have an example on hand?

Ed: > I like yaml. It's easy and readable.

I humbly disagree that deeply nested yaml is easy to read (and write) Kubernetes is awful - but so can complex docker compose files be.

> the fact you can feed any modern yaml parser JSON and still make it work is a benefit for those that want to avoid yaml at all costs.

Not really - JSON is a little easier to edit, but doesn't support comments - they're both pretty bad.

Even something bespoke like open tofu/terraform is better to work with IMNHO.


I hadn't heard about Amper yet, it seems quite neat. Thanks!


It is very rarely known, but I have to vouch for Mill. It is a very well thought out generic build tool written in scala - but it is pretty much as simple as a build tool can be, but not simpler. That is, it does parallelize tasks and properly caches.

You basically just write any imperative code, and if you refer to your other functions from within, they will automagically be added as an input for that function (this is possible through scala metaprogramming magic, but you are never exposed to that).

The output of your function will be serialized and cached. That’s it.


For everyone (predictably) discussing Gradle vs Maven vs x, I recommend the (linked) article/opinion piece by the author:

https://blog.frankel.ch/final-take-gradle/


Honestly Bazel and Java is actually a really nice experience. Pull in third party stuff from https://github.com/bazelbuild/rules_jvm_external. There is built in support for uber jar builds by adding a _deploy.jar suffix to binary jars. Protobuf and other codegen is super easy!


Here is an example project that even downloads the right JDK automatically for you: https://github.com/rockwotj/SyllabusDB


You should always configure Bazel this way, i.e `remotejdk`. This helps with portability between machines for one but also helps you be ready for things like Remote Build Execution which is where Bazel really has an advantage over other build systems which aren't natively distributed aware.


+1

Bazel and monorepo are a bit exotic to most enterprise Java devs, but now that I've experienced it, I'll never go back to Gradle or Maven or sbt.


maven can do monorepos pretty easily i thought. The potential benefit of bazel is the better level of cache compiled artifacts that maven doesn't seem to handle very well (and so you almost always end up recompiling everything just in case...)


There is still a bit of work to be done to make it really nice and rules_kotlin tends to lag a bit behind if Kotlin is your poison.

One area that needs work that I haven't had time to invest more in yet is a proper split layer OCI image build with rules_oci similar to what Jib does out of the box. Right now the example in the repo just dumps in the _deploy.jar which works but results in one fat layer that has to be pushed every time even if only app code changed.

Overall though I think Bazel is awesome.

I still end up using Gradle but try not to use it for projects other people need to work on, Gradle's comprehensibility cliff is worse than Bazel IMO and the more constrained nature of Bazel keeps people from making too bad of a mess.


I tried it once but didn't have much joy using bazel under MacOS - has this changed? I've never had an issue with maven on non-linux platforms which is what you come to expect with the JVM ecosystem.

Although I've limited experience, I actually much prefer the overall bazel approach/design than that of maven. That experience was with a large C++ project but it only required building on Linux. However I've much more maven experience so can always figure my way out of maven issues - while I am much more inclined to just give up when I get bazel build errors.


Should this be renamed to "Modern Gradle/Maven Build Practices"? Many ways to build Java projects, and implying Gradle is the modern way feels rather opinionated.


It seems to be the contemporary way for Android projects?


Would be nice to show how to handle multimodule projects. Maven does that out of the box but Gradle is rather complicated (too many ways).

Another idea: publish as inheritable POM for Maven and Gradle convention plugin so that people don't need to copy all that boilerplate (with some refactoring to make it reusable)


It's not trivial to get right. People easily misunderstand the purpose of multimodule projects and parent POMs. And before you know it, your team is maintaining versions of all dependencies in a released parent, and all modules need to be updated at the same time when there's a new version of the parent. Yey, a distributed monolith. Or something like that. Perhaps make a simple parent POM that defines long-term truths for your organization (where your Maven repository is, maybe some very well-established build configuration, and zero to none dependencies).

(And if you want to manage dependencies a bit more strictly, like everywhere in oop: composition over inheritence.)


Meta question: why is Java build process so complicated? It really shouldn’t be, linking (the hardest part) is done by JVM at runtime, all you need is compile .java files to .class files (which you can do 1-by-1) and put them in a single .jar archive.


In simple projects it's fundamentally what you describe. But there are some fancy libraries which e.g. require bytecode weaving (some AOP libraries, JPA implementations) or generate code from annotations.

The other thing is that maven and gradle aren't just pure build tools, but they serve other roles too - chiefly dependency management, they are test runners, generate reports from those tests, can upload the generated JARs into artifact repositories, run the built application etc.


There are some other things. What you have described would work if jar has no dependencies (or is used as a library). When you build a deployable application you need stuff like:

- building fat jar (jar with all dependencies, including transitive ones) or even whole docker image or just deploying the jar you created to the maven repository

- unit tests

- integration tests, with things like starting database

- code coverage

- dependency checks (version clashes of transitive dependencies, security issues)

- code quality checks

Additionally it is nice if you can do following through maven CLI:

- update major/minor/patch version

- deploy application to chosen environment

- create javadocs for the project

Above things are done through plugins, which release versions independently. Additionally plugins might have dependencies - for example maven-checkstyle-plugin depend on checkstyle checkstyle library - you might want to override checkstyle library version that is used by the plugin. Some plugins' default configuration might also not work nicely with each other - you need to update those.

On top of that maven is using XML and on top of that maven creators chosen to make it even more verbose by ditching XML attributes. That way you end with really big pom.xml files (~3 times bigger than gradle file in this project).


Can we say that JS ecosystem did it right by splitting package management, bundling and testing into separate tools?


Bundling and testing in maven is already done by separate tools. It is just that maven have some defaults (dependent on the packaging selected) which tools may cover such tasks.

I think NPM made one step too far - it removed whole build structure from the build tool. NPM is just package manager and simple run utility (both based on horrible package.json configuration, even maven's verbose XML pom.xml are better). There is no compilation step, verification step etc. - there are just cli commands you can preconfigure in package.json.


Not if you go by the number of blog posts complaining about NPM and bundling in javascript


you can do that with a script or a makefile...really maven, ant, or gradle shine when you have to test, build for development/production, deploy, source control, dependency control, and what have yous.


I still use Ant - but with one key trick and a bit of discpline.

The key trick is to reuse common tasks between different projects by importing them. Originally we did this using XML entities.

https://ant.apache.org/faq.html#xml-entity-include

Eventually the Ant import task came along and we now use that.

That way the project specific stuff stays very small, all projects have the same set of commands - but you can still customise relatively easily if you really need to.

I find it funny when people blame the messy complexity of some build script on the tool rather than the people who built it...


Something that occurred to me recently: It probably isn't just me who hasn't kept up to date in how java projects are structured nowadays, let alone python projects. So many open-source projects seem to assume that you know all this, even though it changes yearly and every language and framework have their own way of doing things.

Getting a grip on how things are structured, let alone why, is always the hard part for me. But maybe people are better at just hacking away and figuring out that stuff as needed than I am.


If you’re interested in build caching for Gradle or Maven, we support it at Buildless

You can use the local agent entirely for free and upgrade to Cloud caching if it fits your case.

Maven and Gradle are two of our best integrations.

https://less.build


I wonder if we have a similar guide for JavaScript/TypeScript project.


The build.gradle is great... can you add the Maven publishing code for Sonatype? That is truly cursed, but essential for libraries.


It depends what you are doing on the jvm. Java is pretty well supported with both gradle and maven. But a lot of stuff just defaults to gradle these days. I'm doing Kotlin (server side and some multiplatform stuff; no android). With Kotlin, Gradle is the only good option. Anything else just doesn't enjoy first class support from Jetbrains. They don't document it. They don't support you. You can probably make maven do most of what you need for simple Kotlin projects. If it doesn't work, you are on your own. Not worth the trouble. And the kotlin script dialect for Gradle is actually making things nicer gradually (IDE support for that is way better).

What Maven and Gradle have in common is that they are quite convoluted and complicated tools for what they do. I have a love-hate relationship with both as they can be serious time sinks when they don't work as advertised. But I'm also smart enough to know that I need to use one of them as the alternatives are usually worse.

I've been on projects where people tried to make maven do things it clearly wasn't good at. I learned a hard rule there: never replace a 5 line bash script with an unreadable 300 line blob of maven xml depending on all sorts of exotic plugins. It will break on you. Repeatedly. And it's going to be a PITA every time. That five line bash script is looking really tempting when you've banged your head against the wall in frustration for a few hours. Same with Gradle. Both tools have things they do well, things they can technically do that are a bit of an afterthought, and things they just really suck at. And you usually end up with a mix of all of those things.

Having said that, doing custom things with gradle tends to be a whole lot easier. You can basically just do whatever with files and directories in some custom task. I've not used Ant in about 15-20 years or so. Used to be quite OK as an option when the Java world was a lot simpler. But I've also dealt with some really hacky & convoluted build scripts back then.

A good practice with any build tool is to maintain and nurture your build files and update tools and dependencies regularly. I've been on projects where people just stopped caring and and fixing those projects is usually a chunk of work. It's a form of technical debt that you should probably avoid. Once you can't upgrade because all hell breaks loose when you do, it's too late.

What I like with gradle is that it has the gradle wrapper. Maven has a wrapper as well these days. So you can control which version of your tool of choice builds your project and developers don't have to worry about installing the right version. Every few months, I just update my projects to require the latest gradle version and fix whatever needs fixing. Doing this incrementally is pretty straightforward. If you wait five years, good luck. Maven is a lot easier with this because effectively it barely changes for the last decade plus. And not because there's nothing left to improve. There just isn't a whole lot happening in terms of new stuff in the maven world. Maven still is on the same major version since I last used it; a decade ago. And there haven't been a lot of minor version bumps either.

Another thing I use is the refreshVersions plugin for Gradle (mentioned in the article). I highly recommend taking a look at that because it's great and really easy to take into use. It makes keeping your dependencies up to date really easy. Out of date dependencies are another form of technical debt. So you don't want all the security, stability, correctness, and other fixes? Or is it just that you can't be bothered to find out if there are any? If the answer is the latter, you probably have a pile of technical debt right there. The longer you delay updating, the harder it gets. BTW. that's portable advice to just about any language and build tool.


[flagged]


The maven pom.xml contains a lot more than what would be in cargo.toml.

How much extra config or shell scripts would cargo have to have to add code coverage, findbug tools (that do things like linting and pattern matching to detect common coding errors)? How much extra is needed to add a unit testing mechanism, an integration testing mechanism, a dependency checker (e.g., checking if you're not using a duplicate transitive that is pulling in different versions that might clash)?

The only thing comparable to cargo.toml is these 40-ish lines[1], which you pay double for the xml tax. The rest are things that are adding value, but could be removed (tho i would say it's adding a lot more value than it's costing you lines of code).

[1]https://github.com/binkley/modern-java-practices/blob/master...


> How much extra config or shell scripts would cargo have to have to add code coverage

2 lines with cargo tarpaulin. 1 to add the dependency, 1 to actually run it.

> findbug tools (that do things like linting and pattern matching to detect common coding errors)?

0. It's built in. Cargo check runs by default, you can run cargo clippy with 1 line if you want stricter checks.

> How much extra is needed to add a unit testing mechanism, an integration testing mechanism,

0. It's built in. (I don't particularly like the built in integration test mechanism, but that's another matter)

> a dependency checker (e.g., checking if you're not using a duplicate transitive that is pulling in different versions that might clash)

0. Cargo does this already.


You could put all this stuff in a parent POM and re-use it for all your projects.

Statements like "just 2 lines" in Cargo is just a weird comparison. Because it's only 2 lines if you don't configure anything. You can also use all this stuff out of the box with Maven or Gradle with far fewer lines if you don't configure anything. I can point out a whole slew of weird defaults in Clippy [1] which will probably require me to specify a dozen of the hundreds [2] of configuration options it has.

> 0. It's built in. Cargo check runs by default, you can run cargo clippy with 1 line if you want stricter checks.

Findbugs is a framework for static code analysis with plugin support and what not. Clippy is just a linter.

[1] https://rust-lang.github.io/rust-clippy/master/index.html [2] https://doc.rust-lang.org/clippy/lint_configuration.html


The world has moved on though to opinionated tools, and Rust isn't even the furthest in that direction (That would be Go). The equivalent of those two lines in Cargo.toml would be this example of a basic configuration from the jacoco-maven-plugin: https://www.jacoco.org/jacoco/trunk/doc/examples/build/pom.x... - That's 40 lines in the <plugin> section to do the "defaults".

Yes, you could add a load of config for files to include/exclude from coverage and so on, but the idea that that's a norm is way more common in Java projects than other languages. Like here's some example Cargo.toml files from complicated Rust projects:

Servo: https://github.com/servo/servo/blob/main/Cargo.toml

rust-gdext: https://github.com/godot-rust/gdext/blob/master/godot-core/C...

ripgrep: https://github.com/BurntSushi/ripgrep/blob/master/Cargo.toml

socketio: https://github.com/1c3t3a/rust-socketio/blob/main/socketio/C...


That has not much to do with the JVM. See Scala CLI[1] for instance, the developer experience is pretty similar to Cargo.

The thing is, with any non-trivial project, zero to hello world isn't a very useful metric. Gradle (and Maven, sbt, ...) do a lot more than Cargo, and their usage is primarily optimized for complex multi-modules projects.

[1] https://scala-cli.virtuslab.org


And then you need 3700 lines of Cargo.lock to make sure that those 15 lines of Cargo.toml actually does work


Which you don't need to write manually, unlike your maven <dependencyManagement> section


Just older lineage language. Modern languages come batteries included and we use JSON or TOML more.

Hardly a surprise that we got better at this.


... and all that means nothing in any bigger longer running project, the real challenges and complexities lie elsewhere. Also, 700 lines of trivial-to-read highly structured XML is the worst offense you can find?


Every second you’re reading build XML or making decisions about it is time not accomplishing the core goal. I’ve created a lot of little libraries over the years, and every time, the worst part is the tedium of setting up the environment.


It is pretty bad if I encounter say an open source project and want to quickly see what it depends on or answer another such question.


Looks to be a lot of useful experience summarised here. I've just skimmed parts so far, but will come back and read more as time allows.

> https://github.com/binkley/modern-java-practices#keep-your-b...

It boggles my mind that in this day in age, Maven still recompiles everything if you touch anything. The problem of figuring out what files need to be built and building only them was solved for C in I think the late 70s (makedepend).

I don't know if Gradle is better in this respect. (It certainly can't be worse.)


I don't know if Gradle is better in this respect. (It certainly can't be worse.)

Every Gradle project I’ve worked on has had non-deterministic builds where you sometimes need to manually clean and rebuild to fix it, and still does unnecessary work every time. I think it’s worse.


I don't like Gradle much, but I have to defend it here.

Gradle uses a proper task graph with cached incremental builds, out of the box. The only way to break this is if you add your own task types that don't follow their rules, which as a sibling comment observes, is something they've been tightening up in recent releases. Now it is too easy to break those rules and end up with weird problems, and that is a valid criticism of Gradle. But it's also one they're fixing.

One of the reasons Gradle is so prevalent over Maven, especially at large companies, is because Gradle scales drastically better and has far better performance for large projects. A Maven project with hundreds of modules will become impractical quickly due to its lack of a proper task graph and poor support for incremental compilation. Gradle can handle this easily.

The other reason Gradle has become so widespread is because the path for creating custom tasks is far smoother. Often in build systems you need to define ad-hoc tasks, which may grow over time into more powerful collections of build logic. This is easier and less verbose in Gradle than in Maven, which goes straight from declarative to "write everything as plain Java on your own" really fast.

As a result of that there are tons of Gradle plugins available for different features and frameworks, and they are much easier to find. Gradle just has a bigger ecosystem and that means collectively it has a lot of features, which is hard to compete with. In fact, there are many frameworks that can only be used via Gradle for this reason.

I've spent a lot of time studying how to make a build system that can compete well with Gradle and despite the product's obvious flaws, it's not as easy to beat as you might think.


If I wasn't sat here trying to figure out why a Transformer for a Provider is causing a circular evaluation when it's queried, but only when called from the Kotlin DSL, I'd be a lot more inclined to agree with you. At least Gradle 8.6-rc-1 gives a nice error message instead of just failing with a StackOverflowError.

I'm actually kind of a Gradle fan, but it's hard sometimes.


Absolutely. As I said, I have spent a while studying how to build a competitor, and actually have written my own build engine that runs on the JVM as part of that! It powers Conveyor which is a build system for packaging and deploying desktop apps+servers (see my bio) and is by now very robust. So I'm very familiar with Gradle's problems and ways it could be better.

Nonetheless, once you include all the plugins, Gradle has an enormous feature set. Usability is the clear problem with it, but it's also the vaguest. Pointing out problems is eashy, coming up with alternative designs is medium hard, getting everyone to agree those alternatives are better is really hard.


Hmm, you probably have more direct experience; I can only speak to my personal experience which is that biggish Gradle projects are generally broken. Specifically at the last, let's see, three Android jobs I've had.

It also seems very, very slow. A trivial wizard-generated app in Android Studio takes significant time to set up and build, for no good reason that I can see. Builds where nothing has changed take time -- it spends many seconds just parsing the build files.

I've tried Bazel and it's both faster and infinitely more robust. I really wish Google had adopted that instead of Gradle for Android Studio.

In a personal project I use Ninja build files generated by hand-rolled Python scripts and that's much, much faster than Gradle. Plenty robust for my purposes and I'm not afraid of clean builds because it's fast.

Not sure what general lesson I should take away, except that Gradle is bad and I don't like it!

I suppose it could be one of those things where you need to learn it properly to appreciate it fully, but the more Gradle I learn the less I like it. Trying to do any kind of custom code generation is really painful, for example.

Edit to add: despite all my whining, I would still use Gradle for a new Android project just because the official tooling support trumps everything else. I definitely wouldn't want to use it outside of Android.


The first run is very slow yes for a variety of reasons. It should only re-configure if you change the build scripts. It's possible that the Android plugin breaks that property in some way; as I said, it's way too easy to do that.

Bazel has been around for years but never managed to displace Gradle, even in Google's own platforms. It's designed for a very specific corporate environment in which policy constrains the way you write software to a tremendous degree. If you match those policies it's great. If you don't then you'll quickly find cases where stuff is easy with Gradle and harder with Bazel. For example, for the longest time even pulling deps from Maven Central took extra third party rules (probably fixed now?), but it's reflective of the mentality.


> For example, for the longest time even pulling deps from Maven Central took extra third party rules (probably fixed now?), but it's reflective of the mentality.

This seems like a feature to me. Bazel is a generic, extensible build tool; the core project shouldn't need to know the specifics of one dependency system for one language.

With recent versions of Bazel and its new module system (bzlmod), it's much easier to pull in third party rules. In this case, just one line:

  bazel_dep(name = "rules_jvm_external", version = "5.3")
https://registry.bazel.build/modules/rules_jvm_external


It has rules for C++ and Java apps out of the box, iirc? I'm all for clean layering but that's not an excuse for poor usability. Good to hear that they got it fixed though, last time I looked Bazel had poor IntelliJ support but maybe I should check it out again.


I mean, that’s android that is doing a shitload of stuff. But what other build tool could do the same thing as android does? There are not many contenders, and I don’t think they would fare much better.

On the other side of mobile OSs, xcode is definitely not faring better.


Gradle scripts are just programs so it's impossible to vouch for every single gradle script that exist. I mean you can just write

  if( Random.nextBoolean()) {
      project.delete(
          fileTree(project)
      )
  }

And delete your whole project on roughly every second build.

Yes, so maybe it's too easy to shoot yourself in the foot but the "non-deterministic" behavior isn't coming from anything gradle does inherently but from your build script.

In my experience most critique of gradle happens in a vacuum. Is it perfect? No. But on planet earth most people use gulp for builds and npm for dependencies that together have a superset of any issues that gradle has.


This has got better with more recent Gradle versions, at the expense of stricter checks about task output directories.


Then they fked up big time with the config file.

Gradle has a correct view of the build graph, unlike maven. There are never a case (unless deliberately broken) where you have to clean build for correctness.


"Deliberately" is a rather strong way to say "accidentally". When multiple different totally independent teams manage to subtly break things, I think you have to start questioning the tool rather than the teams.

Like, I don't know, maybe the problem was caused by a dependency they were using rather than their own build scripts. How do you know, how do you fix it? What are you supposed to do, not use dependencies?


A gradle dependency is a finished artifact. It gets added to the classpath, what could it break?

A gradle plugin could potentially break something, but a) you ain’t writing gradle plugins willy-nilly, b) if it’s a “big” one, it’s likely not gonna be that bad.


Maven does not recompile everything each time. You have to do mvn clean to force it to. If you change a single class just that one class gets recompiled which is evident by Maven's output.


Depends on how you interpret 'everything' and 'each time'.

The default compiler plugin configuration is quite eager to recompile allot if something changed.

https://maven.apache.org/plugins/maven-compiler-plugin/compi...

"true (default) in this mode the compiler plugin determines whether [...] any source file was added, removed or changed since the last compilation. If this is the case, the compiler plugin recompiles _all_ sources.


Yeah, although my experience is that people collect shell aliases and similar workflows that always do something like `mvn clean package` and then complain about build times.


Also, while I love Maven, I don't run it very often (or at least not directly on the command line). Most work happens in IntelliJ, and then I'll let the build server deal with it. Builds are short, so no harm if the build server fails and I have to fix something up...


It's been a while since I used maven but if I recall correctly the reason for that is that javac (java compiler) actually doesn't do incremental compilation. It was such a problem that eclipse (the ide) had it's own java compiler that could do incremental compilation. This alternative compiler was so popular that you could even use it in Jetbrains IDE and I think you could configure maven to use it to get incremental compilation there to.


Eclipse's own compiler is (impressively!) able to do truly incremental compilation: compiling the syntactically correct subset of methods in source files that aren't fully syntactically correct, and not bothering to recompile untouched methods. javac will compile every file you tell it to, plus any out-of-date immediate dependencies (and their out-of-date immediate dependencies, and so on, recursively), but if A uses B which uses C and you change C.java only, `javac A.java` doesn't "see" that all 3 need recompilation.

Maven doesn't even offer javac's incomplete dependency checking by default, though I believe it can be forced to. (0.5 points for correctness over efficiency, I suppose.)




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

Search: