1. Overview

In both Java and Scala, we can package our applications by aggregating all the source code and resources into a single JAR (Java ARchive) file for the ease of distribution.

In this tutorial, we’ll learn what a fat JAR is, how to build it using SBT, and how we can customize it.

2. What Is a Fat JAR?

When we run the sbt package command, the SBT’s native packager will create a thin JAR file for us, packing all the application source code and resources. This package will only contain our local files and none of its dependencies.

On the other hand, a fat JAR, also known as an uber JAR, is built with all of the project dependencies included.

The advantage of using a fat JAR is that we can distribute it to any destination, regardless of whether the project dependencies are present in the running environment or not.

3. The sbt-assembly Plugin

The sbt-assembly plugin is an SBT plugin for building a single independent fat JAR file with all dependencies included. This is inspired by the popular Maven assembly plugin, which is used to build fat JARs in Maven.

To install the sbt-assembly plugin in our project, we’ll first add the plugins.sbt file under the project folder. Once we add the file, the project structure will look like:

project-root
 |----build.sbt
 |----src
 |----project
	  |----plugins.sbt
	  |----build.properties

Next, let’s add the sbt-assembly plugin to our plugins.sbt file:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")

Now, we’re ready to execute the assembly task. Let’s navigate to the project root folder and run the assembly command:

> sbt assembly

This will generate a fat JAR, under the target folder, that is ready to be deployed to the server:

target/scala-2.12/scala-sbt-assembly-1.0.jar

We can change the default name of the JAR file by setting the property assemblyJarName in our build.sbt file:

assemblyJarName in assembly := "baeldung-scala-sbt-assembly-fatjar-1.0.jar"

Sometimes, we may not want certain libraries to be part of our JAR file. In that case, we can mark that dependency as “provided” in our build.sbt file.

The “provided” keyword indicates that the dependency is provided by the runtime, so there’s no need to include it in the JAR file.

Let’s look at an example of a Spark application where we’re marking the “spark-core” library as “provided” in the build.sbt file:

libraryDependencies += "org.apache.spark" %% "spark-core" % sparkVersion % "provided"

When using sbt-assembly, we may encounter an error caused by the default deduplicate merge strategy. In most cases, this is caused by files in the META-INF directory.

We can resolve such merge issues by choosing the appropriate merge strategy for the paths causing the error. In this case, we’ll use the discard strategy for the META-INF path:

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

The sbt-assembly plugin offers various types of merge strategies that we can use for different situations.

4. Conclusion

In this article, we learned how to create a fat JAR using SBT and the sbt-assembly plugin.

Though fat JAR files make our deployment process easier, adding libraries that have many other dependencies can lead to problems. Therefore, we should always be careful when adding dependencies to our projects and try to make them “provided” whenever possible.

As always, the code can be found over on GitHub.

Comments are closed on this article!