# One and Done [part 2]: Industry's adoption of self-contained JVM applications

# **Introduction**

In the [previous article](https://blog.lambdaspot.dev/one-and-done-embrace-single-file-jvm-apps-for-speedy-development), I briefly introduced tools that enable the creation of simple scripts using languages from the JVM family, focusing on two of them that offer much more: [JBang](https://www.jbang.dev/) and [Scala-CLI](https://scala-cli.virtuslab.org/). In this article, I would like to showcase a few examples of how these tools can be applied in the industry to simplify the maintenance of existing products and the development of new ones.

# **Exploring serverless Lambda functions**

The aforementioned tools excel in creating simple scripts, command-line applications, basic web applications, and serverless Lambda functions. Let's focus on the latter proposition and examine a few examples.

## **Creating a Lambda function with JBang**

Let's explore a simple JBang example by creating an AWS Lambda function using [the freshly-released Java 17 runtime](https://aws.amazon.com/blogs/compute/java-17-runtime-now-available-on-aws-lambda/). This demonstrates how we can focus on the handler implementation without sophisticated tools like Maven, Gradle, or SBT, and easily build a deployment package. Dependencies and configurations are declared using directives in the file header.

```java
//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17
//DEPS com.amazonaws:aws-lambda-java-core:1.2.2
//DEPS com.amazonaws:aws-lambda-java-events:3.11.1
//SOURCES model/Person.java

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import java.util.Map;
import model.Person;

public class MyApp implements RequestHandler<Person, APIGatewayProxyResponseEvent> {
    @Override
    public APIGatewayProxyResponseEvent handleRequest(Person person, Context context) {
        context.getLogger().log("Request received: " + person + "\n");

        return new APIGatewayProxyResponseEvent()
                .withStatusCode(200)
                .withHeaders(Map.of("Content-Type", "text/plain"))
                .withBody("Hello " + person.name() + "!");
    }
}
```

In the example above, we observe an interesting aspect: importing `model.Person` (Notice also the use of the `SOURCES` directive). We're not limited to single-file applications and can import additional source files as needed. In [Person.java](http://Person.java), we feature the `record` as [AWS officially supports Java 17](https://aws.amazon.com/blogs/compute/java-17-runtime-now-available-on-aws-lambda/), a blazing hot addition to the platform. The new runtime enables using records as request event models, deserializing them on-the-fly, simplifying implementation, and providing a more direct approach.

```java
package model;

public record Person(String name, int age) {}
```

### **Building the JAR**

Now, all that remains is to build the JAR, so we can then deploy it (`sam deploy --guided`) or test it locally (`sam local invoke`). In the CloudFormation template, we specify the path to Jar (CodeUri), runtime, and point to the handler method. Voilà!

```bash
jbang export fatjar MyApp.java
```

Our `template.yml` looks as follows.

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
  HelloFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      CodeUri: MyApp-fatjar.jar
      Handler: 'MyApp::handleRequest'
      Runtime: java17
      Description: 'Hello Java 17 in the cloud'
      MemorySize: 1769
      Timeout: 15
```

### Testing

Now, let's trigger the Lambda using SAM-CLI, and in response, we'll receive the expected JSON.

```bash
echo '{"name": "John Doe", "age": 44}' | sam local invoke --event - "HelloFunction"
```

Should show:

```json
{
  "statusCode": 200,
  "headers": {
    "Content-Type": "text/plain"
  },
  "body": "Hello John Doe!"
}
```

A significant part of this paragraph focuses on testing the result using AWS, while the essential implementation was completed in the first step. It's that simple.

## **Creating a Lambda function with Scala-CLI**

As described in the previous paragraph, Scala-CLI enables direct implementation of serverless functions and building deployment packages.

In this case, we don't have to place the record (`case class`) in a separate file, as Scala allows for multiple classes within a single source file. However, if the model were more complex, we could put it in a separate file and import it as in the previous example. Here, we don't need to use a directive to include the file, as was the case with JBang.

Our entire example application boils down to just one file.

```scala
//> using scala "3"
//> using jvm "17"
//> using repository "jitpack"
//> using dep "com.github.lambdaspot:aws-lambda-scala-bridge:0.1.5"
//> using dep "com.amazonaws:aws-lambda-java-core:1.2.2"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core:2.23.0"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:2.23.0"

import com.amazonaws.services.lambda.runtime.Context
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
import dev.lambdaspot.aws.lambda.core.*
import dev.lambdaspot.aws.lambda.events.ApiGatewayProxiedResponse

import scala.util.{Success, Try}

// AWS Lambda function handler
object MyApp extends AwsLambdaEntryPoint:
  override lazy val entryPoint =
    new AwsLambda[PersonDto, ApiGatewayProxiedResponse]:

      override def run(person: PersonDto, context: Context): Try[ApiGatewayProxiedResponse] =
        context.getLogger.log(s"Request received: $person\n")
        Success(
          ApiGatewayProxiedResponse(
            statusCode = 200,
            headers = Map("Content-Type" -> "text/plain"),
            body = Some(s"Hello ${person.name}!")
          )
        )

// Request object
final case class PersonDto(name: String, age: Int)
object PersonDto:
  given JsonValueCodec[PersonDto] = JsonCodecMaker.make
```

### **Building the JAR**

Now, let's build the JAR. The above code uses Scala-CLI directives, available starting from 1.0.0-RC1.

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

### Testing

All set, now we create the CloudFormation template, with only a minor difference compared to the previous paragraph. It concerns the path to the Jar and specifying the handler method.

```yaml
      CodeUri: MyApp.jar
      Handler: 'MyApp::apply'
```

Let's now trigger the Lambda using SAM-CLI, following the same steps as in the previous paragraph. We can expect to receive the same JSON response.

Once again, the entire process of creating a serverless application is amazingly straightforward!

**Digression:** A few words about the [`aws-lambda-scala-bridge`](https://github.com/lambdaspot/aws-lambda-scala-bridge) used in the above example. It is a [micro-library](https://github.com/lambdaspot/aws-lambda-scala-bridge), or rather a thin wrapper, created specifically for this blog post. It demonstrates how easy it is to create a bridge to use a different JSON serializer than the default Jersey. Here, we use Jsoniter-Scala, which [outperforms all available serializers in the JVM](https://github.com/kostya/benchmarks/tree/master#json), ranking among the top C++ ones. [Learn more about Jsoniter-Scala here](https://blog.lambdaspot.dev/the-fastest-and-safest-json-parser-and-serializer-for-scala).

## **Creating a GraalVM Native Image AWS Lambda function**

We will now focus on building a [native executable](https://www.graalvm.org/latest/reference-manual/native-image/) file, which helps reduce the resource consumption of our application and mitigates the issue of Cold Starts. However, we must not forget that using tools like Scala-CLI or JBang, due to their simplicity, is an excellent way to experiment. In this example, we will use Scala-CLI to compile Java code into a native executable file.

Importantly, this example also demonstrates the ability to create simple, multi-file, self-contained applications. We are dealing here with GraalVM Native Image, which requires additional configurations, such as `reflect-config.json`, etc.

Here is the code for the native Java application, using Scala-CLI. The solution is based on [Lambda runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html).

```java
//> using jvm "graalvm-java17:22.3.2"
//> using dep "com.amazonaws:aws-lambda-java-core:1.2.2"
//> using dep "com.amazonaws:aws-lambda-java-events:3.11.1"
//> using dep "com.amazonaws:aws-lambda-java-runtime-interface-client:2.3.2"
//> using mainClass "com.amazonaws.services.lambda.runtime.api.client.AWSLambda"
//> using resourceDir "../resources"

package helloworld;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.*;

public class MyApp implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
    @Override
    public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
        return APIGatewayV2HTTPResponse.builder()
                .withStatusCode(200)
                .withBody("Hello world!")
                .build();
    }
}
```

The above is a "Hello World" AWS Lambda function in Java. What is important is the resources directory, which contains the [standard configurations for Native Image](https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/#embedding-a-configuration-file).

*One surprising thing is the use of* `../` *instead of* `./` *when referring to resources. In the case of Scala sources, the latter works. This is also the case according to the* [*documentation*](https://scala-cli.virtuslab.org/docs/reference/directives/#resource-directories)*. I will create a GitHub issue to confirm whether this is intentional.*

### **Building the executable**

Alright, the application is ready. Now it's time to build the image. Do we need to install anything? GraalVM? Native Image?

JBang requires preliminary installation and configuration (`GRAALVM_HOME`, etc). In the case of Scala-CLI, everything happens automatically, and we just need to run the following command.

```bash
scala-cli --power package MyApp.java --native-image -o dist/native
```

The built executable will land in the `dist` directory with the name `native`.

If you're using Linux, the above is enough. If you're using macOS or Windows, you must utilize Docker to build a Linux-based native image. However, there is no need to create a `Dockerfile` manually, as you can also utilize Scala-CLI's [Docker image](https://github.com/VirtusLab/scala-cli/blob/main/website/docs/release_notes.md#added-support-for-packaging-native-images-from-docker) for this purpose.

```bash
docker pull virtuslab/scala-cli
docker run --rm -v $(pwd)/MyApp.java:/MyApp.java -v $(pwd)/dist:/dist -v $(pwd)/resources:/resources virtuslab/scala-cli --power package --native-image -o dist/native /MyApp.java
```

You can find the files generated in the `dist` directory.

### Testing

Now we need to zip (`zip -j dist/package.zip dist/*`) the bootstrap and executable file, and we're ready to go.

Testing an AWS Lambda function that uses a custom runtime locally requires some sophisticated steps. To conduct local testing, it is necessary to utilize the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-runtime-interface-client/README.md#local-testing), which simulates the AWS Lambda environment and allows for testing of your native functions. We can always upload the [zipped package](https://github.com/lambdaspot/tutorials/tree/main/aws-lambda-scala-cli-java-graalvm#preparing-deployment-package) to the cloud using AWS Lambda Console.

Next, invoke the function, and see the result:

```json
{
  "statusCode": 200,
  "body": "Hello world!",
  "isBase64Encoded": false
}
```

The CloudFormation template stub will be as below. We can then use SAM-CLI and `sam deploy --guided`.

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
  HelloFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      CodeUri: package.zip
      Handler: 'helloworld.MyApp::handleRequest'
      Runtime: provided.al2
      Description: 'Hello GraalVM Java in the cloud'
      MemorySize: 512
      Architectures:
        - x86_64
      Timeout: 3
```

### JBang and Scala-CLI vs Maven

And that's it, so simple. When we compare it to something like Maven, we can see how difficult it is to navigate there. Look for yourself [in the repository](https://github.com/aws/aws-sam-cli-app-templates/tree/b6a0f90d4732a5a980384c0058af0cbe8874f509/al2/graalvm/17/maven/%7B%7Bcookiecutter.project_name%7D%7D) from which `sam init` generates a similar Hello World skeleton.

In the example from this article, everything is at our fingertips. The `dist/bootstrap` file explains how it works in AWS. Please see the necessary directive in the source code.

```java
//> using mainClass "com.amazonaws.services.lambda.runtime.api.client.AWSLambda"
```

In the case of `pom.xml`, there is so much noise that you may not immediately notice it. The "main class" above, in conjunction with `bootstrap`, is crucial regarding the [AWS Lambda Java Runtime Interface Client](https://github.com/aws/aws-lambda-java-libs/tree/main/aws-lambda-java-runtime-interface-client) used in the above example.

Okay, we have it! One and done!

# Streamlining bug reporting via Github Gists

Examples of using solutions such as Scala-CLI in the industry are plentiful. Another interesting example is [the ability to directly run Github Gists](https://scala-cli.virtuslab.org/docs/cookbooks/gists/). This is useful for quickly exchanging prototypes or discussing functionality between engineers. But it is also a perfect tool for reporting bugs. We can easily provide an example using a specific language version, or a specific JVM version, for instance:

```bash
scala-cli run --jvm adoptium:1.20.0.1 https://gist.github.com/baldram/b7a4617fc135f08141e3906ca9adf51f
```

With JBang:

```bash
jbang run https://gist.github.com/baldram/b7a4617fc135f08141e3906ca9adf51f
```

The above code is an example of integer overflow, and it will compile and run without any warnings.

```bash
Compiling project (Java)
Compiled project (Java)
The sum is: -2147483648
```

# Code Examples

You can find the complete code examples from this article on GitHub [here](https://github.com/lambdaspot/tutorials).

# Conclusion

The article demonstrates how the tools discussed in the [previous article](https://blog.lambdaspot.dev/one-and-done-embrace-single-file-jvm-apps-for-speedy-development) have potential applications in the industry beyond education, prototyping, and experimentation. For instance, the article showcases serverless applications. Similarly, other simple applications, whether web-based or command-line tools, can benefit from the straightforward approach offered by JBang or Scala-CLI. These tools can effectively replace traditional build tools, such as Maven, Gradle, or SBT, simplifying routine tasks, reducing boilerplate code, and minimizing complexity, reducing the risk of errors.
