# One and Done: Embrace single-file JVM apps for speedy development

# Introduction

Besides traditional build tools like Maven, Gradle, and SBT, developers might desire quicker code-to-execution cycles, as seen in straightforward scripting situations. Although Java 9 introduced JShell and [JEP 330 (Launch Single-File Source-Code Programs)](https://openjdk.java.net/jeps/330), neither option supports downloading and caching dependencies. Alternative solutions include the Groovy language, known for its scripting capabilities and [Grapes](http://docs.groovy-lang.org/latest/html/documentation/grape.html), which enables the swift addition of Maven repository dependencies to the classpath. Other notable options are [Kscript](https://github.com/kscripting/kscript) for Kotlin and [Ammonite-REPL](https://ammonite.io/) for Scala.

In the given context, [JBang](https://www.jbang.dev/) and [Scala-CLI](https://scala-cli.virtuslab.org/) offer **more than just scripting** for Java, Scala, Groovy, or Kotlin. They enable quick starts with minimal setup, download dependencies from Maven, install the required JDK version, and work with single-file JVM apps or **single-module projects**. They are suitable for serverless lambdas, basic web applications, and utilizing frameworks such as [Spring Boot](https://spring.io/), [Quarkus](https://quarkus.io/), [ZIO](https://zio.dev/), and more.

# Installation

Installing both [JBang](https://www.jbang.dev/documentation/guide/latest/installation.html) and [Scala-CLI](https://scala-cli.virtuslab.org/install) is simple. Choose your preferred platform: Windows, MacOS, or Linux, and proceed with the installation using SDKMan, Homebrew, Chocolatey, Scoop, or manually through cURL.

# Creating a single-file JVM application

## JBang

JBang is a command-line tool that simplifies running Java applications without complex build systems or project setups.

Below is a Hello World Quarkus app, easily compiled, run, and packaged for deployment.

```java
//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17
//DEPS io.quarkus.platform:quarkus-bom:3.0.1.Final@pom
//DEPS io.quarkus:quarkus-resteasy-reactive
//JAVAC_OPTIONS -parameters
//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager

import io.quarkus.runtime.Quarkus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.logging.Logger;

@Path("/hello")
@ApplicationScoped
public class MyApp {
    @GET
    public String sayHello() {
        return "Hello world!";
    }

    public static void main(String[] args) {
        Quarkus.run(args);
    }
}
```

For more information on the Javac and Java options used in the example above, please refer to [this article](https://quarkus.io/guides/scripting).

### Running the application

`jbang run MyApp.java`

The command above runs the app, fetching dependencies and installing the JDK if necessary.

Run in your browser: `http://localhost:8080/hello`.

### Packaging the application

`jbang export fatjar MyApp.java`

The command packages the app as a JAR.

## Scala-CLI

Similar to JBang, Scala-CLI is an excellent command-line tool for education, scripting, prototyping, and experimentation. It is not only suitable for playgrounds but also ideal for single-module projects such as serverless lambdas, web application microservices, and more. Primarily, it is a tool to interact with the Scala language; however, it also supports running Java code, so nothing prevents you from using it for that purpose.

It is worth mentioning that, currently, Scala-CLI is in the process of becoming the official Scala runner.

Here is the equivalent ZIO app from the previous chapter.

```scala
//> using jvm "17"
//> using dep "dev.zio::zio-http:3.0.0-RC1"
//> using scala "3.2.2"

import zio.*
import zio.http.*

object MyApp extends ZIOAppDefault:

  val app: App[Any] = 
    Http.collect[Request] {
      case Method.GET -> !! / "hello" => Response.text("Hello world!")
    }

  override val run =
    Server.serve(app).provide(Server.default)
```

If necessary, you can [configure it as a Unix shebang](https://scala-cli.virtuslab.org/docs/reference/commands/#shebang) by adding a line that specifies the interpreter for [executing the script](https://scala-cli.virtuslab.org/docs/guides/scripts/#self-executable-scala-script), e.g.: `#!/usr/bin/env scala-cli` .

### Running the application

`scala-cli run MyApp.scala`

The command runs the app, fetching dependencies and installing JDK if necessary.

Run in your browser: `http://localhost:8080/hello`.

### Packaging the application

`scala-cli --power package MyApp.scala --assembly --preamble=false`

The command packages the app as a JAR.

You might have observed some [extra flags](https://scala-cli.virtuslab.org/docs/reference/cli-options/). The `power` option enables advanced features; however, it's possible to enable all features and [activate them globally](https://scala-cli.virtuslab.org/docs/reference/scala-command/).

The `preamble` setting determines whether to include a bash/bat preamble in the assembly JAR.

## **Building a Docker image**

In the case of JBang, you can easily build a Docker image by creating a simple `Dockerfile`, an example of which can be found [here](https://maciejwalkowiak.com/blog/single-file-java-with-jbang/#building-a-dockerimage-for-jbang-application). With Scala-CLI, it's [even simpler](https://scala-cli.virtuslab.org/docs/commands/package/#docker-container); just use the command-line option `--docker`.

Example:

`scala-cli --power package --docker MyApp.scala --docker-image-repository hello-docker`

Such a Docker image can be run with: `docker run hello-docker:latest` .

## Packaging the application as a platform-specific executable

If you have the native-image binary installed and `GRAALVM_HOME` configured, you can build and run the native executable using the command:

`jbang --native MyApp.java`

In the case of Scala-CLI, it's slightly easier, as there are no prerequisites.

Simply run:

`scala-cli --power package MyApp.scala --native-image`

The Scala CLI [automatically downloads and unpacks](https://scala-cli.virtuslab.org/docs/commands/package/#native-image) a GraalVM distribution.

Of course, just like any other Native Image application, you must provide a `reflect-config.json` file if necessary.

A simpler alternative for creating platform-specific executables is available with [Scala Native](https://scala-cli.virtuslab.org/docs/commands/package/#scala-native), which removes the need for Native Image. However, this option won't be suitable if you're using Java dependencies or Scala ones without native target support. Also, check out the [other available targets](https://scala-cli.virtuslab.org/docs/commands/package/) you can use without complex configurations simply by utilizing command-line options or directives in the file.

# **Single-module projects**

As mentioned it's also a great choice for simple applications like microservices or serverless lambda functions.

JBang did not support multiple source files for quite some time, but now it is listed as [one of its features](https://www.jbang.dev/documentation/guide/latest/index.html#features). From its early days, Scala-CLI has offered support for this feature. I will concentrate on this in a separate article. For now, you can explore an example project scaffold from the [Tapir project initializer](https://adopt-tapir.softwaremill.com/), where Scala-CLI shows as an option alongside SBT.

# IDE integration

In the case of Scala-CLI, to initialize IDE integration ([IntelliJ](https://scala-cli.virtuslab.org/docs/cookbooks/intellij/) or [VSCode](https://scala-cli.virtuslab.org/docs/cookbooks/vscode/)), run the command `scala-cli setup-ide` in the project directory. This command generates all the necessary configurations for IDE support, enabling the option to import the project. On the other hand, if one has used [basic commands](https://scala-cli.virtuslab.org/docs/commands/basics) such as: `run`, `compile`, or `test`, there is no need to execute the `setup-ide` command.

I wish IntelliJ simply offered an option to "import the project as" without requiring the execution of an additional shell command. Perhaps the future will introduce a plugin, similar to the [one available for JBang](https://plugins.jetbrains.com/plugin/18257-jbang). The extension [for VSCode](https://github.com/jbangdev/jbang-vscode) also exists.

# Conclusion

The article above gives an overview of unique tools that streamline rapid interaction with JVM-based languages, removing the necessity for preconfiguring a working environment. These solutions excel in educational contexts, swift testing or prototyping, experimentation, or error reproduction associated with particular language versions and dependencies. Moreover, they can be accessible alternatives to sophisticated build tools in production use, especially for simple applications.
