How to create an executable JAR file with `scalac` (and run it with `scala`)

If you’re ever working on a really small Scala project — something that contains only a few source code files — and don’t want to use SBT to create a JAR file, you can do it yourself manually. Let’s look at a quick example. Note that the commands below work on Mac and Linux systems, and should work on Windows with minor changes.

Back to top

Example

Given a file named Hello.scala with these contents:

object Hello extends App {
    println("Hello, world")
}

you can compile that source code file directly to a JAR file like this:

$ scalac Hello.scala -d Hello.jar

That creates Hello.jar, whose contents you can look at like this:

$ jar tvf Hello.jar
    76 Mon Apr 15 18:45:56 MDT 2019 META-INF/MANIFEST.MF
  2554 Mon Apr 15 18:45:56 MDT 2019 Hello$.class
   753 Mon Apr 15 18:45:56 MDT 2019 Hello.class
   751 Mon Apr 15 18:45:56 MDT 2019 Hello$delayedInit$body.class

The META-INF/MANIFEST.MF file contains these contents:

Manifest-Version: 1.0
Scala-Compiler-Version: 2.12.8
Main-Class: Hello

Now you can run/execute the JAR file with the scala command like this:

$ scala Hello.jar

That produces the expected output:

Hello, world

That’s all there is to it. The key to this solution is knowing that the scalac “-d” option can be used to create an executable JAR fie.

Back to top

A more complex example

As a more complicated example I just did a similar thing by creating a one-object application that relies on two JAR files. The commands I used are the same, except for adding in the classpath complexity. Assuming I start with a Scala source code file named Foo.scala and want to create a JAR file named Foo.jar, the scalac command to create the JAR file looks like this:

$ scalac -classpath "sed_2.12-0.1.jar:kaleidoscope_2.12-0.1.0.jar" Foo.scala -d Foo.jar

I can then run the application with the scala command like this:

$ scala -classpath "sed_2.12-0.1.jar:kaleidoscope_2.12-0.1.0.jar:Foo.jar" Foo

I should mention that the Foo.scala file has source code that begins like this:

object Foo extends App ...

It’s not in a package, so I refer to Foo at the end of the scala command (as opposed to something like my.package.Foo).

Back to top

Problems?

A few people wrote to say that this technique didn’t work for them. It worked on several examples for me, and today (May 12, 2019) I had my first problem with it. My problem seems to be that it generated an empty META-INF/MANIFEST.MF file. I tried a couple of things to get this to work, but I’m out of time for today; I’ll work on it again when I have more time.

Another thing I just noticed (in July, 2019) is that another JAR file didn’t run correctly. When I tried to run it I got this error:

$ scala RenumberAllMdFiles.jar RenumberAllMdFiles
java.lang.NullPointerException
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at scala.reflect.internal.util.ScalaClassLoader.$anonfun$tryClass$1(ScalaClassLoader.scala:52)
    at scala.util.control.Exception$Catch.$anonfun$opt$1(Exception.scala:246)
    at scala.util.control.Exception$Catch.apply(Exception.scala:228)
    at scala.util.control.Exception$Catch.opt(Exception.scala:246)
    at scala.reflect.internal.util.ScalaClassLoader.tryClass(ScalaClassLoader.scala:52)
    at scala.reflect.internal.util.ScalaClassLoader.tryToInitializeClass(ScalaClassLoader.scala:48)
    at scala.reflect.internal.util.ScalaClassLoader.tryToInitializeClass$(ScalaClassLoader.scala:48)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.tryToInitializeClass(ScalaClassLoader.scala:132)
    at scala.reflect.internal.util.ScalaClassLoader.run(ScalaClassLoader.scala:99)
    at scala.reflect.internal.util.ScalaClassLoader.run$(ScalaClassLoader.scala:98)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:132)
    at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:28)
    at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:27)
    at scala.tools.nsc.JarRunner$.run(MainGenericRunner.scala:21)
    at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:35)
    at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:34)
    at scala.tools.nsc.JarRunner$.runAndCatch(MainGenericRunner.scala:21)
    at scala.tools.nsc.JarRunner$.runJar(MainGenericRunner.scala:33)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:79)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:92)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:103)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:108)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

I recognized that error message, and knew that I probably needed to add the scala-library.jar file to my classpath, so I ran the command again like this and it worked fine:

$ scala -cp $SCALA_HOME/lib/scala-library.jar:RenumberAllMdFiles.jar RenumberAllMdFiles

At the moment I can’t remember why that’s required, but I can tell you that it fixes the problem.

... this post is sponsored by my books ...
 

Back to top

Summary

If you ever need to create a JAR file with scalac and then run that JAR file with scala, I hope these examples are helpful.

Back to top