Background and Architectural Context
SBT uses either Ivy (legacy) or Coursier (modern) as its dependency resolver. In enterprise systems with dozens of internal libraries and snapshot dependencies, the build graph can span hundreds of artifacts. Complex interactions occur when multiple resolvers—such as Artifactory, Nexus, and Maven Central—are queried simultaneously, sometimes through slow or misconfigured network proxies.
Why This Matters at Scale
- Resolution delays directly impact CI/CD throughput.
- Classpath size affects incremental compilation speed and JVM startup time.
- Misconfigured resolvers can cause repeated, redundant fetch attempts.
Diagnostics and Root Cause Analysis
Step 1: Enable Detailed Dependency Resolution Logging
Run the build with sbt -Dsbt.log.noformat=true -debug update
to trace resolver activity. Look for repeated fetch attempts or long pauses on certain resolvers.
Step 2: Identify Bottlenecks
In large organizations, slowdowns often occur due to authentication prompts, SSL handshake delays, or timeouts from private repositories. Capture network traces if needed to confirm.
Step 3: Inspect Dependency Graph
Use dependencyTree
or coursier resolve --tree
to identify duplicate artifacts, conflicting versions, or circular dependency patterns.
// Enable Coursier and inspect resolution ThisBuild / useCoursier := true dependencyTree
Common Pitfalls
- Mixing Ivy and Coursier resolvers without understanding resolution precedence.
- Using dynamic versions (
1.+
) in dependencies, forcing frequent cache invalidation. - Failing to prune unused transitive dependencies.
Step-by-Step Resolution
1. Configure Resolver Ordering
Place the fastest and most reliable internal resolver first to reduce external fetch attempts.
resolvers := Seq( "Internal Releases" at "https://repo.corp.local/artifactory/releases", Resolver.mavenCentral )
2. Lock Dependency Versions
Use dependencyOverrides
or a lockfile to pin versions and avoid resolution churn.
dependencyOverrides += "org.scala-lang" % "scala-library" % "2.13.12"
3. Limit Classpath Size
Split monolithic projects into smaller modules and exclude unnecessary transitive dependencies.
excludeDependencies += ExclusionRule("org.slf4j", "slf4j-log4j12")
4. Cache Dependencies Locally
Ensure that CI agents have a pre-warmed Ivy/Coursier cache to avoid repeated downloads.
Best Practices for Long-Term Stability
- Standardize on either Coursier or Ivy across the organization.
- Audit dependency trees quarterly for conflicts and size reduction opportunities.
- Monitor resolver health and latency as part of build observability.
- Document and enforce dependency version policies.
Conclusion
Dependency resolution bottlenecks in SBT can cripple productivity at enterprise scale. By streamlining resolver configurations, locking versions, managing classpath size, and implementing robust caching strategies, organizations can significantly reduce build times and improve overall developer experience.
FAQs
1. Why does my SBT build hang during dependency resolution?
It often results from slow or unreachable resolvers, authentication delays, or conflicting version resolution between repositories.
2. Can switching to Coursier instantly improve performance?
Yes, in many cases, but only if resolver ordering and dependency constraints are well-defined. Coursier's parallel fetching is faster but still subject to network bottlenecks.
3. How big is too big for a classpath?
Once classpath length approaches OS command-line limits or JVM startup slows noticeably, it's time to modularize and prune dependencies.
4. What's the best way to handle internal and external dependencies?
Prioritize internal resolvers for internal artifacts, with fallback to external repositories. This minimizes network latency and avoids proxy delays.
5. Should I disable dynamic dependency versions in SBT?
Yes, in enterprise builds dynamic versions cause frequent resolution and can lead to unexpected version drift. Pinning ensures reproducibility and faster builds.