sbt Scala Build

sbt cheatsheet — build Scala and Java projects. sbt compile, sbt test, sbt run, sbt package, sbt console. Manage dependencies and publish artifacts with build.sbt.

5 min read

What it is

sbt is a build tool for Scala and Java projects, used for compiling, running, testing, packaging, and managing dependencies.

Installation

Linux

# Using SDKMAN! (recommended)
sdk install sbt

or

# Manual installation (download from sbt.io)
# Add sbt's bin directory to your PATH

macOS

# Using Homebrew
brew install sbt

or

# Using SDKMAN! (recommended)
sdk install sbt

Windows

# Using SDKMAN! (recommended)
sdk install sbt

or

# Using Chocolatey
choco install sbt

or

# Manual installation (download from sbt.io)
# Add sbt's bin directory to your PATH

Core Concepts

  • Build Definition (build.sbt): The primary build configuration file, written in Scala. It defines project settings, dependencies, and tasks.
  • Plugins: Extend sbt’s functionality. Common plugins include sbt-assembly for fat JARs, sbt-release for version management, and sbt-native-packager for creating installers.
  • Tasks: Actions that sbt can perform (e.g., compile, test, package). Tasks can depend on other tasks.
  • Settings: Key-value pairs that configure the build (e.g., version, scalaVersion, libraryDependencies).
  • Scopes: Define where settings and tasks apply (e.g., ThisBuild, project/name, Compile, Test).
  • Aggregating Projects: A root project can aggregate sub-projects, allowing tasks to be run across multiple projects simultaneously.

Commands / Usage

Running sbt

# Navigate to your project directory (where build.sbt is)
cd /path/to/your/scala/project

# Start the sbt interactive shell
sbt

Once inside the sbt shell:

Common Interactive Commands

# Compile the project
compile

# Run tests
test

# Package the project (creates a JAR)
package

# Clean the project (removes compiled files)
clean

# Show project settings
show settings

# Show specific settings (e.g., scala version)
show scalaVersion

# Show dependencies
show libraryDependencies

# Run a specific Scala object with a main method
runMain com.example.MyApp

# Check for outdated dependencies
dependencyUpdates

# Update dependencies (after checking with dependencyUpdates)
# (This is typically done by editing build.sbt and re-running sbt)

# Exit sbt shell
exit

Running Commands Directly from the Shell

# Compile project without entering interactive shell
sbt compile

# Run tests
sbt test

# Package project
sbt package

# Clean project
sbt clean

# Run a specific Scala object
sbt "runMain com.example.MyApp"

# Run multiple commands
sbt clean compile test

# Execute a sequence of commands from a file
sbt ";" ++ commands.txt

# Set a specific setting for a single command
sbt "set scalaVersion := \"2.13.10\"" compile

build.sbt Configuration Examples

Project Definition

// build.sbt

name := "my-scala-app"
version := "0.1.0"
scalaVersion := "2.13.10"

Dependencies

// build.sbt

libraryDependencies ++= Seq(
  "org.apache.spark" %% "spark-core" % "3.3.0",
  "com.typesafe.akka" %% "akka-actor" % "2.6.19",
  "org.scalatest" %% "scalatest" % "3.2.11" % Test // % Test scopes it for testing only
)
  • %% automatically selects the Scala binary version (e.g., 2.13).

Scala Native Packager Plugin

// build.sbt
// Requires adding the plugin in project/plugins.sbt

enablePlugins(JavaAppPackaging, DockerPlugin)

// For JavaAppPackaging
mainClass in Compile := Some("com.example.MainApp")

// For DockerPlugin
packageName in Docker := "my-scala-app"
dockerBaseImage := "openjdk:11-jdk-slim"

Assembly Plugin (Fat JAR)

// build.sbt
// Requires adding the plugin in project/plugins.sbt

assemblyMergeStrategy in assembly := {
  case PathList("META-INF", xs @ _*) => MergeStrategy.discard
  case x => MergeStrategy.first
}

project/plugins.sbt for Plugin Configuration

// project/plugins.sbt

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.9.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.10.0")

Common Patterns

Compiling and Packaging

sbt clean compile package

Cleans previous builds, compiles the source code, and creates a JAR file in the target/scala-2.13/ directory.

Running Tests

sbt test

Executes all tests defined in the src/test directory.

Running a Main Class

sbt "runMain com.example.MyMainClass arg1 arg2"

Executes the main method of com.example.MyMainClass with optional arguments.

Creating a Fat JAR (with sbt-assembly)

sbt assembly

Creates a single executable JAR file containing all dependencies in target/scala-2.13/.

Creating a Docker Image (with sbt-native-packager)

sbt docker:publish

Builds and publishes a Docker image to your configured Docker registry.

Checking for Dependency Updates

sbt dependencyUpdates

Lists all dependencies that have newer versions available.

Running on a Different Scala Version

sbt "++2.12.15 test"

Runs the test command using Scala version 2.12.15.

Aggregating Projects

If you have a multi-module project with a root build, you can run tasks across all modules:

sbt clean compile package

This will execute clean, compile, and package on all sub-projects.

Setting JVM Options

sbt -J-Xmx2G test

Runs the test task with a 2GB heap size.

Running sbt with a specific build definition file

sbt -sbt-boot /path/to/custom/boot.dir -sbt-script /path/to/custom/sbt.jar

Useful for testing or using specific sbt versions.

Gotchas

  • %% vs %: Always use %% for Scala libraries to ensure the correct Scala binary version is appended to the artifact name (e.g., akka-actor_2.13). Use % for Java libraries.
  • Test Scoping: Dependencies marked with % Test are only available during the test compilation and execution phases. They won’t be included in the final package unless explicitly configured.
  • Plugin Configuration: Plugins are typically configured in project/plugins.sbt and their settings are then used in build.sbt.
  • Caching: sbt aggressively caches downloaded artifacts and compilation results. If you suspect a stale cache, use clean or explore sbt’s cache directory (~/.sbt/boot/ and ~/.ivy2/cache/).
  • run vs runMain: run is a task that executes the main class defined by mainClass in Compile. runMain allows you to specify a main class explicitly, which is often more convenient.
  • Build Definition Reloading: When you change build.sbt or project/plugins.sbt, sbt will automatically detect the changes and reload the build. You’ll see a message like [info] Set current project to ....
  • Inter-Project Dependencies: In multi-module projects, ensure dependencies between projects are correctly declared using dependsOn in build.sbt.
  • sbt clean: While useful, sbt clean deletes everything in the target directory. Be mindful of this if you have generated files you want to keep.
  • fork in run := true: By default, run executes in the same JVM as sbt. For applications requiring specific JVM settings or to avoid classpath conflicts, setting fork in run := true in build.sbt will run your application in a separate process.