Why I prefer Maven over Gradle

Why I prefer Maven over Gradle

In the Java world, one of the first question developers encounter is "should I use Grade or Maven as build tool?". It's a fundamental decision which will stick to you with time. And when googling it, Gradle's biased comparison even pops up as the top search result (at least for me).

At first sight, Gradle looks cool:

  • Their website looks way nicer and polished than Maven's one

  • The syntax is much more compact than Maven's verbose XML

  • Gradle is "newer" while Maven is "older"

  • Gradle is much faster (according to them)

No wonder people pick it up when faced with uncertainty and just wanna get started. So now, let me tell what's wrong with Gradle IMHO and why Maven is still the better option, even so many years later.

Configuration vs Scripting

Basically, the pom.xml that you define in Maven is a "configuration". You define the name, the version, the list of dependencies, etc. Since it follows a specific schema, with a set of properties to define, you can also look at it visually through a UI for example. It's a declarative definition of your library/app.

On the opposite, a Gradle build script is exactly that: a script. It's using the Groovy language, or recently also Kotlin, to let you write anything you want. Let it sink in, you use a programming language to define what the build should do. You can import other scripts, send a HTTP request to check the current weather and insert a funny UI generated picture in your build artifact.

While they have many aspects in common, they nature ("configuration" vs "script") is what differentiates them fundamentally.

You may think: "Isn't it great if I can do anything with that build script?! It's ultimate freedom!". That is right, but this boon is also a curse.

When I see a Maven project, with a pom.xml, I know what it does and where to find what. It's always the same. Directories, commands to run, changing the version, whatever, it's the same for all maven projects.

When I see a Gradle project, I have no idea what the build script does. If you don't have a clear documentation ready, you'll have to dive into the build script to actually discover and try to understand what it exactly does.

The price of freedom

It's not rare that you need something specific in your build. In both Maven and Gradle, it's possible to do so, but here also their approach is opposite.

In Gradle, it's straightforward. Since it's scripting, just write whatever you want, you can do anything very easily. Your own build stages, calling functions, using variables, importing some other scripts, whatever. It's easy.

In Maven, it's the opposite. Adding something custom is more difficult. You will have to use a plugin to enable the specific functionality, or even write a plugin yourself if really necessary. While writing a plugin is definitely more work, this kind of also enforces reusability though.

The takeaway here is the same as before. While Maven builds tend to always follow the same build stages and conventions, Gradle builds tend to become more and more complex and customized over time, because it's so easy to "just add a few lines" to the build script. Look at it after a few years and the Maven pom.xml is likely almost as readable as the first days while the Gradle build.gradle script became rocket science.

As an exercise, I picked a random gradle.build file from another team at work to look at it. It had over thousand lines and the few dependencies it had were externalized in another file and combined in a fancy way.

On the opposite, pick any Maven project, and the list of dependencies will always be in the same place, in the <dependencies>...</dependencies> tag.

History repeating itself. As a side note, it's interesting to see that in the very early days of Java, before maven was born, build scripts were the norm. In the beginning they were plain shell scripts invoking javac ... to compile the source code, packaging it, etc. Then came "ant" to do the same, in a bit more structured way but still tended to become customized and complex over time. Then one day came the idea to use a more declarative approach, by defining a project and its dependencies while letting the tool take care of how it is build. Maven was born. Then, some day, Gradle was born, because "I want to customize stuff".

Gradle lies in your face

Now, this is a little grudge of mine against Gradle's marketing habits. When going on their website, they will feature a "Gradle vs Maven" comparison claiming that Gradle is "oh so much faster" than Maven and the following picture.

Now, let's take a closer look...

First, what's shocking is that a "Clean build with tests" is so much faster than the original build! It's almost instant! Including tests! ...let me get this straight: this is not "clean" at all. It's just doing nothing. I find Maven much more sensible in that case, it actually rebuilds everything from scratch. To go a little bit further, a "build" in Maven will just check for changes and compile changed files, which would result in a similar figure, while a "clean build" will remove the whole directory and re-build everything. I find this should be the expected behavior unlike Gradle's "clean build" not cleaning anything. After all, the aim of a clean build is usually to fix issues due to some undesirable thing lying around in the build directory, for whatever reason.

Then, let's look at the normal case: is Gradle really twice as fast? Well, here is another question for you: who compiles the source code? ...got an idea? Well, it's the javac compiler from the JDK, it's not the build tool! So why would Gradle be twice as fast?! Here is the trick: Gradle runs the tests in parallel while Maven do it sequentially. That is the reason! Gradle ain't faster or anything, it just runs the tests in parallel. I dislike this default. It's just a question of time until you get tests having side-effects and race conditions. Then you'll obtain "Heisenberg tests" succeeding sometimes and failing sometimes, depending on how their executions overlap. You'll wonder why and waste lots of time investigating the issue. Moreover, it usually runs in a background jobs after commits anyway.

Now, while I dislike Gradle's defaults, what I'm really annoyed about is how they distort the truth. They should say "we run tests in parallel by default and our 'clean' does nothing instead!". That should have been the correct way to put it instead of using their misleading statements insinuating that they compile faster.

Gradle is not simple

For Maven, the scope of dependencies is relatively straightforward:

  • Compile (the "usual" dependency)

  • Test (for tests)

  • Provided (provided at runtime by JDK or a container)

  • Runtime (quite rare. For drivers or alike available at runtime but not for compiling)

It's enough and I never needed anything else.

Gradle on the other hand has lots of scopes:

  • api

  • implementation

  • compileOnly

  • compileOnlyApi

  • runtimeOnly

  • testImplementation

  • testCompileOnly

  • testRuntimeOnly

  • ...a few more deprecated scopes

  • ...a few more classpath scopes

  • ...you can also extend and combine scopes

Well, you basically get it. Gradle is "super-customizable", so much that you often wonder what it exactly does or that you make a mistake without realizing. Gradle sells it as "Maven has few, built-in dependency scopes, which forces awkward module architectures" but IMHO it's Gradle which is confusing and overcomplexified here while Maven has exactly what's actually required.

That is just the tip of the iceberg. But basically Gradle is super-customizable while maven favors conventions. No wonder Gradle is also a company that thrives with support and training. If it was simple, such things would not sell.

Gradle needs maven, but not the other way round

Every single library in the Maven Central Repository must have pom.xml. It's the declarative definition of the library containing name, version, license, etc, and most importantly the list of its dependencies. Without a Maven pom.xml there would be no Central Repository nor dependency management possible.

Whether you use Grade or Maven, both read the pom.xml Maven definition to build the dependency tree. It's at the core of the dependency dependencies system to pull all transitive dependencies and resolve version conflicts.

In other words, Maven can live without Gradle, but Gradle still needs Maven to exist. Maven just applies a standardized build based on the pom.xml while Gradle builds in in some way and generates a pom.xml as a build step if you want to actually publish your library.

Maven isn't perfect either

Now, I bashed a lot about Gradle, but Maven isn't perfect either. It has issues too. Their website sucks IMHO, it could welcome YAML as more compact alternative format, some plugins should be built-in and the format itself could be tweaked here and there. But overall I find it OK considering it's a format that lived more than 20 years.

The other drawback is a lack of flexibility. It's indeed rigid in how it expects your project to be and may become problematic if you need for example to mix multiple different techs. For example a building a node project, running a python script, etc. as part of the build procedure to place some extra stuff inside the produced artifact. But for that IMHO, it's better to use CI scripts, running as GitHub actions or GitLab pipelines to build a "mixed bundle". Let each tech stack build its own artifacts and combine them later through scripting. I favor that approach over pushing the build scripts customizations too far.

Take it with a grain of salt

While I bashed at Gradle and praised Maven, it should be taken with a grain of salt. At the end of the day, they are just tool and either can be used wisely or like a fool.

With maven too, you can also produce "monster pom.xml files" by using tons of plugins and super-complex configurations overriding all defaults. Likewise, Gradle is not necessarily a monster. Use it wisely, keep your build script clean, refrain from adding custom build steps and you will do just fine. It's not bad per-se.

It's just that by default, in the hands of average developers, Maven's pom.xml will tend to remain understandable (because it takes effort to escape out of the conventions) while Gradle's build.gradle will tend to become more complex and customized over time (because it's so easy to do so). All the small shortcuts now and little extra steps that stray away from the build conventions tend to become liabilities in the long term.

As said previously, Gradle's great flexibility and customizability of the build is both a boon and a curse. Although I prefer to build "generic" projects where I can, because it's by far simpler to maintain in the long run, using Gradle definitely has its place when you need more specific stuff that requires customization.

TL;DR: as a rule of thumb, Maven's pom.xmtends to remain fairly generic with time, while Gradle's build.gardle leans towards being highly customized and therefore complex. This is due to their "nature", while Maven is based on a rigid project "definition", Gradle is a free form build "script".