Skip to main content

Same project, four build tools

A single application. One application's worth of dependencies. One main class. Here it is, expressed in bleep.yaml, pom.xml, build.sbt, and build.gradle.kts.

Line counts aren't the point — you'll see sbt's three files come in shorter than bleep's one. The point is the kind of artifact each one is: plain data versus XML versus a DSL with hidden state versus a Kotlin program that mutates a build graph. Read each one with that question in mind.

bleep.yaml — plain data

Same shape across all three languages. The only thing that changes between tabs is the per-language fields (scala:, kotlin:, dep coordinates) and the test framework. Each snippet is a real workspace from bleep's own integration tests; the bleep build server compiles and runs each one on every commit.

$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M9
jvm:
name: graalvm-community:25.0.1
projects:
myapp:
extends: template-common
platform:
mainClass: com.example.MainKt
myapp-test:
dependencies:
- io.kotest:kotest-runner-junit5-jvm:5.8.0
- org.junit.jupiter:junit-jupiter:5.10.1
dependsOn: myapp
extends: template-common
isTestProject: true
templates:
template-common:
kotlin:
jvmTarget: '25'
version: 2.3.0
platform:
name: jvm

For the per-feature breakdown of each comparison, see the dedicated pages: vs Maven, vs Gradle, vs sbt, vs Mill.

pom.xml — XML

The Kotlin variant. Java and Scala flavors of pom.xml differ only in the plugin section and dep coordinates — same XML.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>0.1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>2.3.0</kotlin.version>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-runner-junit5-jvm</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
</plugin>
</plugins>
</build>
</project>

It is 2026. We are still typing closing tags by hand. There is no need.

build.sbt + project/ — a DSL across three files

The Scala variant. sbt is rarely used outside Scala — the Java/Kotlin flavors exist but nobody actually picks sbt for them.

build.sbt:

ThisBuild / scalaVersion := "3.8.3"
ThisBuild / organization := "com.example"

lazy val myapp = (project in file("."))
.enablePlugins(JavaAppPackaging)
.settings(
name := "myapp",
libraryDependencies ++= Seq(
"com.lihaoyi" %% "fansi" % "0.5.0"
),
Compile / mainClass := Some("com.example.Main")
)

project/build.properties:

sbt.version=1.10.0

project/plugins.sbt:

addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0")

Three files, four scopes (Compile, Test, Runtime, IntegrationTest), and a DSL whose every keyword is a global side effect on a hidden settings graph. sbt-native-packager is enabled by name; what it does to your build is somewhere on a wiki.

build.gradle.kts — a Kotlin program

The Kotlin variant. Gradle's Java DSL is similar shape; the Scala flavor exists but isn't widely used.

plugins {
application
kotlin("jvm") version "2.3.0"
}

group = "com.example"
version = "0.1.0-SNAPSHOT"

repositories { mavenCentral() }

dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.0")
}

application {
mainClass.set("com.example.MainKt")
}

tasks.withType<JavaCompile> {
sourceCompatibility = "21"
targetCompatibility = "21"
}

A real Kotlin program that side-effects the build graph. Plus settings.gradle.kts, gradle.properties, and the wrapper. Renaming the project means writing a Kotlin task — there is no gradle rename-project.