Maven, older and still extremely widely used, describes a project's dependencies, plugins, and build lifecycle entirely in a declarative pom.xml file — there's no way to write arbitrary logic inside it. Gradle, generally faster due to incremental builds and build caching, uses a build.gradle (Groovy) or build.gradle.kts (Kotlin) script that is a genuine program, letting a team write custom logic directly into the build process itself. Both resolve dependencies from the same underlying repositories (chiefly Maven Central), so a library published for one works identically with the other.
Maven: declarative XML, a fixed lifecycle
Maven's build phases (compile, test, package, install) always run in the same fixed order — predictable, but with no way to inject arbitrary custom logic without writing a plugin.
<project>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>$ mvn compile
$ mvn test
$ mvn package // produces a .jar in target/Gradle: a real build script
Because build.gradle.kts is actual Kotlin code, a team can write genuine conditional logic, custom tasks, or shared configuration functions directly into the build — something Maven's XML fundamentally cannot express without a plugin.
plugins {
id("java")
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
}
tasks.test {
useJUnitPlatform()
}$ ./gradlew build // compiles, tests, and packages in one commandBoth pull from the same repositories
A library published to Maven Central works identically whether the consuming project uses Maven or Gradle — the two tools compete on the developer experience of managing a build, not on which packages are available.