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.
Where these snippets come from
The two sides of this page do not have equal standing, and you should know which is which before you read them.
The bleep.yaml files were not written for this page. Each one is
the literal output of bleep new myapp --lang <language>, captured by
an integration test that then bootstraps the build, runs the app, and
runs its tests in-process —
YourFirstKotlinProjectIT.scala
and its Java and Scala siblings. The test mirrors the file into
docs-snippets-from-tests/, the Build workflow runs
git diff --exit-code on that directory so a stale snippet fails CI,
and this page includes the result verbatim. If one of them were wrong,
the build would be red.
The pom.xml, build.sbt, and build.gradle.kts have no such
backing. We wrote them by hand, and nothing executes them. They are
our best attempt at the smallest thing a competent user of each tool
would actually write for this exact project — same language, same
dependency versions, same main class, same test framework, no plugin
present unless the bleep build does the same job. That's a claim, not
evidence, and we're the interested party. If you'd write it shorter or
cleaner, tell us and
we'll fix the page.
Two things we've deliberately held constant so nothing is rigged: every
side uses the same versions the bleep scaffold picks (Kotlin 2.4.10,
Kotest 6.2.3, Scala 3.8.4, MUnit 1.3.4, JDK 25), and every side is
minimal rather than realistic. A production pom.xml is much longer
than the one below — so is a production bleep.yaml.
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. The $version you see is substituted from the
newest release tag when this site is built, so it is never stale.
- Kotlin
- Java
- Scala
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M12
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:6.2.4
dependsOn: myapp
extends: template-common
isTestProject: true
templates:
template-common:
kotlin:
jvmTarget: '25'
version: 2.4.10
platform:
name: jvm
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M12
jvm:
name: graalvm-community:25.0.1
projects:
myapp:
extends: template-common
platform:
mainClass: com.example.Main
myapp-test:
dependencies: org.junit.jupiter:junit-jupiter:5.14.4
dependsOn: myapp
extends: template-common
isTestProject: true
templates:
template-common:
platform:
name: jvm
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M12
jvm:
name: graalvm-community:25.0.1
projects:
myapp:
extends: template-common
platform:
mainClass: com.example.helloMain
myapp-test:
dependencies: org.scalameta::munit:1.3.4
dependsOn: myapp
extends: template-common
isTestProject: true
templates:
template-common:
platform:
name: jvm
scala:
options: -encoding utf8 -feature -unchecked
strict: true
version: 3.8.4
For the per-feature breakdown of each comparison, see the dedicated pages: vs Maven, vs Gradle, vs sbt, vs Mill, vs Bazel.
Bazel has no snippet on this page on purpose. A BUILD file for this
project would be short and unremarkable, and it would tell you nothing
about what Bazel actually costs or buys — that argument is about
hermeticity, remote execution, and staffing, so it
gets its own page.
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>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>2.4.10</kotlin.version>
<kotlin.compiler.jvmTarget>25</kotlin.compiler.jvmTarget>
</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>6.2.3</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>
<executions>
<execution>
<id>compile</id>
<goals><goal>compile</goal></goals>
</execution>
<execution>
<id>test-compile</id>
<goals><goal>test-compile</goal></goals>
</execution>
</executions>
</plugin>
<!-- Pinned rather than inherited: which Surefire the super-POM
gives you depends on your Maven version, and the JUnit 5
platform that Kotest runs on needs a 3.x. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
</plugins>
</build>
</project>
The <executions> block is not padding: kotlin-maven-plugin binds no
goals by default, so without it Maven compiles nothing. This pom.xml
also stops one job short of the bleep.yaml above — bleep's
mainClass: com.example.MainKt makes bleep run work, and the Maven
equivalent is another plugin (exec-maven-plugin, or a manifest entry
via maven-jar-plugin).
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, matching the Scala tab above. Sbt is rarely used outside Scala; the Java/Kotlin flavors exist but nobody actually picks sbt for them.
build.sbt:
ThisBuild / scalaVersion := "3.8.4"
lazy val myapp = (project in file("."))
.settings(
name := "myapp",
scalacOptions ++= Seq("-encoding", "utf8", "-feature", "-unchecked"),
libraryDependencies += "org.scalameta" %% "munit" % "1.3.4" % Test,
Compile / mainClass := Some("com.example.helloMain")
)
project/build.properties:
sbt.version=1.11.2
project/plugins.sbt:
addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.0")
That third file is there because the bleep build sets strict: true,
and bleep implements strict by applying sbt-tpolecat's dev-mode
option set (bleep vendors the plugin as
liberated/sbt-tpolecat).
Same behaviour, so it belongs on both sides of the comparison. Drop
strict from the bleep.yaml and you can drop plugins.sbt here.
This is a tight sbt build, and it comes in shorter than the
bleep.yaml. The difference isn't length. It's three default
configurations (Compile, Test, Runtime) plus any you define, a
DSL whose every keyword is a global side effect on a hidden settings
graph, and a plugin enabled by name whose effect on your build is
documented on somebody else's website. Nothing here can be read as
data: to know what this build does you have to run it and ask.
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.
build.gradle.kts:
plugins {
application
kotlin("jvm") version "2.4.10"
}
group = "com.example"
version = "0.1.0-SNAPSHOT"
repositories { mavenCentral() }
kotlin { jvmToolchain(25) }
dependencies {
testImplementation("io.kotest:kotest-runner-junit5-jvm:6.2.3")
}
application {
mainClass.set("com.example.MainKt")
}
tasks.test { useJUnitPlatform() }
settings.gradle.kts:
rootProject.name = "myapp"
This is the short, correct version. kotlin-stdlib is omitted because
the Kotlin plugin adds it for you; jvmToolchain(25) is the Kotlin
plugin's own knob rather than a JavaCompile tweak that a pure-Kotlin
project would never hit; and useJUnitPlatform() is required, because
without it Gradle runs Kotest's JUnit 5 suites with JUnit 4 and reports
zero tests, green.
It's a real Kotlin program that side-effects the build graph, in two
files, plus the wrapper (gradlew, gradlew.bat,
gradle/wrapper/*) and usually a gradle.properties to give the
daemon enough heap. Renaming the project means editing
rootProject.name in settings.gradle.kts plus a directory rename;
there's no gradle rename-project. Bleep has
bleep build project-rename,
which is only possible because the build is data.