Documentation Index Fetch the complete documentation index at: https://mintlify.com/sbt/sbt/llms.txt
Use this file to discover all available pages before exploring further.
This guide explains sbt’s incremental compilation system powered by Zinc, the incremental Scala compiler.
What is Zinc?
Zinc is the incremental compiler for Scala. It analyzes source code dependencies and only recompiles the minimal set of files affected by changes, dramatically speeding up compilation times.
Zinc is built into sbt and works automatically - no special configuration needed.
How Incremental Compilation Works
Zinc tracks three types of dependencies:
Source dependencies
When file A uses a definition from file B, A depends on B.
Binary dependencies
When source files use external libraries from JARs.
Product dependencies
The compiled class files that need to be updated.
Compilation Process
Analysis - Zinc analyzes source files to detect dependencies
Change detection - Detects which files have changed since last compilation
Invalidation - Determines which files must be recompiled
Compilation - Compiles only the affected files
Analysis storage - Saves dependency information for next compilation
Compilation Analysis
Zinc stores analysis data about each compilation:
// Analysis file location (default)
compileAnalysisFile := target.value / "streams" / "compile" / "compileIncremental" / "_global" / "out"
// Analysis file target root
compileAnalysisTargetRoot := target.value / "zinc"
The analysis file contains:
Source file dependencies
API signatures
Class file mappings
Compilation timestamps
Don’t modify analysis files manually - they’re managed by Zinc.
Incremental Compiler Options
Configure Zinc behavior using incOptions:
incOptions := incOptions.value.withSettings(
Seq (
// Increase recompilation steps
incOptions.value.recompileAllFraction() -> 0.5 ,
// API debug logging
incOptions.value.apiDumpDirectory() -> Some (target.value / "api-dumps" )
)
)
Key Inc Options
Recompilation Control
Class File Management
External Hooks
incOptions := incOptions.value
.withRecompileAllFraction( 0.5 ) // Recompile all if >50% changed
.withRecompileOnMacroDefChange( "dynamic" ) // Macro changes
Understanding Recompilation
Zinc recompiles files when:
Direct changes - The source file itself was modified
API changes - A file’s public API that other files depend on changed
Inheritance changes - A parent class/trait was modified
Macro changes - Macro definitions changed
Signature changes - Type signatures changed
Name Hashing
Zinc uses name hashing to minimize recompilation:
// File A.scala
class A {
def method1 () : Int = 42
def method2 () : String = "hello" // Adding this won't recompile users of method1
}
Zinc tracks which names (methods, fields) are used, not just file-level dependencies.
Name hashing means that adding a new method to a class won’t trigger recompilation of code that doesn’t use it.
Viewing Compilation Statistics
Enable timing information:
showTiming := true
showSuccess := true
Or at runtime:
sbt -Dsbt.task.timings=true compile
Compilation Listeners
Monitor incremental compilation decisions:
import xsbti . compile . RunProfiler
zincCompilationListeners += new RunProfiler {
override def timeCompilation ( run : => Analysis ) : Analysis = {
val start = System .currentTimeMillis()
val result = run
val elapsed = System .currentTimeMillis() - start
println( s "Compilation took $ elapsed ms" )
result
}
}
Optimizing Incremental Compilation
Minimize API changes
Changes to public APIs cause more recompilation. Use private/protected when possible. // Prefer private
private def helper () : Int = 42
// Over public
def helper () : Int = 42 // Will trigger recompilation of dependents
Separate API from implementation
Put stable APIs in separate files from frequently-changing implementations.
Use stable type signatures
Avoid type inference in public APIs: // Explicit type (stable)
def compute () : Int = complexCalculation()
// Inferred type (may change)
def compute () = complexCalculation() // Return type might change
Modularize your code
Split large files into smaller, focused modules to reduce recompilation scope.
Compilation Phases
Zinc integrates with the Scala compiler phases:
// Control compilation order
compileOrder := CompileOrder . Mixed // Default
// Options: JavaThenScala, ScalaThenJava, Mixed
Compile Order Strategies
Order Description Use When MixedCompiles Scala and Java together Default, most flexible JavaThenScalaJava first, then Scala Scala depends on Java ScalaThenJavaScala first, then Java Java depends on Scala
Compiler Bridge
Zinc uses a compiler bridge for each Scala version:
// Compiler bridge source (auto-configured)
scalaCompilerBridgeSource := {
val scalaV = scalaVersion.value
if (scalaV.startsWith( "3." ))
( "org.scala-lang" % "scala3-sbt-bridge" % scalaV)
else
( "org.scala-lang" % "scala2-sbt-bridge" % scalaV)
}
sbt automatically downloads and compiles the appropriate compiler bridge for your Scala version.
Compiler Cache
Zinc caches Scala compiler instances:
// Compiler cache is stored in State
compilerCache := state.value
.get( Keys .stateCompilerCache)
.getOrElse( CompilerCache .fresh)
The cache improves startup time for repeated compilations.
Analysis File Management
Clearing Analysis
Force a clean recompilation:
# Clean compiled files and analysis
sbt clean
# Clean specific configuration
sbt Test/clean
Analysis Compatibility
Analysis files are version-specific:
Incompatible between Zinc versions
Incompatible between Scala versions
Should be regenerated when upgrading
If you upgrade Scala or sbt versions, run sbt clean to remove old analysis files.
Debugging Compilation Issues
Verbose Compilation
Enable detailed logging:
# Verbose output
sbt "set incOptions := incOptions.value.withApiDebug(true)" compile
# Show what gets recompiled
sbt "set logLevel := Level.Debug" compile
Analysis Dumps
Dump API information:
incOptions := incOptions.value.withSettings(
Seq (
incOptions.value.apiDumpDirectory() -> Some (target.value / "api-dumps" )
)
)
Pipelined Compilation
For multi-project builds, enable pipelining:
// Enable pipelining in subprojects
ThisBuild / usePipelining := true
// Export early output for downstream projects
exportPipelining := true
Pipelining allows dependent projects to start compiling before upstream projects finish.
Pipelining is especially beneficial for large multi-project builds.
External Class File Management
Control how class files are managed:
incOptions := incOptions.value.withClassfileManagerType(
Some ( TransactionalManagerType .default)
)
Transactional management:
Backup class files before compilation
Restore on compilation failure
Ensures consistent state
Stamp Management
Zinc uses stamps to detect changes:
// Use hash-based stamping (default)
inputFileStamper := sbt.nio. FileStamper . Hash
// Use timestamp-based stamping
inputFileStamper := sbt.nio. FileStamper . LastModified
Stamper Pros Cons HashAccurate, detects content changes Slower for large files LastModifiedFast May miss changes if timestamp unchanged
Compilation Reporting
Customize compilation progress reporting:
compileProgress := new CompileProgress {
override def startUnit ( phase : String , unitPath : String ) : Unit = {
println( s "Compiling $ unitPath in phase $ phase " )
}
override def advance ( current : Int , total : Int ) : Boolean = {
println( s "Progress: $ current / $ total " )
true // Continue compilation
}
}
Best Practices
Keep API changes minimal
Minimize changes to public APIs to reduce recompilation cascades.
Use explicit types in APIs
Declare return types explicitly in public methods to avoid API changes from type inference.
Clean when needed
Run sbt clean after upgrading Scala or sbt versions.
Leverage pipelining
Enable usePipelining for multi-project builds to improve compilation times.
Monitor compilation
Use -Dsbt.task.timings=true to identify compilation bottlenecks.
Organize code wisely
Separate stable APIs from changing implementations to minimize recompilation.
Troubleshooting
Problem: Too much recompilation
Solution: Check what changed:
sbt "set logLevel := Level.Debug" compile
Problem: Compilation errors after upgrade
Solution: Clean and rebuild:
Problem: Slow incremental compilation
Solution: Check if too many files are recompiling:
incOptions := incOptions.value.withRecompileAllFraction( 0.8 )
Problem: Out of memory during compilation
Solution: Increase heap size: