export AWS_ACCESS_KEY_ID=XXXX
export AWS_SECRET_KEY=YYYY
Table of Contents
Micronaut AWS
Provides integration between Micronaut and Amazon Web Services (AWS)
Version:
1 Introduction
This project provides various extensions to Micronaut for Amazon Web Services (AWS). The primary focus initially is on AWS Lambda, however other integrations may be included in this project in the future.
A base AWSClientConfiguration is provided which can be used as a base configuration class for any configuration that needs to configure an AWS SDK client.
2 What's new?
Micronatu AWS 2.0.0 contains improvements to ease the creation of Alexa Skills with Micronaut:
-
Creation of Alexa Skills as http services.
-
Creation of Flash Briefing skills.
-
Easier creation of SSML.
Check the Alexa section to learn more.
See the section Breaking Changes to ease into the migration to Micronaut AWS 2.x
3 Amazon Correto
Amazon Corretto is an OpenJDK distribution that provides free, long-term support with no pay-gated features or restrictions on how it’s used in production. Corretto is used by thousands of Amazon workloads; for example, it’s the JDK used by the AWS Lambda java11 runtime, which provides insights Amazon uses to push improvements upstream.
4 AWS Credentials Provider
When working with AWS SDK, you may need to provide a com.amazonaws.auth.AWSCredentialsProvider
. To ease that this module provides a utility class: EnvironmentAWSCredentialsProvider.
For example the following snippet show how you may configure a S3 Client if you set two environment variables:
AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder.standard();
amazonS3ClientBuilder.setCredentials(new EnvironmentAWSCredentialsProvider(applicationContext.getEnvironment()));
AmazonS3 s3 = amazonS3ClientBuilder.build();
Read about externalized Configuration with property sources in Micronaut.
5 AWS Lambda Support
Regular Micronaut functions created via mn create-function
can be deployed to Lambda directly.
Using the CLI
$ mn create-function example |
For example, when using create-function hello
Micronaut 1.1 and above will create a function that looks like this:
package example;
import io.micronaut.function.executor.FunctionInitializer;
import javax.inject.*;
@Singleton
public class EchoFunction extends FunctionInitializer { (1)
@Inject GreetingService greetingService; (2)
public Message echo(Message msg) { (3)
return greetingService.hello(msg);
}
public static void main(String...args) throws IOException { (4)
EchoFunction function = new EchoFunction();
function.run(args, (context)-> function.echo(context.get(Message.class)));
}
}
1 | The function should be a simple class annotated with @Singleton that extends from FunctionInitializer and has no constructor arguments. |
2 | You can use dependency injection to inject fields. |
3 | The function should be defined as a public method, and accept a single argument that is a simple java.lang type or a POJO bean that can be serialized/deserialized to JSON. |
4 | You can optionally define a main method if you plan to deploy the function to other FaaS environments that use standard in/out. If not, you can remove the main method |
The FunctionInitiazer
super class will bootstrap Micronaut and perform injection on the instance prior to the echo
method being executed.
An appropriate SAM definition for this function would be:
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
Handler: example.EchoFunction::echo
The key part is the Handler
which should be set to example.EchoFunction::echo
which is the class name and method name combination. The main
method is optional and can be removed if it is not needed. The main
method allows the function to be executed from the cli, for example:
$ echo '{"name":"Fred"}' | java -jar function.jar
The above example pipes the JSON into system in which the functions reads and writes the result to system out. Certain FaaS systems such as OpenFaaS use system in/out for function inputs and outputs, hence with the main method in place you can deploy the function to such systems.
If you need further integration with what AWS Lambda has to offer, then this project includes a number of parent classes and utilities to simplify working with the native Lambda APIs.
5.1 Micronaut Request Handlers
The micronaut-function-aws
module includes two parent classes you can subclass to implement native AWS Lambda functionality. To get started first add the micronaut-function-aws
dependency to your build:
implementation("io.micronaut:micronaut-function-aws")
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-function-aws</artifactId>
</dependency>
You can then use either MicronautRequestHandler or MicronautRequestStreamHandler as your parent class to implement your Lambda handler. For example:
MicronautRequestHandler
package example;
import io.micronaut.context.env.Environment;
import javax.inject.Inject;
public class RoundHandler extends MicronautRequestHandler<Float, Integer> { (1)
@Inject
MathService mathService; (2)
@Inject
Environment env;
@Override
public Integer execute(Float input) {
return mathService.round(input); (3)
}
}
1 | The class extends MicronautRequestHandler and must have a default constructor. |
2 | You can use field injection to inject dependencies |
3 | The function implementation |
With MicronautRequestHandler it is expected that you supply generic types with the input and the output types.
If you wish to work with raw streams then subclass MicronautRequestStreamHandler instead.
You should then configure the class name as the Lambda handler when deploying to AWS. For example with SAM:
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: example.RoundHandler::handleRequest
5.2 AWS API Gateway Support
An alternative approach to using Micronaut with AWS is to use Micronaut support for the AWS Serverless Java Container project.
Using the CLI with Micronaut 1.1 or above
$ mn create-app my-app --features aws-api-gateway |
In this arrangement you build your application as a regular REST application, for example:
package example;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller("/ping")
public class PingController {
private Logger log = LoggerFactory.getLogger(PingController.class);
@Get("/")
public String ping() {
log.trace("Received a ping.");
return "{\"pong\":true}";
}
}
You then need to add the micronaut-function-aws-api-proxy
dependency:
implementation("io.micronaut.aws:micronaut-function-aws-api-proxy")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-function-aws-api-proxy</artifactId>
</dependency>
You can then implement a RequestHandler
that handles the API proxy request:
package example;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.*;
import com.amazonaws.services.lambda.runtime.*;
import io.micronaut.function.aws.proxy.MicronautLambdaContainerHandler;
import java.io.*;
public class StreamLambdaHandler implements RequestStreamHandler {
private MicronautLambdaContainerHandler handler;
public StreamLambdaHandler() {
try {
handler = new MicronautLambdaContainerHandler(); (1)
} catch (ContainerInitializationException e) {
// if we fail here. We re-throw the exception to force another cold start
e.printStackTrace();
throw new RuntimeException("Could not initialize Micronaut", e);
}
}
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
handler.proxyStream(inputStream, outputStream, context); (2)
}
}
1 | Micronaut is initialized in the constructor |
2 | The API Proxy Request is handled |
The MicronautLambdaContainerHandler class is used to initialization Micronaut and handle the serverless requests.
The MicronautLambdaContainerHandler
constructor also accepts a ApplicationContextBuilder
instance which you can use to further customize application bootstrap.
See the Example Application and associated README for instructions on how to deploy locally using AWS SAM Local and AWS Cloud Formation for production. |
Micronaut API Gateway Proxy builds on top of AWS Servless Java Container. You can check a Pet Store sample application.
ObjectMapper
configuration
By default, for efficiency reasons, Micronaut uses the default Jackson ObjectMapper
to communicate with the AWS API proxy.
However, if you have configured it for your application (for example, setting jackson.propertyNamingStrategy: SNAKE_CASE
)
in a way that it would be incompatible with the API proxy, you can set aws.proxy.shared-object-mapper: false
, and Micronaut
will create a brand new ObjectMapper
for the API proxy.
If you wish to further configure this ObjectMapper
, you can register a BeanCreatedEventListener<ObjectMapper>
and
filter based on event.getBeanDefinition()
having an annotation like @Named("aws")
.
6 AWS Lambda Custom Runtime
Micronaut’s eases the deployment of your functions as a Custom AWS Lambda runtime:
A runtime is a program that runs a Lambda function’s handler method when the function is invoked. You can include a runtime in your function’s deployment package in the form of an executable file named bootstrap.
Why would you want to deploy your function as a Custom Runtime? For example, to use a different JVM than the one AWS provides or execute your function as GraalVM Native Image.
First, you need to add the micronaut-function-aws-custom-runtime
dependency to your build:
implementation("io.micronaut.aws:micronaut-function-aws-custom-runtime")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-function-aws-custom-runtime</artifactId>
</dependency>
The main API you will interact with is AbstractMicronautLambdaRuntime. An abstract class which you can extend to create your custom runtime mainClass
. That class includes the necessary code to perform the Processing Tasks described in the Custom Runtime documentation.
6.1 Custom Runtime for Handler functions
Lets describe a scenario with a handler which works with an input and output POJO.
package io.micronaut.docs;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.function.aws.MicronautRequestHandler;
@Introspected (1)
public class BookRequestHandler extends MicronautRequestHandler<Book, BookSaved> {
@Override
public BookSaved execute(Book input) {
BookSaved bookSaved = new BookSaved();
bookSaved.setName(input.getName());
bookSaved.setIsbn("XXX");
return bookSaved;
}
}
1 | Annotate your handler with io.micronaut.core.annotation.Introspected so that Micronaut can instantiate the handler. |
import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;
@Introspected (1)
public class Book {
@NonNull
@NotBlank
private String name;
public Book() {
}
// Getters & Setters
1 | Annotate your POJOs with io.micronaut.core.annotation.Introspected for reflection-free JSON serialization and deserialization. |
import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;
@Introspected (1)
public class BookSaved {
@NonNull
@NotBlank
private String name;
@NonNull
@NotBlank
private String isbn;
public BookSaved() {
}
// Getters & Setters
1 | Annotate your POJOs with io.micronaut.core.annotation.Introspected for reflection-free JSON serialization and deserialization. |
In the AWS Console, as Handler value you will set io.micronaut.docs.BookRequestHandler .
|
bootstrap
You will deploy your custom runtime code as a ZIP file. At the root of the ZIP file you need a bootstrap file.
If there’s a file named bootstrap in your deployment package, Lambda executes that file.
Create a bootstrap
bash script:
bootstrap
#!/bin/sh
set -euo pipefail
java -XX:TieredStopAtLevel=1 -noverify -cp demo.jar io.micronaut.docs.BookLambdaRuntime
Create the class referenced in the previous bootstrap
file:
package io.micronaut.docs;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import io.micronaut.function.aws.runtime.AbstractMicronautLambdaRuntime;
import java.net.MalformedURLException;
public class BookLambdaRuntime
extends AbstractMicronautLambdaRuntime<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent, Book, BookSaved> {
public static void main(String[] args) {
try {
new BookLambdaRuntime().run(args);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
@Override
protected Object createHandler(String... args) {
return new BookRequestHandler();
}
}
The above class is an example of a Lambda whose trigger is an API Gateway event.
6.2 Deploy a Custom Runtime
You can deploy it via the AWS console or the AWS CLI. You will need to create a zip bundle including the bootstrap
and your application JAR file (in the above case named demo.jar
).
If you use the AWS CLI, specify a runtime of provided
:
aws lambda create-function --function-name my-function \
--zip-file fileb://function.zip --handler function.handler --runtime provided \
--role arn:aws:iam::123456789:role/lambda_basic_execution
See Publishing a Custom Runtime for more information. |
6.3 Custom runtime for AWS API Gateway Proxy functions
If you build your application with the API Gateway approach. You don’t need to create a class which extends
AbstractMicronautLambdaRuntime you can use MicronautLambdaRuntime
.
The bootstrap
bash script will reference that class:
bootstrap
for MicronautLambdaRuntime
#!/bin/sh
set -euo pipefail
java -XX:TieredStopAtLevel=1 -noverify -cp demo.jar io.micronaut.function.aws.runtime.MicronautLambdaRuntime
In addition to the dependencies to micronaut-function-aws-aws-custom-runtime
, you will need to include dependencies to the
micronaut-function-aws-api-proxy
module.
6.4 Custom runtime for Function as a GraalVM Native Image
To achieve the absolutely best cold startup time you can create a function that is compiled into a GraalVM native image and then run it from the bootstrap
script.
You need several pieces in place:
GraalVM native-image.properties
First you will need to create a native-image.properties file:
Args = -H:IncludeResources=logback.xml|application.yml \
-H:Name=demo (1)
-H:Class=io.micronaut.docs.BookLambdaRuntime (2)
1 | This is the native image name. You reference this name in the Dockerfile which builds the native image |
2 | This is the mainClass of your app. |
A Dockerfile to build a GraalVM native image
Create a Dockerfile
which:
-
Uses the
amazonlinux
image -
Builds the JAR of the function.
-
Install the necessary dependencies.
-
Downloads GraalVM community edition
-
Installs
native-image
utility. -
With the
native-image
command and the JAR, generates a GraalVM native image -
Bundles the native image of our function and the bootstrap file into a ZIP file.
FROM gradle:6.3.0-jdk8 as builder
COPY --chown=gradle:gradle . /home/application
WORKDIR /home/application
RUN ./gradlew build --no-daemon
FROM amazonlinux:2018.03.0.20191014.0 as graalvm
ENV LANG=en_US.UTF-8
RUN yum install -y gcc gcc-c++ libc6-dev zlib1g-dev curl bash zlib zlib-devel zip
ENV GRAAL_VERSION 20.0.0
ENV JDK_VERSION java8
ENV GRAAL_FILENAME graalvm-ce-${JDK_VERSION}-linux-amd64-${GRAAL_VERSION}.tar.gz
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} -o /tmp/${GRAAL_FILENAME}
RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \
&& mv /tmp/graalvm-ce-${JDK_VERSION}-${GRAAL_VERSION} /usr/lib/graalvm
RUN rm -rf /tmp/*
CMD ["/usr/lib/graalvm/bin/native-image"]
FROM graalvm
COPY --from=builder /home/application/ /home/application/
WORKDIR /home/application
RUN /usr/lib/graalvm/bin/gu install native-image
RUN /usr/lib/graalvm/bin/native-image --no-server -cp build/libs/demo-*-all.jar
RUN chmod 777 bootstrap
RUN chmod 777 demo (1)
RUN zip -j function.zip bootstrap demo (1)
EXPOSE 8080
ENTRYPOINT ["/home/application/demo"]
1 | It matches the name used in the native-image.properties file. |
Execute the native image from bootstrap
Invoke the native image from the bootstrap
script:
bootstrap
with native image#!/bin/sh
set -euo pipefail
./demo -Xmx128m -Djava.library.path=$(pwd)
A script to build the function ZIP
You can then use Docker to build a function ZIP file ready for deployment to AWS Lambda:
#!/bin/bash
docker build . -t demo
mkdir -p build
docker run --rm --entrypoint cat demo /home/application/function.zip > build/function.zip
7 Alexa Support
The Micronaut’s aws-alexa
module simplifies development of Alexa Skills with Java, Kotlin or Groovy.
implementation("io.micronaut.aws:micronaut-aws-alexa")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-aws-alexa</artifactId>
</dependency>
To create the sample skill described in Amazon Documentation - Develop your first skill with Micronaut’s Alexa you will write the same LaunchRequestHandler, HelloWorldIntent, HelpIntent, CancelandStopHandler, FallbackIntentHandler, SessionEndedRequestHandler handlers.
You will do just one change, you will annotate those handlers with javax.inject.Singleton
.
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.model.Response;
import com.amazon.ask.request.Predicates;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton (1)
public class HelloWorldIntentHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(Predicates.intentName("HelloWorldIntent"));
}
@Override
public Optional<Response> handle(HandlerInput input) {
String speechText = "Hello world";
return input.getResponseBuilder()
.withSpeech(speechText)
.withSimpleCard("HelloWorld", speechText)
.build();
}
}
1 | The Singleton scope indicates only one instance of the bean should exist in the Micronaut’s Bean Context |
Typically, the next step will be to provide an instance of AlexaSkillConfiguration. The easiest way to do that is to configure the skill id via configuration:
alexa:
skills:
myskill:
skill-id 'xxxx-yaaa-zz123'
Micronaut’s alexa module provides by default
StandardSkillBuilderProvider which creates an SDK instance using the Skills.standard
builder. You can provide your own implementation of Micronaut’s SkillBuilderProvider.
For each AlexaSkillConfiguration bean, Micronaut uses the builder provided by [SkillBuilderProvider to create for you a bean of type AlexaSkill for you and wires up the beans of the following types:
-
com.amazon.ask.dispatcher.request.handler.RequestHandler
-
com.amazon.ask.dispatcher.request.interceptor.RequestInterceptor
-
com.amazon.ask.dispatcher.request.interceptor.ResponseInterceptor
-
com.amazon.ask.dispatcher.exception.ExceptionHandler
-
com.amazon.ask.builder.SkillBuilder
7.1 SSML Builder
Micronaut Alexa ships with a Speech Systhesys Markup Language builder.
new Ssml().speak(new Ssml("Welcome to Ride Hailer. ").audio('soundbank://soundlibrary/transportation/amzn_sfx_car_accelerate_01').build()).build() == '<speak>Welcome to Ride Hailer. <audio src="soundbank://soundlibrary/transportation/amzn_sfx_car_accelerate_01"/></speak>'
7.2 The IntentHandler Annotation
To simplify the programming model Micronaut AWS includes a @IntentHandler annotation that can be used on any bean method to make the method an intent handler.
The method must accept a single value of type com.amazon.ask.dispatcher.request.handler.HandlerInput and return a value of type Optional<com.amazon.ask.model.Response> otherwise a compilation error will occur.
|
A typical Alexa application written in Micronaut looks like:
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.model.Response;
import io.micronaut.function.aws.alexa.AlexaIntents;
import io.micronaut.function.aws.alexa.annotation.IntentHandler;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton (1)
public class AlexaApplication {
public static final String INTENT_NAME = "HelloWorldIntent";
private final MessageService messageService;
public AlexaApplication(MessageService messageService) { (2)
this.messageService = messageService;
}
@IntentHandler(INTENT_NAME) (3)
public Optional<Response> greet(HandlerInput input) { (4)
String speechText = messageService.sayHello();
return input.getResponseBuilder()
.withSpeech(speechText)
.withSimpleCard("HelloWorld", speechText)
.build();
}
import com.amazon.ask.dispatcher.request.handler.HandlerInput
import com.amazon.ask.model.Response
import groovy.transform.CompileStatic
import io.micronaut.function.aws.alexa.AlexaIntents
import io.micronaut.function.aws.alexa.annotation.IntentHandler
import javax.inject.Singleton
@Singleton (1)
@CompileStatic
class AlexaApplication {
private final MessageService messageService
AlexaApplication(MessageService messageService) { (2)
this.messageService = messageService
}
@IntentHandler("HelloWorldIntent") (3)
Optional<Response> greet(HandlerInput input) { (4)
String speechText = messageService.sayHello()
return input.getResponseBuilder()
.withSpeech(speechText)
.withSimpleCard("HelloWorld", speechText)
.build()
}
import com.amazon.ask.dispatcher.request.handler.HandlerInput
import com.amazon.ask.model.Response
import io.micronaut.function.aws.alexa.AlexaIntents
import io.micronaut.function.aws.alexa.annotation.IntentHandler
import javax.inject.Singleton
import java.util.Optional
@Singleton (1)
class AlexaApplication(val messageService: MessageService) { (2)
companion object {
const val INTENT_NAME = "HelloWorldIntent"
}
@IntentHandler(INTENT_NAME) (3)
fun greet(input : HandlerInput) : Optional<Response> { (4)
val speechText = messageService.sayHello()
return input.responseBuilder
.withSpeech(speechText)
.withSimpleCard("HelloWorld", speechText)
.build()
}
1 | The javax.inject.Singleton annotation is used to define AlexApplication as a bean |
2 | Other services can be dependency injected into the constructor |
3 | The @IntentHandler is used to indicate which methods are intent handlers |
4 | The method receives a HandlerInput and returns a Response |
7.3 Alexa Skill as a Web Service
Micronaut simplifies hosting a Custom Skill as a Web Service.
The micronaut-aws-webservice
is a fork of Amazon Servlet module. It allow you to run your Alexa Skill backend logic in Micronaut applications deployed with a netty or servlet runtimes.
Just by including the dependency:
implementation("io.micronaut.aws:micronaut-aws-alexa-httpserver")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-aws-alexa-httpserver</artifactId>
</dependency>
you get an POST endpoint at /alexa
(the route is configurable via alexa.endpoint.path
.
You can configure your Skill’s endpoint under Build/Endpoint
in the Alexa developer console.
7.4 Alexa Skill as an AWS Lambda Function
The micronaut-function-aws-alexa
module includes support for building deploying an Alexa Skill as a Lambda Function.
implementation("io.micronaut.aws:micronaut-function-aws-alexa")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-function-aws-alexa</artifactId>
</dependency>
As handler specify io.micronaut.function.aws.alexa.AlexaFunction
. You don’t need to create a class which extends SkillStreamHandler
, AlexaFunction takes care of adding request handlers interceptors etc.
7.5 Flash Briefings
Micronaut’s eases the creation of Flash Briefing Skills.
You can create a flash briefing skill to provide Alexa customers with news headlines and other short content. Typically a flash briefing becomes a part of a customer’s daily routine.
Your application must expose an endpoint which returns a JSON Array of FlashBriefingItem
package io.micronaut.aws.alexa.flashbriefing;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import javax.validation.Validator;
import java.util.List;
import java.util.stream.Collectors;
@Controller("/news")
public class FlashBriefingsController {
private final Validator validator;
private final FlashBriefingRepository flashBriefingRepository;
public FlashBriefingsController(Validator validator,
FlashBriefingRepository flashBriefingRepository) {
this.validator = validator;
this.flashBriefingRepository = flashBriefingRepository;
}
@Get (1)
public List<FlashBriefingItem> index() {
return flashBriefingRepository.find()
.stream()
.filter(item -> validator.validate(item).isEmpty()) (2)
.sorted() (3)
.limit(5) (4)
.collect(Collectors.toList());
}
}
1 | By default, Micronaut sets the response HTTP Header Content-Type with the value application-json . |
2 | Flash Briefing Feed items must be valid according to Flash Briefing Skill API Feed Reference constraints. |
3 | Items should be provided in order from newest to oldest, based on the date value for the item. Alexa may ignore older items. |
4 | Flash Briefing Skill API Feed Reference instructs to provide between 1 and 5 unique items at a time. |
8 Repository
You can find the source code of this project in this repository:
9 Breaking Changes
AlexaFunction has been refactored to ease extension.
Old Class Name | New Class |
---|---|
|
Class Name | Old Package | New Package |
---|---|---|
|
|
|
|
|
|
|
|
10 Release History
For more information, check the GitHub release list.
1.4
Setting Micronaut 1.2 as the minimum supported version
1.3
-
Improved support for GraalVM native images
-
Upgrade to
aws-serverless-java-container-core
1.4
1.2
Compatibility with Micronaut 1.2
1.1
Compatibility with Micronaut 1.1
1.0
Initial Release