Scala FAQ: How can I deploy a single “fat” executable JAR file when using Scala and SBT? For example, I want to build a single JAR file and my project has multiple dependencies, which may be managed dependencies or unmanaged dependencies.
Solution
The solution is to use the sbt-assembly plugin. Read the background for more background information, or skip ahead to the “Using sbt-assembly” section for details on how to use sbt-assembly.
NOTE: This solution is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 18.14, “How to Deploy a Single, Executable JAR File.”
Background
The sbt package
command creates a JAR file that includes the class files it compiles from your source code, along with the resources in your project (from src/main/resources), but there are two things it doesn’t include in the JAR file:
- Your project dependencies (JAR files in your project’s lib folder, or managed dependencies declared in build.sbt).
- Libraries from the Scala distribution that are needed to execute the JAR file with the java command.
This makes it difficult to distribute a single, executable JAR file for your application. There are three things you can do to solve this problem:
- Distribute all the JAR files necessary with a script that builds the classpath and executes the JAR file with the
scala
command. This requires that Scala be installed on client systems. - Distribute all the JAR files necessary (including Scala libraries) with a script that builds the classpath and executes the JAR file with the java command. This requires that Java is installed on client systems.
- Use an SBT plug-in such as sbt-assembly to build a single, complete JAR file that can be executed with a simple
java
command. This requires that Java is installed on client systems.
This solution focuses on the third approach. The first two approaches are examined in the Discussion.
Using sbt-assembly
The installation instructions for sbt-assembly will probably change, but at the time of this writing (May, 2018), just create a file named assembly.sbt in your SBT project directory with this line:
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
After that, just reload your SBT project. That’s all you have to do. (It used to be harder than this.)
See the sbt-assembly project documentation for the latest version and configuration information.
Now run sbt assembly
to create your single, executable JAR file:
$ sbt assembly
When the assembly task finishes running it will tell you where the executable JAR file is located. For instance, when packaging my Blue Parrot application, SBT prints the following lines of output that show the dependencies sbt-assembly is including, and the location of the final JAR file:
[info] Including akka-actor-2.0.1.jar
[info] Including scala-library.jar
[info] Including applescriptutils_2.9.1-1.0.jar
[info] Including forms-1.0.7.jar
[info] Including sounds_2.9.1-1.0.jar
[info] Packaging target/BlueParrot-assembly-1.0.jar ...
[info] Done packaging.
The sbt-assembly plug-in works by copying the class files from your source code, the class files from your dependencies, and the class files from the Scala library into one single JAR file that can be executed with the java interpreter. This can be important if there are license restrictions on a JAR file, for instance.
As noted, there are other plug-ins to help solve this problem, including One-JAR, but sbt-assembly worked best with several applications I’ve deployed as single, executable JAR files.
Discussion
A JAR file created by SBT can be run by the Scala interpreter, but not the Java interpreter. This is because class files in the JAR file created by sbt package
have dependencies on Scala class files (Scala libraries), which aren’t included in the JAR file SBT generates. This is easily demonstrated.
First, create an empty SBT project directory. (See Recipe 18.1 for easy ways to do this.) Then place the following code in a file named Main.scala in the root directory of the project:
package foo.bar.baz
object Main extends App {
println("Hello, world")
}
Next, run sbt package
to create the JAR file:
$ sbt package
[info] Loading global plugins from /Users/Al/.sbt/plugins
[info] Done updating.
[info] Compiling 1 Scala source to target/scala-2.10/classes...
[info] Packaging target/scala-2.10/basic_2.10-1.0.jar ...
[info] Done packaging.
[success] Total time: 6 s
Now attempt to run the JAR file with the java -jar
command. This will fail:
$ java -jar target/scala-2.10/basic_2.10-1.0.jar
Exception in thread "main" java.lang.NoClassDefFoundError: scala/App
at java.lang.ClassLoader.defineClass1(Native Method)
... 32 more
This fails because the Java interpreter doesn’t know where the scala/App trait is. Next, demonstrate that you can run the same JAR file with the Scala interpreter:
$ scala target/scala-2.10/basic_2.10-1.0.jar
Hello, world
This works fine.
For the Java interpreter to run your JAR file, it needs the scala-library.jar file from your Scala installation to be on its classpath. You can get this example to work with Java by including that JAR file on its classpath with this command:
$ java -cp "${CLASSPATH}:${SCALA_HOME}/lib/scala-library.jar:target/scala-2.10/basic_2.10-1.0.jar" foo.bar.baz.Main
Hello, world
As shown, adding the scala-library.jar file lets the Java interpreter find the scala/App trait (which is a normal Java class file), which lets it run the application successfully for you.
This is part of the work that sbt-assembly performs for you. It repackages the class files from ${SCALA_HOME}/lib/scala-library.jar
into your single, executable JAR file, and does the same thing with your other project dependencies. Note that if your application is more complicated, it may need additional JAR files from the ${SCALA_HOME}/lib
directory.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
See Also
- The sbt-assembly project
- My Blue Parrot application is written in Scala, and packaged with SBT and sbt-assembly
- The One-JAR project