Comparison with sbt
sbt was a real achievement. It made arbitrary Scala code in builds expressible, brought incremental compilation to the JVM, and shaped how a generation of Scala teams ship libraries. The case for switching to bleep is that the cost of "your build is a Scala program", slow startup, expensive plugin ecosystem, debugging via println, is no longer worth it now that there's an alternative.
Build is data, not a program
The same single-module Scala project, in build.sbt and in
bleep.yaml:
| build.sbt + project/plugins.sbt | bleep.yaml |
|---|---|
| |
The sbt version is Scala code. To know what runs. You have to know
what ThisBuild does, what := versus += versus ++= mean, when
the Test configuration applies, what implicit conversions
libraryDependencies triggers. The project/plugins.sbt lives in a
sibling Scala project that compiles before your real build can be
read.
The bleep version is data. You can cat it, grep it, git diff it,
have a script edit it, paste it into an LLM and get a useful answer.
There is no project/ sibling to maintain.
Faster everything
| sbt | Bleep | |
|---|---|---|
| Cold startup | ~10-60s (JVM warmup, Scala compiler load, plugins resolved & compiled, build evaluated); minutes for large or plugin-heavy builds | ~0ms (native binary) |
| Incremental compile | Zinc | Zinc (same incremental engine) |
| IDE import | Tens of seconds to several minutes depending on project size | ~1 second via BSP |
| Build re-evaluation after editing build.sbt | seconds (recompile project/) | instant (re-parse YAML) |
| Memory footprint | sbt server + JVM, often 1-2GB | native CLI is ~tens of MB; bleep-bsp daemon (used for compile/test/run) holds Zinc analysis in a separate JVM, configurable heap, shared across every bleep workspace on the machine |
The Zinc compile core is identical, both tools wrap the same incremental compiler. What's different is everything that wraps it: the launcher, the build evaluator, the plugin model, the IDE integration.
Plugins and tasks
sbt plugins live in project/plugins.sbt, get compiled before your
build evaluates, and extend the build via AutoPlugin, Setting,
and Task. They're macros and DSL on top of macros.
Bleep doesn't have plugins. It has scripts:
plain Java, Kotlin, or Scala programs that depend on bleepscript and run
from the command line.
| sbt plugin | Bleep script | |
|---|---|---|
| Lives in | project/MyPlugin.scala + addSbtPlugin(...) | regular project under scripts: in bleep.yaml |
| Distribution | publish to a Maven repo, every consumer adds addSbtPlugin | publish to a Maven repo, every consumer adds a dependencies: line |
| Debugging | sbt shell + println, classloader-isolated | breakpoint in IntelliJ, step through |
| Testing | sbt's scripted framework | regular unit tests |
| API surface | sbt Keys, configurations, scopes, AutoPlugin | bleepscript: Started, Commands, args |
sbt's task graph and scoping are real power. You can ask
"Compile / scalacOptions for myproject" and sbt will give you the
right answer with all overrides applied. Bleep doesn't model that.
For most build scripts you don't need it; for the rare cases where
you do, you walk the build model directly in your script.
What sbt does that bleep doesn't
- Scoped task customization.
Compile / scalacOptions += "-Wunused",Test / fork := true,inConfig(IntegrationTest)(Defaults.testSettings). Bleep crosses can overridescala.optionsper cross variant; finer per-config scoping isn't modelled. Most teams don't need it. scriptedplugin testing framework. Bleep scripts are tested with regular JUnit/MUnit/ScalaTest.- Mature plugin ecosystem. Hundreds of sbt plugins exist; bleep has ~10 ports of the most-used ones (sbt-ci-release, sbt-sonatype, sbt-pgp, sbt-dynver, sbt-native-image, sbt-scalafix, sbt-jni, mdoc). For one-off plugin features, you re-implement in a script.
reloadflow. sbt's interactive shell withreload,compile,test,consoleis a working environment for some teams. Bleep's command-line model is one-shot.
What bleep does that sbt doesn't
- Native binary startup. ~0ms vs ~10-60s, longer for large or plugin-heavy builds.
- BSP-driven IDE import in ~1 second. No project/ sibling to re-evaluate.
- YAML build that's safe to edit machine-side: LLMs, scripts, IDE refactors all work without compiling project code.
- Scripts as plain Java/Scala, debuggable in your IDE, not macro-evaluated DSL.
- Built-in
bleep importfrom sbt and Maven. Convert an existing build to bleep in one command. - First-class cross-building axes for Java release, Kotlin version,
and Scala version (plus platform). sbt's cross-building axes are Scala
version and platform; Java-version and Kotlin-version axes need plugins
or
projectMatrix. - CI project invalidation built in (
bleep build invalidated --base HEAD~1).
Migrating from sbt
bleep import reads your sbt build (via sbt bloopInstall) and
generates a bleep.yaml. See Import from sbt.
Once imported, the parts that won't carry over are sbt-specific build logic, custom tasks, scope-axis overrides, plugins not yet ported. Porting sbt plugins covers the translation patterns and shows real worked examples (DynVerPlugin, PgpPlugin, the Sonatype client).
When sbt is still the right answer
- You depend on a niche sbt plugin that hasn't been ported and isn't worth porting.
- You rely heavily on the sbt shell as a development environment
(
reload,console, custom interactive tasks). - Your team is shipping features faster than the cost of sbt's startup is hurting you. If 60s startup × 5 invocations a day isn't the bottleneck, leave it alone.