This page introduces multiple subprojects in a single build.
Please read the earlier pages in the Getting Started Guide first, in particular you need to understand build.sbt before reading this page.
It can be useful to keep multiple related subprojects in a single build, especially if they depend on one another and you tend to modify them together.
Each subproject in a build has its own source directories, generates its own jar file when you run package, and in general works like any other project.
A project is defined by declaring a lazy val of type Project. For example, :
lazy val util = (project in file("util")) lazy val core = (project in file("core"))
The name of the val is used as the subproject’s ID, which is used to refer to the subproject at the sbt shell.
Optionally the base directory may be omitted if it’s the same as the name of the val.
lazy val util = project lazy val core = project
To factor out common settings across multiple projects,
create a sequence named
commonSettings and call
on each project.
lazy val commonSettings = Seq( organization := "com.example", version := "0.1.0-SNAPSHOT", scalaVersion := "2.12.6" ) lazy val core = (project in file("core")) .settings( commonSettings, // other settings ) lazy val util = (project in file("util")) .settings( commonSettings, // other settings )
Now we can bump up
version in one place, and it will be reflected
across subprojects when you reload the build.
Another a bit advanced technique for factoring out common settings
across subprojects is to define the settings scoped to
ThisBuild. (See Scopes)
Projects in the build can be completely independent of one another, but usually they will be related to one another by some kind of dependency. There are two types of dependencies: aggregate and classpath.
Aggregation means that running a task on the aggregate project will also run it on the aggregated projects. For example,
lazy val root = (project in file(".")) .aggregate(util, core) lazy val util = (project in file("util")) lazy val core = (project in file("core"))
In the above example, the root project aggregates
up sbt with two subprojects as in the example, and try compile. You
should see that all three projects are compiled.
In the project doing the aggregating, the root project in this case,
you can control aggregation per-task. For example, to avoid aggregating
lazy val root = (project in file(".")) .aggregate(util, core) .settings( update / aggregate := false ) [...]
update / aggregate is the aggregate key scoped to the
update task. (See
Note: aggregation will run the aggregated tasks in parallel and with no defined ordering between them.
A project may depend on code in another project. This is done by adding
dependsOn method call. For example, if core needed util on its
classpath, you would define core as:
lazy val core = project.dependsOn(util)
Now code in
core can use classes from
util. This also creates an
ordering between the projects when compiling them;
util must be updated
and compiled before core can be compiled.
To depend on multiple projects, use multiple arguments to
core dependsOn(util) means that the
compile configuration in
compile configuration in
util. You could write this explicitly as
dependsOn(util % "compile->compile").
"compile->compile" means “depends on” so
test configuration in
core would depend on the
->config part implies
dependsOn(util % "test") means that the
test configuration in
Compile configuration in
A useful declaration is
"test->test" which means
test depends on
This allows you to put utility code for testing in
and then use that code in
core/src/test/scala, for example.
You can have multiple configurations for a dependency, separated by
semicolons. For example,
dependsOn(util % "test->test;compile->compile").
On extremely large projects with many files and many subprojects, sbt can perform less optimally at watching files have changed during interactively and using a lot of disk and system I/O.
settings. These can be used to control whether to trigger compilation
of a dependent subprojects when you call
compile. Both keys will
take one of three values:
TrackLevel.TrackAlways. By default
they are both set to
trackInternalDependencies is set to
TrackLevel.TrackIfMissing, sbt will no longer try to compile
internal (inter-project) dependencies automatically, unless there are
*.class files (or JAR file when
true) in the
When the setting is set to
TrackLevel.NoTracking, the compilation of
internal dependencies will be skipped. Note that the classpath will
still be appended, and dependency graph will still show them as
dependencies. The motivation is to save the I/O overhead of checking
for the changes on a build with many subprojects during
development. Here’s how to set all subprojects to
lazy val root = (project in file(".")). aggregate(....). settings( inThisBuild(Seq( trackInternalDependencies := TrackLevel.TrackIfMissing, exportJars := true )) )
exportToInternal setting allows the dependee subprojects to opt
out of the internal tracking, which might be useful if you want to
track most subprojects except for a few. The intersection of the
exportToInternal settings will be
used to determine the actual track level. Here’s an example to opt-out
lazy val dontTrackMe = (project in file("dontTrackMe")). settings( exportToInternal := TrackLevel.NoTracking )
If a project is not defined for the root directory in the build, sbt creates a default one that aggregates all other projects in the build.
hello-foo is defined with
base = file("foo"), it will be
contained in the subdirectory foo. Its sources could be directly under
foo/Foo.scala, or in
foo/src/main/scala. The usual sbt
directory structure applies underneath
foo with the
exception of build definition files.
.sbt files in
foo/build.sbt, will be merged with the build
definition for the entire build, but scoped to the
If your whole project is in hello, try defining a different version
version := "0.6") in
show version at the sbt interactive prompt. You
should get something like this (with whatever versions you defined):
> show version [info] hello-foo/*:version [info] 0.7 [info] hello-bar/*:version [info] 0.9 [info] hello/*:version [info] 0.5
hello-foo/*:version was defined in
hello-bar/*:version was defined in
hello/*:version was defined in
hello/build.sbt. Remember the
syntax for scoped keys. Each
version key is scoped to a
project, based on the location of the
build.sbt. But all three
are part of the same build definition.
.sbtfiles in the base directory of that project, while the
.scalafile can be as simple as the one shown above, listing the projects and base directories. There is no need to put settings in the
You may find it cleaner to put everything including settings in
files in order to keep all build definition under a single project
directory, however. It’s up to you.
You cannot have a project subdirectory or
project/*.scala files in the
foo/project/Build.scala would be ignored.
At the sbt interactive prompt, type
projects to list your projects and
project <projectname> to select a current project. When you run a task
compile, it runs on the current project. So you don’t necessarily
have to compile the root project, you could compile only a subproject.
You can run a task in another project by explicitly specifying the
project ID, such as
The definitions in
.sbt files are not visible in other
.sbt files. In
order to share code between
.sbt files, define one or more Scala files
project/ directory of the build root.
See organizing the build for details.