diff --git a/README.md b/README.md
index 3f26e0c1b..b7da76ea2 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Currently the following versions are maintained:
|---------|--------|-----------------------------|-----------------|------------------------|----------------|---------------|
| 1.x | [1.x](https://github.com/aws/serverless-java-container/tree/1.x) | Java EE (javax.*) | 5.x (Boot 2.x) | 2.x | :white_check_mark: | :white_check_mark: |
| 2.x | [main](https://github.com/aws/serverless-java-container/tree/main) | Jakarta EE 9-10 (jakarta.*) | 6.x (Boot 3.x) | 3.x | :x: | :x: |
-| 3.x | | Jakarta EE 11 (jakarta.*) | 7.x (Boot 4.x) | 4.x | :x: | :x: |
+| 3.x | [main](https://github.com/aws/serverless-java-container/tree/main) | Jakarta EE 11 (jakarta.*) | 7.x (Boot 4.x) | 4.x | :x: | :x: |
Follow the quick start guides in [our wiki](https://github.com/aws/serverless-java-container/wiki) to integrate Serverless Java Container with your project:
* [Spring quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Spring)
diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml
index e9aa6bbec..9929a6149 100644
--- a/aws-serverless-java-container-core/pom.xml
+++ b/aws-serverless-java-container-core/pom.xml
@@ -6,18 +6,19 @@
AWS Serverless Java container support - Core
Allows Java applications written for a servlet container to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
3.1.0
6.0.0
+ 3.0.2
@@ -40,13 +41,13 @@
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
${jackson.version}
- com.fasterxml.jackson.module
+ tools.jackson.module
jackson-module-afterburner
${jackson.version}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
index 0ae00b4da..8e13bd799 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
@@ -18,7 +18,7 @@
import com.amazonaws.serverless.proxy.model.ErrorModel;
import com.amazonaws.serverless.proxy.model.Headers;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import tools.jackson.core.JacksonException;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -103,7 +103,7 @@ protected String getErrorJson(String message) {
try {
return LambdaContainerHandler.getObjectMapper().writeValueAsString(new ErrorModel(message));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
log.error("Could not produce error JSON", e);
return "{ \"message\": \"" + message + "\" }";
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
index ff978456b..86d4216a1 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
@@ -19,12 +19,10 @@
import com.amazonaws.serverless.proxy.model.ContainerConfig;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectReader;
-import com.fasterxml.jackson.databind.ObjectWriter;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectReader;
+import tools.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -86,10 +84,9 @@ public abstract class LambdaContainerHandler {
return subject;
});
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
log.error("Error while attempting to parse JWT body for requestId: " + SecurityUtils.crlf(event.getRequestContext().getRequestId()), e);
return null;
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
index 2cf6d77a6..8226be983 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
@@ -13,17 +13,16 @@
package com.amazonaws.serverless.proxy.model;
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.annotation.JsonDeserialize;
+import tools.jackson.databind.annotation.JsonSerialize;
+import tools.jackson.databind.deser.std.StdDeserializer;
+import tools.jackson.databind.ser.std.StdSerializer;
+import tools.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.util.HashMap;
@@ -77,10 +76,9 @@ public HttpApiV2AuthorizerDeserializer() {
}
@Override
- public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
- throws IOException, JsonProcessingException {
+ public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap();
- JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+ JsonNode node = deserializationContext.readTree(jsonParser);
if (node.has(JWT_KEY)) {
HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper()
.treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class);
@@ -88,7 +86,7 @@ public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, Deserialization
}
if (node.has(LAMBDA_KEY)) {
Map context = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(LAMBDA_KEY),
- TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, Object.class));
+ LambdaContainerHandler.getObjectMapper().getTypeFactory().constructMapType(HashMap.class, String.class, Object.class));
map.put(LAMBDA_KEY, context);
}
if (node.has(IAM_KEY)) {
@@ -110,16 +108,19 @@ public HttpApiV2AuthorizerSerializer() {
@Override
public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator,
- SerializerProvider serializerProvider) throws IOException {
+ SerializationContext serializationContext) {
jsonGenerator.writeStartObject();
if (httpApiV2AuthorizerMap.isJwt()) {
- jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer());
+ jsonGenerator.writeName(JWT_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.getJwtAuthorizer());
}
if (httpApiV2AuthorizerMap.isLambda()) {
- jsonGenerator.writeObjectField(LAMBDA_KEY, httpApiV2AuthorizerMap.getLambdaAuthorizerContext());
+ jsonGenerator.writeName(LAMBDA_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.getLambdaAuthorizerContext());
}
if (httpApiV2AuthorizerMap.isIam()) {
- jsonGenerator.writeObjectField(IAM_KEY, httpApiV2AuthorizerMap.get(IAM_KEY));
+ jsonGenerator.writeName(IAM_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.get(IAM_KEY));
}
jsonGenerator.writeEndObject();
}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
index 012827e40..e15b9876f 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
@@ -5,8 +5,8 @@
import com.amazonaws.serverless.exceptions.InvalidResponseObjectException;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.ErrorModel;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@@ -47,7 +47,7 @@ void typedHandle_InvalidRequestEventException_500State() {
@Test
void typedHandle_InvalidRequestEventException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null));
assertNotNull(resp);
@@ -74,7 +74,7 @@ void typedHandle_InvalidResponseObjectException_502State() {
@Test
void typedHandle_InvalidResponseObjectException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null));
assertNotNull(resp);
@@ -106,7 +106,7 @@ void typedHandle_InternalServerErrorException_500State() {
@Test
void typedHandle_InternalServerErrorException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class);
Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE);
@@ -131,7 +131,7 @@ void typedHandle_InternalServerErrorException_jsonContentTypeHeader() {
@Test
void typedHandle_NullPointerException_responseObject()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new NullPointerException());
assertNotNull(resp);
@@ -248,7 +248,7 @@ void getErrorJson_ErrorModel_validJson()
void getErrorJson_JsonParsinException_validJson()
throws IOException {
ObjectMapper mockMapper = mock(ObjectMapper.class);
- JsonProcessingException exception = mock(JsonProcessingException.class);
+ JacksonException exception = mock(JacksonException.class);
when(mockMapper.writeValueAsString(any(Object.class))).thenThrow(exception);
String output = exceptionHandler.getErrorJson(INVALID_RESPONSE_MESSAGE);
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
index e42130453..a817bd2bf 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
@@ -15,8 +15,8 @@
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.*;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.IOUtils;
import org.apache.hc.core5.http.ContentType;
@@ -106,7 +106,7 @@ public AwsProxyRequestBuilder alb() {
try {
String json = objectMapper.writeValueAsString(this.request);
albRequest = objectMapper.readValue(json, AwsProxyRequest.class);
- } catch (JsonProcessingException jpe) {
+ } catch (JacksonException jpe) {
throw new RuntimeException(jpe);
}
@@ -265,7 +265,7 @@ public AwsProxyRequestBuilder body(Object body) {
if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)) {
try {
return body(LambdaContainerHandler.getObjectMapper().writeValueAsString(body));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
throw new UnsupportedOperationException("Could not serialize object: " + e.getMessage());
}
} else {
@@ -438,7 +438,7 @@ public InputStream buildStream() {
try {
String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(request);
return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
return null;
}
}
@@ -448,7 +448,7 @@ public InputStream toHttpApiV2RequestStream() {
try {
String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(req);
return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
return null;
}
}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
index 6d0aa9eeb..07bdff979 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
@@ -7,7 +7,7 @@
import java.io.IOException;
import org.junit.jupiter.api.Test;
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
public class AwsProxyRequestTest {
private static final String CUSTOM_HEADER_KEY_LOWER_CASE = "custom-header";
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
index 20ff4dff2..e51a1602b 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
@@ -1,7 +1,7 @@
package com.amazonaws.serverless.proxy.model;
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import tools.jackson.core.JacksonException;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -181,7 +181,7 @@ void deserialize_fromJsonString_authorizerPopulatedCorrectly() {
assertTrue(req.getRequestContext().getAuthorizer().getJwtAuthorizer().getClaims().containsKey("claim1"));
assertEquals(2, req.getRequestContext().getAuthorizer().getJwtAuthorizer().getScopes().size());
assertEquals(RequestSource.API_GATEWAY, req.getRequestSource());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -196,7 +196,7 @@ void deserialize_fromJsonString_authorizerEmptyMap() {
assertFalse(req.getRequestContext().getAuthorizer().isJwt());
assertFalse(req.getRequestContext().getAuthorizer().isLambda());
assertFalse(req.getRequestContext().getAuthorizer().isIam());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -212,7 +212,7 @@ void deserialize_fromJsonString_lambdaAuthorizer() {
assertTrue(req.getRequestContext().getAuthorizer().isLambda());
assertEquals(5, req.getRequestContext().getAuthorizer().getLambdaAuthorizerContext().size());
assertEquals(1, req.getRequestContext().getAuthorizer().getLambdaAuthorizerContext().get("numberKey"));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -239,7 +239,7 @@ void deserialize_fromJsonString_iamAuthorizer() {
req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserArn());
assertEquals("AIDACOSFODNN7EXAMPLE2",
req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserId());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -254,7 +254,7 @@ void deserialize_fromJsonString_isBase64EncodedPopulates() {
req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, HttpApiV2ProxyRequest.class);
assertTrue(req.isBase64Encoded());
assertEquals(RequestSource.API_GATEWAY, req.getRequestSource());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -277,7 +277,7 @@ void serialize_toJsonString_authorizerPopulatesCorrectly() {
assertTrue(reqString.contains("\"scopes\":[\"first\",\"second\"]"));
assertTrue(reqString.contains("\"authorizer\":{\"jwt\":{"));
assertTrue(reqString.contains("\"isBase64Encoded\":false"));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while serializing request" + e.getMessage());
}
diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml
index 1788a36b8..8fcc07c4b 100644
--- a/aws-serverless-java-container-jersey/pom.xml
+++ b/aws-serverless-java-container-jersey/pom.xml
@@ -6,12 +6,12 @@
AWS Serverless Java container support - Jersey implementation
Allows Java applications written for Jersey to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
@@ -24,18 +24,12 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
tests
test-jar
test
@@ -68,14 +62,6 @@
test
-
- com.fasterxml.jackson.core
- jackson-databind
- ${jackson.version}
- true
- test
-
-
org.glassfish.jersey.media
jersey-media-json-jackson
@@ -88,11 +74,11 @@
jackson-annotations
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-core
diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
index b845a0b69..23f1078fa 100644
--- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
+++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
@@ -24,8 +24,8 @@
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
@@ -303,7 +303,7 @@ void error_statusCode_methodNotAllowed(String reqType) {
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody(String reqType) throws JacksonException {
initJerseyAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(CUSTOM_HEADER_VALUE);
@@ -460,7 +460,7 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(key));
assertEquals(value, response.getValues().get(key));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -471,7 +471,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
index 9dc1ab32a..5ca09b11b 100644
--- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
+++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
@@ -11,7 +11,8 @@
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Disabled;
@@ -281,7 +282,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -292,7 +293,7 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(key));
assertEquals(value, response.getValues().get(key));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml
index f03d9db0a..d85534faf 100644
--- a/aws-serverless-java-container-spring/pom.xml
+++ b/aws-serverless-java-container-spring/pom.xml
@@ -6,18 +6,18 @@
AWS Serverless Java container support - Spring implementation
Allows Java applications written for the Spring framework to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
- 6.2.8
- 6.5.1
+ 7.0.0
+ 7.0.0
@@ -25,12 +25,12 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
tests
test-jar
test
@@ -57,12 +57,7 @@
test
-
- com.fasterxml.jackson.core
- jackson-databind
- ${jackson.version}
- test
-
+
jakarta.activation
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
index 9504b6166..4b7d3eb81 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
@@ -15,8 +15,8 @@
import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel;
import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
@@ -294,7 +294,7 @@ void error_unauthenticatedCall_filterStepsRequest(String reqType) {
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody(String reqType) throws JacksonException {
initSpringAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(CUSTOM_HEADER_VALUE);
@@ -311,7 +311,7 @@ void responseBody_responseWriter_validBody(String reqType) throws JsonProcessing
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody_UTF(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody_UTF(String reqType) throws JacksonException {
initSpringAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(UNICODE_VALUE);
@@ -363,7 +363,7 @@ void injectBody_populatedResponse_noException(String reqType) {
try {
SingleValueModel output = objectMapper.readValue(response.getBody(), SingleValueModel.class);
assertEquals("true", output.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail();
}
@@ -373,7 +373,7 @@ void injectBody_populatedResponse_noException(String reqType) {
try {
SingleValueModel output = objectMapper.readValue(emptyResp.getBody(), SingleValueModel.class);
assertNull(output.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail();
}
@@ -392,7 +392,7 @@ void servletRequestEncoding_acceptEncoding_okStatusCode(String reqType) {
.header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.queryString("status", "200")
.body(objectMapper.writeValueAsString(singleValueModel));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
fail("Could not serialize object to JSON");
}
@@ -484,7 +484,7 @@ private void validateMapResponseModel(AwsProxyResponse output) {
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(CUSTOM_HEADER_KEY));
assertEquals(CUSTOM_HEADER_VALUE, response.getValues().get(CUSTOM_HEADER_KEY));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -495,7 +495,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
index cf4aa495e..9b3e0cecc 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
@@ -1,7 +1,7 @@
package com.amazonaws.serverless.proxy.spring.echoapp;
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
index 7742db7f6..d08a3e45c 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
@@ -8,7 +8,7 @@
import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler;
import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig;
import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml
index dc9ffca03..b636dd61d 100644
--- a/aws-serverless-java-container-springboot3/pom.xml
+++ b/aws-serverless-java-container-springboot3/pom.xml
@@ -3,7 +3,7 @@
aws-serverless-java-container
com.amazonaws.serverless
- 2.1.5-SNAPSHOT
+ 2.1.5
4.0.0
@@ -12,7 +12,6 @@
AWS Serverless Java container support - SpringBoot 3 implementation
Allows Java applications written for SpringBoot 3 to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
6.2.8
@@ -30,16 +29,22 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 2.1.5
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 2.1.5
tests
test-jar
test
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.9.3
+ provided
+
org.springframework
spring-webflux
diff --git a/aws-serverless-java-container-springboot4/pom.xml b/aws-serverless-java-container-springboot4/pom.xml
new file mode 100644
index 000000000..a897a65a5
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/pom.xml
@@ -0,0 +1,93 @@
+
+
+
+ aws-serverless-java-container
+ com.amazonaws.serverless
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ AWS Serverless Java container support - SpringBoot 4 implementation
+ Allows Java applications written for SpringBoot 4 to run in AWS Lambda
+ https://aws.amazon.com/lambda
+ 3.0.0-SNAPSHOT
+
+
+ 7.0.0
+ 4.0.0
+ 7.0.0
+ 5.0.0
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-function-serverless-web
+ ${springcloud.function.version}
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-core
+ 3.0.0-SNAPSHOT
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-core
+ 3.0.0-SNAPSHOT
+ tests
+ test-jar
+ test
+
+
+ org.springframework
+ spring-webflux
+ ${spring.version}
+ true
+
+
+ org.springframework.boot
+ spring-boot
+ ${springboot.version}
+ true
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${springboot.version}
+ true
+
+
+ org.springframework
+ spring-core
+ ${spring.version}
+ true
+
+
+ org.springframework
+ spring-context
+ ${spring.version}
+ true
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 25
+ 25
+
+
+
+
+
diff --git a/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java
new file mode 100644
index 000000000..0a461aa97
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazonaws.serverless.proxy.spring;
+
+import com.amazonaws.serverless.proxy.model.*;
+import org.springframework.aot.generate.GenerationContext;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
+import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
+import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse;
+import tools.jackson.core.JsonToken;
+
+/**
+ * AOT Initialization processor required to register reflective hints for GraalVM.
+ * This is necessary to ensure proper JSON serialization/deserialization.
+ * It is registered with META-INF/spring/aot.factories
+ *
+ * @author Oleg Zhurakousky
+ */
+public class AwsSpringAotTypesProcessor implements BeanFactoryInitializationAotProcessor {
+
+ @Override
+ public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
+ return new ReflectiveProcessorBeanFactoryInitializationAotContribution();
+ }
+
+ private static final class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
+ @Override
+ public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
+ RuntimeHints runtimeHints = generationContext.getRuntimeHints();
+ // known static types
+
+ runtimeHints.reflection().registerType(AwsProxyRequest.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(AwsProxyResponse.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(SingleValueHeaders.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(JsonToken.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(MultiValuedTreeMap.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(Headers.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(AwsProxyRequestContext.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(ApiGatewayRequestIdentity.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES);
+ runtimeHints.reflection().registerType(AwsHttpServletResponse.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2ProxyRequest.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2HttpContext.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2ProxyRequestContext.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.HttpApiV2AuthorizerDeserializer.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.HttpApiV2AuthorizerSerializer.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2IamAuthorizer.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ runtimeHints.reflection().registerType(HttpApiV2JwtAuthorizer.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS);
+ }
+
+ }
+}
diff --git a/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java
new file mode 100644
index 000000000..7c262a072
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java
@@ -0,0 +1,222 @@
+package com.amazonaws.serverless.proxy.spring;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import com.amazonaws.serverless.proxy.internal.HttpUtils;
+import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest;
+import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest;
+import com.amazonaws.serverless.proxy.model.RequestSource;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.cloud.function.serverless.web.ServerlessHttpServletRequest;
+import org.springframework.cloud.function.serverless.web.ServerlessMVC;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.util.MultiValueMapAdapter;
+import org.springframework.util.StringUtils;
+
+import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter;
+import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter;
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.SecurityContextWriter;
+import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse;
+import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
+import com.amazonaws.services.lambda.runtime.Context;
+import tools.jackson.databind.ObjectMapper;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+
+import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.decodeValueIfEncoded;
+import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.getQueryParamValuesAsList;
+
+class AwsSpringHttpProcessingUtils {
+
+ private static Log logger = LogFactory.getLog(AwsSpringHttpProcessingUtils.class);
+ private static final int LAMBDA_MAX_REQUEST_DURATION_MINUTES = 15;
+
+ private AwsSpringHttpProcessingUtils() {
+
+ }
+
+ public static AwsProxyResponse processRequest(HttpServletRequest request, ServerlessMVC mvc,
+ AwsProxyHttpServletResponseWriter responseWriter) {
+ CountDownLatch latch = new CountDownLatch(1);
+ AwsHttpServletResponse response = new AwsHttpServletResponse(request, latch);
+ try {
+ mvc.service(request, response);
+ boolean requestTimedOut = !latch.await(LAMBDA_MAX_REQUEST_DURATION_MINUTES, TimeUnit.MINUTES); // timeout is potentially lower as user configures it
+ if (requestTimedOut) {
+ logger.warn("request timed out after " + LAMBDA_MAX_REQUEST_DURATION_MINUTES + " minutes");
+ }
+ AwsProxyResponse awsResponse = responseWriter.writeResponse(response, null);
+ return awsResponse;
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static String extractVersion() {
+ try {
+ String path = AwsSpringHttpProcessingUtils.class.getProtectionDomain().getCodeSource().getLocation().toString();
+ int endIndex = path.lastIndexOf('.');
+ if (endIndex < 0) {
+ return "UNKNOWN-VERSION";
+ }
+ int startIndex = path.lastIndexOf("/") + 1;
+ return path.substring(startIndex, endIndex).replace("spring-cloud-function-serverless-web-", "");
+ }
+ catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to detect version", e);
+ }
+ return "UNKNOWN-VERSION";
+ }
+
+ }
+
+ public static HttpServletRequest generateHttpServletRequest(InputStream jsonRequest, Context lambdaContext,
+ ServletContext servletContext, ObjectMapper mapper) {
+ try {
+ String text = new String(FileCopyUtils.copyToByteArray(jsonRequest), StandardCharsets.UTF_8);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Creating HttpServletRequest from: " + text);
+ }
+ return generateHttpServletRequest(text, lambdaContext, servletContext, mapper);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static HttpServletRequest generateHttpServletRequest(String jsonRequest, Context lambdaContext,
+ ServletContext servletContext, ObjectMapper mapper) {
+ Map _request = readValue(jsonRequest, Map.class, mapper);
+ SecurityContextWriter securityWriter = "2.0".equals(_request.get("version"))
+ ? new AwsHttpApiV2SecurityContextWriter()
+ : new AwsProxySecurityContextWriter();
+ HttpServletRequest httpServletRequest = "2.0".equals(_request.get("version"))
+ ? AwsSpringHttpProcessingUtils.generateRequest2(jsonRequest, lambdaContext, securityWriter, mapper, servletContext)
+ : AwsSpringHttpProcessingUtils.generateRequest1(jsonRequest, lambdaContext, securityWriter, mapper, servletContext);
+ return httpServletRequest;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static HttpServletRequest generateRequest1(String request, Context lambdaContext,
+ SecurityContextWriter securityWriter, ObjectMapper mapper, ServletContext servletContext) {
+ AwsProxyRequest v1Request = readValue(request, AwsProxyRequest.class, mapper);
+
+ // Use AWS container's servlet request instead of Spring Cloud Function's
+ return new AwsProxyHttpServletRequest(v1Request, lambdaContext, securityWriter.writeSecurityContext(v1Request, lambdaContext));
+ }
+
+
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static HttpServletRequest generateRequest2(String request, Context lambdaContext,
+ SecurityContextWriter securityWriter, ObjectMapper mapper, ServletContext servletContext) {
+ HttpApiV2ProxyRequest v2Request = readValue(request, HttpApiV2ProxyRequest.class, mapper);
+
+
+ ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext,
+ v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath());
+ populateQueryStringParametersV2(v2Request.getQueryStringParameters(), httpRequest);
+
+ v2Request.getHeaders().forEach(httpRequest::setHeader);
+
+ populateContentAndContentType(
+ v2Request.getBody(),
+ v2Request.getHeaders().get(HttpHeaders.CONTENT_TYPE),
+ v2Request.isBase64Encoded(),
+ httpRequest
+ );
+
+ httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext());
+ httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables());
+ httpRequest.setAttribute(RequestReader.HTTP_API_EVENT_PROPERTY, v2Request);
+ httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext);
+ httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY,
+ securityWriter.writeSecurityContext(v2Request, lambdaContext));
+ return httpRequest;
+ }
+
+ private static void populateQueryStringParametersV2(Map requestParameters, ServerlessHttpServletRequest httpRequest) {
+ if (!CollectionUtils.isEmpty(requestParameters)) {
+ for (Entry entry : requestParameters.entrySet()) {
+ // fix according to parseRawQueryString
+ httpRequest.setParameter(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ private static void populateQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) {
+ Map requestParameters = v1Request.getQueryStringParameters();
+ if (!CollectionUtils.isEmpty(requestParameters)) {
+ // decode all keys and values in map
+ for (Entry entry : requestParameters.entrySet()) {
+ String k = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getKey()) : entry.getKey();
+ String v = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getValue()) : entry.getValue();
+ httpRequest.setParameter(k, v);
+ }
+ }
+ }
+
+ private static void populateMultiValueQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) {
+ if (v1Request.getMultiValueQueryStringParameters() != null) {
+ MultiValueMapAdapter queryStringParameters = new MultiValueMapAdapter<>(v1Request.getMultiValueQueryStringParameters());
+ queryStringParameters.forEach((k, v) -> {
+ String key = v1Request.getRequestSource() == RequestSource.ALB
+ ? decodeValueIfEncoded(k)
+ : k;
+ List value = v1Request.getRequestSource() == RequestSource.ALB
+ ? getQueryParamValuesAsList(v1Request.getMultiValueQueryStringParameters(), k, false).stream()
+ .map(AwsHttpServletRequest::decodeValueIfEncoded)
+ .toList()
+ : v;
+ httpRequest.setParameter(key, value.toArray(new String[0]));
+ });
+ }
+ }
+
+ private static T readValue(String json, Class clazz, ObjectMapper mapper) {
+ try {
+ return mapper.readValue(json, clazz);
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void populateContentAndContentType(
+ String body,
+ String contentType,
+ boolean base64Encoded,
+ ServerlessHttpServletRequest httpRequest) {
+ if (StringUtils.hasText(body)) {
+ httpRequest.setContentType(contentType == null ? MediaType.APPLICATION_JSON_VALUE : contentType);
+ if (base64Encoded) {
+ httpRequest.setContent(Base64.getMimeDecoder().decode(body));
+ } else {
+ Charset charseEncoding = HttpUtils.parseCharacterEncoding(contentType,StandardCharsets.UTF_8);
+ httpRequest.setContent(body.getBytes(charseEncoding));
+ }
+ }
+ }
+
+
+
+}
diff --git a/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java
new file mode 100644
index 000000000..f9c316185
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazonaws.serverless.proxy.spring;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.cloud.function.serverless.web.ServerlessMVC;
+import org.springframework.context.SmartLifecycle;
+import org.springframework.core.env.Environment;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.json.JsonMapper;
+
+/**
+ * Event loop and necessary configurations to support AWS Lambda Custom Runtime
+ * - https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html.
+ *
+ * @author Oleg Zhurakousky
+ * @author Mark Sailes
+ *
+ */
+public final class AwsSpringWebCustomRuntimeEventLoop implements SmartLifecycle {
+
+ private static Log logger = LogFactory.getLog(AwsSpringWebCustomRuntimeEventLoop.class);
+
+ static final String LAMBDA_VERSION_DATE = "2018-06-01";
+ private static final String LAMBDA_ERROR_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/error";
+ private static final String LAMBDA_RUNTIME_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/next";
+ private static final String LAMBDA_INVOCATION_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/response";
+ private static final String USER_AGENT_VALUE = String.format("spring-cloud-function/%s-%s",
+ System.getProperty("java.runtime.version"), AwsSpringHttpProcessingUtils.extractVersion());
+
+ private final ServletWebServerApplicationContext applicationContext;
+
+ private volatile boolean running;
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ public AwsSpringWebCustomRuntimeEventLoop(ServletWebServerApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ public void run() {
+ this.running = true;
+ this.executor.execute(() -> {
+ eventLoop(this.applicationContext);
+ });
+ }
+
+ @Override
+ public void start() {
+ this.run();
+ }
+
+ @Override
+ public void stop() {
+ this.executor.shutdownNow();
+ this.running = false;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return this.running;
+ }
+
+ private void eventLoop(ServletWebServerApplicationContext context) {
+ ServerlessMVC mvc = ServerlessMVC.INSTANCE(context);
+
+ Environment environment = context.getEnvironment();
+ logger.info("Starting AWSWebRuntimeEventLoop");
+
+ String runtimeApi = environment.getProperty("AWS_LAMBDA_RUNTIME_API");
+ String eventUri = MessageFormat.format(LAMBDA_RUNTIME_URL_TEMPLATE, runtimeApi, LAMBDA_VERSION_DATE);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Event URI: " + eventUri);
+ }
+
+ RequestEntity requestEntity = RequestEntity.get(URI.create(eventUri))
+ .header("User-Agent", USER_AGENT_VALUE).build();
+ RestTemplate rest = new RestTemplate();
+ ObjectMapper mapper = JsonMapper.builder()
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .build();
+ AwsProxyHttpServletResponseWriter responseWriter = new AwsProxyHttpServletResponseWriter();
+
+ logger.info("Entering event loop");
+ while (this.isRunning()) {
+ logger.debug("Attempting to get new event");
+ ResponseEntity incomingEvent = rest.exchange(requestEntity, String.class);
+
+ if (incomingEvent != null && incomingEvent.hasBody()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("New Event received from AWS Gateway: " + incomingEvent.getBody());
+ }
+ String requestId = incomingEvent.getHeaders().getFirst("Lambda-Runtime-Aws-Request-Id");
+
+ try {
+ logger.debug("Submitting request to the user's web application");
+
+ AwsProxyResponse awsResponse = AwsSpringHttpProcessingUtils.processRequest(
+ AwsSpringHttpProcessingUtils.generateHttpServletRequest(incomingEvent.getBody(),
+ null, mvc.getServletContext(), mapper), mvc, responseWriter);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Received response - body: " + awsResponse.getBody() +
+ "; status: " + awsResponse.getStatusCode() + "; headers: " + awsResponse.getHeaders());
+ }
+
+ String invocationUrl = MessageFormat.format(LAMBDA_INVOCATION_URL_TEMPLATE, runtimeApi,
+ LAMBDA_VERSION_DATE, requestId);
+
+ ResponseEntity result = rest.exchange(RequestEntity.post(URI.create(invocationUrl))
+ .header("User-Agent", USER_AGENT_VALUE).body(awsResponse), byte[].class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Response sent: body: " + result.getBody() +
+ "; status: " + result.getStatusCode() + "; headers: " + result.getHeaders());
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("Result POST status: " + result);
+ }
+ }
+ catch (Exception e) {
+ logger.error(e);
+ this.propagateAwsError(requestId, e, mapper, runtimeApi, rest);
+ }
+ }
+ }
+ }
+
+ private void propagateAwsError(String requestId, Exception e, ObjectMapper mapper, String runtimeApi, RestTemplate rest) {
+ String errorMessage = e.getMessage();
+ String errorType = e.getClass().getSimpleName();
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ String stackTrace = sw.toString();
+ Map em = new HashMap<>();
+ em.put("errorMessage", errorMessage);
+ em.put("errorType", errorType);
+ em.put("stackTrace", stackTrace);
+ try {
+ byte[] outputBody = mapper.writeValueAsBytes(em);
+ String errorUrl = MessageFormat.format(LAMBDA_ERROR_URL_TEMPLATE, runtimeApi, LAMBDA_VERSION_DATE, requestId);
+ ResponseEntity
@@ -24,12 +24,12 @@
com.amazonaws.serverless
aws-serverless-java-container-jersey
- ${project.version}
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- ${project.version}
+ 3.0.0-SNAPSHOT
tests
test-jar
test
@@ -51,18 +51,18 @@
jackson-annotations
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-core
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
\${jackson.version}
diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java
index 768a5ad98..a4e2251e4 100644
--- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java
+++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java
@@ -10,6 +10,7 @@
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
+import tools.jackson.core.JacksonException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
index 53db9b161..a247daed7 100644
--- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
+++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
@@ -16,6 +16,7 @@
import jakarta.ws.rs.core.Response;
import java.io.ByteArrayOutputStream;
+import tools.jackson.core.JacksonException;
import java.io.IOException;
import java.io.InputStream;
@@ -81,7 +82,7 @@ private void handle(InputStream is, ByteArrayOutputStream os) {
private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) {
try {
return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class);
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Error while parsing response: " + e.getMessage());
}
diff --git a/aws-serverless-spring-archetype/pom.xml b/aws-serverless-spring-archetype/pom.xml
index 2a2f59b8b..fc00bb2ce 100644
--- a/aws-serverless-spring-archetype/pom.xml
+++ b/aws-serverless-spring-archetype/pom.xml
@@ -4,12 +4,12 @@
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless.archetypes
aws-serverless-spring-archetype
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
maven-archetype
diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml
index 48b70f27f..4dd847a38 100644
--- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml
@@ -16,8 +16,8 @@
1.8
1.8
- 6.2.6
- 5.12.1
+ 7.0.0
+ 6.0.0
2.24.2
@@ -25,12 +25,12 @@
com.amazonaws.serverless
aws-serverless-java-container-spring
- ${project.version}
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- ${project.version}
+ 3.0.0-SNAPSHOT
tests
test-jar
test
diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
index 53db9b161..cfb809f88 100644
--- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
+++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
@@ -81,7 +81,7 @@ private void handle(InputStream is, ByteArrayOutputStream os) {
private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) {
try {
return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class);
- } catch (IOException e) {
+ } catch (tools.jackson.core.JacksonException e) {
e.printStackTrace();
fail("Error while parsing response: " + e.getMessage());
}
diff --git a/aws-serverless-springboot4-archetype/pom.xml b/aws-serverless-springboot4-archetype/pom.xml
new file mode 100644
index 000000000..82e1ad033
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/pom.xml
@@ -0,0 +1,80 @@
+
+ 4.0.0
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container
+ 3.0.0-SNAPSHOT
+
+
+ com.amazonaws.serverless.archetypes
+ aws-serverless-springboot4-archetype
+ 3.0.0-SNAPSHOT
+ maven-archetype
+
+
+ https://github.com/aws/serverless-java-container.git
+ HEAD
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+
+ src/main/resources
+ true
+
+ archetype-resources/pom.xml
+ archetype-resources/README.md
+
+
+
+ src/main/resources
+ false
+
+ archetype-resources/pom.xml
+
+
+
+
+
+
+ org.apache.maven.archetype
+ archetype-packaging
+ 3.4.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.3.1
+
+ \
+
+
+
+ org.apache.maven.plugins
+ maven-archetype-plugin
+ 3.4.0
+
+
+
+ integration-test
+
+
+
+
+
+
+
+
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-springboot4-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
new file mode 100644
index 000000000..5379692ba
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -0,0 +1,39 @@
+
+
+
+ src/main/java
+
+ **/*.java
+
+
+
+ src/main/resources
+
+ **/*.properties
+
+
+
+ src/test/java
+
+ **/*.java
+
+
+
+ src/assembly
+
+ *
+
+
+
+
+
+ template.yml
+ README.md
+ build.gradle
+
+
+
+
\ No newline at end of file
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/README.md
new file mode 100644
index 000000000..311c40aee
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/README.md
@@ -0,0 +1,99 @@
+#set($resourceName = $artifactId)
+#macro(replaceChar $originalName, $char)
+ #if($originalName.contains($char))
+ #set($tokens = $originalName.split($char))
+ #set($newResourceName = "")
+ #foreach($token in $tokens)
+ #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase())
+ #end
+ ${newResourceName}
+ #else
+ #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1))
+ ${newResourceName}
+ #end
+#end
+#set($resourceName = "#replaceChar($resourceName, '-')")
+#set($resourceName = "#replaceChar($resourceName, '.')")
+#set($resourceName = $resourceName.replaceAll("\n", "").trim())
+# \${artifactId} serverless API
+The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/aws/serverless-java-container).
+
+The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests.
+
+The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli).
+
+#[[##]]# Pre-requisites
+* [AWS CLI](https://aws.amazon.com/cli/)
+* [SAM CLI](https://github.com/awslabs/aws-sam-cli)
+* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/)
+
+#[[##]]# Building the project
+You can use the SAM CLI to quickly build the project
+```bash
+$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false
+$ cd \${artifactId}
+$ sam build
+Building resource '\${resourceName}Function'
+Running JavaGradleWorkflow:GradleBuild
+Running JavaGradleWorkflow:CopyArtifacts
+
+Build Succeeded
+
+Built Artifacts : .aws-sam/build
+Built Template : .aws-sam/build/template.yaml
+
+Commands you can use next
+=========================
+[*] Invoke Function: sam local invoke
+[*] Deploy: sam deploy --guided
+```
+
+#[[##]]# Testing locally with the SAM CLI
+
+From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI.
+
+```bash
+$ sam local start-api
+
+...
+Mounting ${groupId}.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH]
+...
+```
+
+Using a new shell, you can send a test ping request to your API:
+
+```bash
+$ curl -s http://127.0.0.1:3000/ping | python -m json.tool
+
+{
+ "pong": "Hello, World!"
+}
+```
+
+#[[##]]# Deploying to AWS
+To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen
+
+```
+$ sam deploy --guided
+```
+
+Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL
+
+```
+...
+-------------------------------------------------------------------------------------------------------------
+OutputKey-Description OutputValue
+-------------------------------------------------------------------------------------------------------------
+\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets
+-------------------------------------------------------------------------------------------------------------
+```
+
+Copy the `OutputValue` into a browser or use curl to test your first request:
+
+```bash
+$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool
+
+{
+ "pong": "Hello, World!"
+}
+```
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/build.gradle
new file mode 100644
index 000000000..3aa54825c
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'java'
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {url "https://repo.spring.io/milestone"}
+ maven {url "https://repo.spring.io/snapshot"}
+}
+
+dependencies {
+ implementation (
+ 'org.springframework.boot:spring-boot-starter-web:3.4.5',
+ 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:[2.0-SNAPSHOT,)',
+ )
+
+ testImplementation("com.amazonaws.serverless:aws-serverless-java-container-core:[2.0-SNAPSHOT,):tests")
+ testImplementation("org.apache.httpcomponents.client5:httpclient5:5.5")
+ testImplementation(platform("org.junit:junit-bom:5.13.1"))
+ testImplementation("org.junit.jupiter:junit-jupiter")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+}
+
+task buildZip(type: Zip) {
+ from compileJava
+ from processResources
+ into('lib') {
+ from(configurations.compileClasspath) {
+ exclude 'tomcat-embed-*'
+ }
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
+
+build.dependsOn buildZip
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 000000000..73d407dd8
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,180 @@
+#set($dollar = '$')
+
+
+ 4.0.0
+
+ \${groupId}
+ \${artifactId}
+ \${version}
+ jar
+
+ Serverless Spring Boot 3 API
+ https://github.com/aws/serverless-java-container
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.5
+
+
+
+ 17
+ 5.12.1
+
+
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot3
+ ${project.version}
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-core
+ ${project.version}
+ tests
+ test-jar
+ test
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.4.3
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ import
+ pom
+
+
+
+
+
+
+ shaded-jar
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+ org.apache.tomcat.embed:*
+
+
+
+
+
+
+
+
+
+
+ assembly-zip
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.2
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.8.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${dollar}{project.build.directory}${dollar}{file.separator}lib
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+ zip-assembly
+ package
+
+ single
+
+
+ ${dollar}{project.artifactId}-${dollar}{project.version}
+
+ src${dollar}{file.separator}assembly${dollar}{file.separator}bin.xml
+
+ false
+
+
+
+
+
+
+
+
+
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml
new file mode 100644
index 000000000..1e085057d
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml
@@ -0,0 +1,27 @@
+
+ lambda-package
+
+ zip
+
+ false
+
+
+
+ ${project.build.directory}${file.separator}lib
+ lib
+
+ tomcat-embed*
+
+
+
+
+ ${project.build.directory}${file.separator}classes
+
+ **
+
+ ${file.separator}
+
+
+
\ No newline at end of file
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/Application.java b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/Application.java
new file mode 100644
index 000000000..1b74086f7
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/Application.java
@@ -0,0 +1,24 @@
+#macro(loggingOff)
+ logging.level.root:OFF
+#end
+#set($logging = "#loggingOff()")
+#set($logging = $logging.replaceAll("\n", "").trim())
+package ${groupId};
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+
+import ${groupId}.controller.PingController;
+
+
+@SpringBootApplication
+// We use direct @Import instead of @ComponentScan to speed up cold starts
+// @ComponentScan(basePackages = "${groupId}.controller")
+@Import({ PingController.class })
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java
new file mode 100644
index 000000000..e022540c1
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java
@@ -0,0 +1,33 @@
+package ${groupId};
+
+
+import com.amazonaws.serverless.exceptions.ContainerInitializationException;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+public class StreamLambdaHandler implements RequestStreamHandler {
+ private static SpringBootLambdaContainerHandler handler;
+ static {
+ try {
+ handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
+ } 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 Spring Boot application", e);
+ }
+ }
+
+ @Override
+ public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
+ throws IOException {
+ handler.proxyStream(inputStream, outputStream, context);
+ }
+}
\ No newline at end of file
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java
new file mode 100644
index 000000000..94f517f07
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java
@@ -0,0 +1,20 @@
+package ${groupId}.controller;
+
+
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@RestController
+@EnableWebMvc
+public class PingController {
+ @RequestMapping(path = "/ping", method = RequestMethod.GET)
+ public Map ping() {
+ Map pong = new HashMap<>();
+ pong.put("pong", "Hello, World!");
+ return pong;
+ }
+}
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties
new file mode 100644
index 000000000..070e632fe
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+# Reduce logging level to make sure the application works with SAM local
+# https://github.com/aws/serverless-java-container/issues/134
+logging.level.root=WARN
\ No newline at end of file
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
new file mode 100644
index 000000000..26d5360bf
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java
@@ -0,0 +1,89 @@
+package ${groupId};
+
+
+import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
+import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
+import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import com.amazonaws.services.lambda.runtime.Context;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class StreamLambdaHandlerTest {
+
+ private static StreamLambdaHandler handler;
+ private static Context lambdaContext;
+
+ @BeforeAll
+ public static void setUp() {
+ handler = new StreamLambdaHandler();
+ lambdaContext = new MockLambdaContext();
+ }
+
+ @Test
+ public void ping_streamRequest_respondsWithHello() {
+ InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET)
+ .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
+ .buildStream();
+ ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
+
+ handle(requestStream, responseStream);
+
+ AwsProxyResponse response = readResponse(responseStream);
+ assertNotNull(response);
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
+
+ assertFalse(response.isBase64Encoded());
+
+ assertTrue(response.getBody().contains("pong"));
+ assertTrue(response.getBody().contains("Hello, World!"));
+
+ assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE));
+ assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON));
+ }
+
+ @Test
+ public void invalidResource_streamRequest_responds404() {
+ InputStream requestStream = new AwsProxyRequestBuilder("/pong", HttpMethod.GET)
+ .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
+ .buildStream();
+ ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
+
+ handle(requestStream, responseStream);
+
+ AwsProxyResponse response = readResponse(responseStream);
+ assertNotNull(response);
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatusCode());
+ }
+
+ private void handle(InputStream is, ByteArrayOutputStream os) {
+ try {
+ handler.handleRequest(is, os, lambdaContext);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) {
+ try {
+ return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("Error while parsing response: " + e.getMessage());
+ }
+ return null;
+ }
+}
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/template.yml
new file mode 100644
index 000000000..18c231878
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/template.yml
@@ -0,0 +1,52 @@
+#set($resourceName = $artifactId)
+#macro(replaceChar $originalName, $char)
+ #if($originalName.contains($char))
+ #set($tokens = $originalName.split($char))
+ #set($newResourceName = "")
+ #foreach($token in $tokens)
+ #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase())
+ #end
+ ${newResourceName}
+ #else
+ #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1))
+ ${newResourceName}
+ #end
+#end
+#set($resourceName = "#replaceChar($resourceName, '-')")
+#set($resourceName = "#replaceChar($resourceName, '.')")
+#set($resourceName = $resourceName.replaceAll("\n", "").trim())
+#macro(regionVar)
+ AWS::Region
+#end
+#set($awsRegion = "#regionVar()")
+#set($awsRegion = $awsRegion.replaceAll("\n", "").trim())
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: AWS Serverless Spring Boot 2 API - ${groupId}::${artifactId}
+Globals:
+ Api:
+ EndpointConfiguration: REGIONAL
+
+Resources:
+ ${resourceName}Function:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: ${groupId}.StreamLambdaHandler::handleRequest
+ Runtime: java25
+ CodeUri: .
+ MemorySize: 512
+ Policies: AWSLambdaBasicExecutionRole
+ Timeout: 30
+ Events:
+ ProxyResource:
+ Type: Api
+ Properties:
+ Path: /{proxy+}
+ Method: any
+
+Outputs:
+ ${resourceName}Api:
+ Description: URL for application
+ Value: !Sub 'https://${ServerlessRestApi}.execute-api.${${awsRegion}}.amazonaws.com/Prod/ping'
+ Export:
+ Name: ${resourceName}Api
diff --git a/aws-serverless-springboot4-archetype/src/test/resources/projects/base/archetype.properties b/aws-serverless-springboot4-archetype/src/test/resources/projects/base/archetype.properties
new file mode 100644
index 000000000..7df3bf6e1
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/test/resources/projects/base/archetype.properties
@@ -0,0 +1,3 @@
+groupId=test.service
+artifactId=springboot-archetype-test
+version=1.0-SNAPSHOT
diff --git a/aws-serverless-springboot4-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-springboot4-archetype/src/test/resources/projects/base/goal.txt
new file mode 100644
index 000000000..597acc768
--- /dev/null
+++ b/aws-serverless-springboot4-archetype/src/test/resources/projects/base/goal.txt
@@ -0,0 +1 @@
+package
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ebc634fbb..94456d1a8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.amazonaws.serverless
aws-serverless-java-container
pom
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
AWS Serverless Java container
A Java framework to run Spring, Spring Boot, Jersey, Spark, and Struts applications inside AWS Lambda
https://github.com/aws/serverless-java-container
@@ -29,9 +29,11 @@
aws-serverless-java-container-jersey
aws-serverless-java-container-spring
aws-serverless-java-container-springboot3
+ aws-serverless-java-container-springboot4
aws-serverless-jersey-archetype
aws-serverless-spring-archetype
aws-serverless-springboot3-archetype
+ aws-serverless-springboot4-archetype
@@ -78,9 +80,9 @@
0.7
12.1.1
- 2.19.1
+
2.0.17
- 5.12.2
+ 6.0.0
5.19.0
1.3
UTF-8
diff --git a/samples/springboot3/pet-store/build.gradle b/samples/springboot3/pet-store/build.gradle
index 653135db0..893605416 100644
--- a/samples/springboot3/pet-store/build.gradle
+++ b/samples/springboot3/pet-store/build.gradle
@@ -1,5 +1,10 @@
apply plugin: 'java'
+java {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+}
+
repositories {
mavenLocal()
mavenCentral()
diff --git a/samples/springboot4/alt-pet-store/README.md b/samples/springboot4/alt-pet-store/README.md
new file mode 100644
index 000000000..56cfca327
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/README.md
@@ -0,0 +1,56 @@
+# Serverless Spring Boot 4 example
+A basic pet store written with the [Spring Boot 4 framework](https://projects.spring.io/spring-boot/) and Spring Framework 7.0. Unlike older examples, this example is relying on the new
+`SpringDelegatingLambdaContainerHandler`, which you simply need to identify as a _handler_ of the Lambda function. The main configuration class identified as `MAIN_CLASS`
+environment variable or `Start-Class` or `Main-Class` entry in Manifest file. See provided `template.yml` file for reference.
+
+
+The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition.
+
+## Pre-requisites
+* [AWS CLI](https://aws.amazon.com/cli/)
+* [SAM CLI](https://github.com/awslabs/aws-sam-cli)
+* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/)
+
+## Deployment
+In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package
+```
+$ sam build
+```
+
+This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory.
+
+To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen
+
+```
+$ sam deploy --guided
+```
+
+Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL
+
+```
+...
+---------------------------------------------------------------------------------------------------------
+OutputKey-Description OutputValue
+---------------------------------------------------------------------------------------------------------
+PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets
+---------------------------------------------------------------------------------------------------------
+
+$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets
+```
+
+You can also try a complex request passing both path and request parameters to complex endpoint such as this:
+
+
+```
+@RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
+public String complexRequest(@RequestBody String body,
+ @PathVariable("gender") String gender,
+ @PathVariable("age") String age,
+ @RequestParam("name") String name
+)
+```
+For example.
+
+```
+curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST https://zuhd709386.execute-api.us-east-2.amazonaws.com/foo/male/bar/25?name=Ricky
+```
diff --git a/samples/springboot4/alt-pet-store/build.gradle b/samples/springboot4/alt-pet-store/build.gradle
new file mode 100644
index 000000000..7a6b13a49
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'java'
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_25
+ targetCompatibility = JavaVersion.VERSION_25
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {url "https://repo.spring.io/milestone"}
+ maven {url "https://repo.spring.io/snapshot"}
+}
+
+dependencies {
+ implementation (
+ implementation('org.springframework.boot:spring-boot-starter-web:3.4.5') {
+ exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
+ },
+ 'com.amazonaws.serverless:aws-serverless-java-container-springboot4:[2.0-SNAPSHOT,)',
+ 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
+ )
+}
+
+task buildZip(type: Zip) {
+ from compileJava
+ from processResources
+ into('lib') {
+ from(configurations.compileClasspath) {
+ exclude 'tomcat-embed-*'
+ }
+ }
+}
+
+build.dependsOn buildZip
diff --git a/samples/springboot4/alt-pet-store/pom.xml b/samples/springboot4/alt-pet-store/pom.xml
new file mode 100644
index 000000000..6f497e68b
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/pom.xml
@@ -0,0 +1,148 @@
+
+
+ 4.0.0
+
+ com.amazonaws.serverless.sample
+ petstore-springboot4-example
+ 2.0-SNAPSHOT
+ Spring Boot 4 example for the aws-serverless-java-container library
+ Simple pet store written with Spring Framework 7.0 and Spring Boot 4.0
+ https://aws.amazon.com/lambda/
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.0
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ 25
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ [2.2.0-SNAPSHOT,),[2.1.1,)
+
+
+
+
+
+ shaded-jar
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+ org.apache.tomcat.embed:*
+
+
+
+
+
+
+
+
+
+
+ assembly-zip
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.4
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.8.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/lib
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+ zip-assembly
+ package
+
+ single
+
+
+ ${project.artifactId}-${project.version}
+
+ src${file.separator}assembly${file.separator}bin.xml
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/springboot4/alt-pet-store/src/assembly/bin.xml b/samples/springboot4/alt-pet-store/src/assembly/bin.xml
new file mode 100644
index 000000000..1e085057d
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/assembly/bin.xml
@@ -0,0 +1,27 @@
+
+ lambda-package
+
+ zip
+
+ false
+
+
+
+ ${project.build.directory}${file.separator}lib
+ lib
+
+ tomcat-embed*
+
+
+
+
+ ${project.build.directory}${file.separator}classes
+
+ **
+
+ ${file.separator}
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
new file mode 100644
index 000000000..f5d30c519
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
@@ -0,0 +1,51 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import com.amazonaws.serverless.sample.springboot4.controller.PetsController;
+import com.amazonaws.serverless.sample.springboot4.filter.CognitoIdentityFilter;
+
+import jakarta.servlet.Filter;
+
+
+@SpringBootApplication
+@Import({ PetsController.class })
+public class Application {
+
+ // silence console logging
+ @Value("${logging.level.root:OFF}")
+ String message = "";
+
+ /*
+ * Create required HandlerMapping, to avoid several default HandlerMapping instances being created
+ */
+ @Bean
+ public HandlerMapping handlerMapping() {
+ return new RequestMappingHandlerMapping();
+ }
+
+ /*
+ * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
+ */
+ @Bean
+ public HandlerAdapter handlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ @Bean("CognitoIdentityFilter")
+ public Filter cognitoFilter() {
+ return new CognitoIdentityFilter();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
new file mode 100644
index 000000000..f31542e55
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.controller;
+
+
+
+import com.amazonaws.serverless.sample.springboot4.model.Pet;
+import com.amazonaws.serverless.sample.springboot4.model.PetData;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import java.security.Principal;
+import java.util.Optional;
+import java.util.UUID;
+
+
+@RestController
+@EnableWebMvc
+public class PetsController {
+
+ @RequestMapping(path = "/pets", method = RequestMethod.POST)
+ public Pet createPet(@RequestBody Pet newPet) {
+ if (newPet.getName() == null || newPet.getBreed() == null) {
+ return null;
+ }
+
+ Pet dbPet = newPet;
+ dbPet.setId(UUID.randomUUID().toString());
+ return dbPet;
+ }
+
+ @RequestMapping(path = "/pets", method = RequestMethod.GET)
+ public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) {
+ int queryLimit = 10;
+ if (limit.isPresent()) {
+ queryLimit = limit.get();
+ }
+
+ Pet[] outputPets = new Pet[queryLimit];
+
+ for (int i = 0; i < queryLimit; i++) {
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setName(PetData.getRandomName());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ outputPets[i] = newPet;
+ }
+
+ return outputPets;
+ }
+
+ @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
+ public Pet listPets() {
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ newPet.setName(PetData.getRandomName());
+ return newPet;
+ }
+
+ @RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
+ public String complexRequest(@RequestBody String body,
+ @PathVariable("gender") String gender,
+ @PathVariable("age") String age,
+ @RequestParam("name") String name
+ ) {
+ System.out.println("Body: " + body + " - " + gender + "/" + age + "/" + name);
+ return gender + "/" + age + "/" + name;
+ }
+
+}
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
new file mode 100644
index 000000000..705683ae2
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
@@ -0,0 +1,69 @@
+package com.amazonaws.serverless.sample.springboot4.filter;
+
+
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import java.io.IOException;
+
+
+/**
+ * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context
+ * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container
+ * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot4.StreamLambdaHandler} class.
+ */
+public class CognitoIdentityFilter implements Filter {
+ public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId";
+
+ private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // nothing to do in init
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
+ if (apiGwContext == null) {
+ log.warn("API Gateway context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+ if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) {
+ log.warn("API Gateway context object is not of valid type");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext;
+ if (ctx.getIdentity() == null) {
+ log.warn("Identity context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId();
+ if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) {
+ log.warn("Cognito identity id in request is null");
+ }
+ servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy() {
+ // nothing to do in destroy
+ }
+}
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
new file mode 100644
index 000000000..ddc63025b
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+public class Error {
+ private String message;
+
+ public Error(String errorMessage) {
+ message = errorMessage;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
new file mode 100644
index 000000000..b7e95ca97
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+import java.util.Date;
+
+
+public class Pet {
+ private String id;
+ private String breed;
+ private String name;
+ private Date dateOfBirth;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getBreed() {
+ return breed;
+ }
+
+ public void setBreed(String breed) {
+ this.breed = breed;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getDateOfBirth() {
+ return dateOfBirth;
+ }
+
+ public void setDateOfBirth(Date dateOfBirth) {
+ this.dateOfBirth = dateOfBirth;
+ }
+}
diff --git a/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
new file mode 100644
index 000000000..66bdd3663
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+
+public class PetData {
+ private static List breeds = new ArrayList<>();
+ static {
+ breeds.add("Afghan Hound");
+ breeds.add("Beagle");
+ breeds.add("Bernese Mountain Dog");
+ breeds.add("Bloodhound");
+ breeds.add("Dalmatian");
+ breeds.add("Jack Russell Terrier");
+ breeds.add("Norwegian Elkhound");
+ }
+
+ private static List names = new ArrayList<>();
+ static {
+ names.add("Bailey");
+ names.add("Bella");
+ names.add("Max");
+ names.add("Lucy");
+ names.add("Charlie");
+ names.add("Molly");
+ names.add("Buddy");
+ names.add("Daisy");
+ names.add("Rocky");
+ names.add("Maggie");
+ names.add("Jake");
+ names.add("Sophie");
+ names.add("Jack");
+ names.add("Sadie");
+ names.add("Toby");
+ names.add("Chloe");
+ names.add("Cody");
+ names.add("Bailey");
+ names.add("Buster");
+ names.add("Lola");
+ names.add("Duke");
+ names.add("Zoe");
+ names.add("Cooper");
+ names.add("Abby");
+ names.add("Riley");
+ names.add("Ginger");
+ names.add("Harley");
+ names.add("Roxy");
+ names.add("Bear");
+ names.add("Gracie");
+ names.add("Tucker");
+ names.add("Coco");
+ names.add("Murphy");
+ names.add("Sasha");
+ names.add("Lucky");
+ names.add("Lily");
+ names.add("Oliver");
+ names.add("Angel");
+ names.add("Sam");
+ names.add("Princess");
+ names.add("Oscar");
+ names.add("Emma");
+ names.add("Teddy");
+ names.add("Annie");
+ names.add("Winston");
+ names.add("Rosie");
+ }
+
+ public static List getBreeds() {
+ return breeds;
+ }
+
+ public static List getNames() {
+ return names;
+ }
+
+ public static String getRandomBreed() {
+ return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1));
+ }
+
+ public static String getRandomName() {
+ return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1));
+ }
+
+ public static Date getRandomDoB() {
+ GregorianCalendar gc = new GregorianCalendar();
+
+ int year = ThreadLocalRandom.current().nextInt(
+ Calendar.getInstance().get(Calendar.YEAR) - 15,
+ Calendar.getInstance().get(Calendar.YEAR)
+ );
+
+ gc.set(Calendar.YEAR, year);
+
+ int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
+
+ gc.set(Calendar.DAY_OF_YEAR, dayOfYear);
+ return gc.getTime();
+ }
+}
diff --git a/samples/springboot4/alt-pet-store/src/main/resources/logback.xml b/samples/springboot4/alt-pet-store/src/main/resources/logback.xml
new file mode 100644
index 000000000..81d891777
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/src/main/resources/logback.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/alt-pet-store/template.yml b/samples/springboot4/alt-pet-store/template.yml
new file mode 100644
index 000000000..4a7e8bb43
--- /dev/null
+++ b/samples/springboot4/alt-pet-store/template.yml
@@ -0,0 +1,41 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: Example Pet Store API written with SpringBoot4 spring-cloud-function web-proxy support
+
+Globals:
+ Api:
+ # API Gateway regional endpoints
+ EndpointConfiguration: REGIONAL
+
+Resources:
+ PetStoreFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+# AutoPublishAlias: bcn
+ FunctionName: pet-store-boot-4
+ Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler::handleRequest
+ Runtime: java25
+ SnapStart:
+ ApplyOn: PublishedVersions
+ CodeUri: .
+ MemorySize: 1024
+ Policies: AWSLambdaBasicExecutionRole
+ Timeout: 30
+ Environment:
+ Variables:
+ MAIN_CLASS: com.amazonaws.serverless.sample.springboot4.Application
+ Events:
+ HttpApiEvent:
+ Type: HttpApi
+ Properties:
+ TimeoutInMillis: 20000
+ PayloadFormatVersion: '1.0'
+
+Outputs:
+ SpringPetStoreApi:
+ Description: URL for application
+ Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets'
+ Export:
+ Name: SpringPetStoreApi
+
+
diff --git a/samples/springboot4/graphql-pet-store/README.md b/samples/springboot4/graphql-pet-store/README.md
new file mode 100644
index 000000000..9f1f8db3a
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/README.md
@@ -0,0 +1,38 @@
+# Serverless Spring Boot 4 with GraphQL example
+A basic pet store written with the [Spring Boot 4 framework](https://projects.spring.io/spring-boot/) and Spring Framework 7.0. Unlike older examples, this example uses the [Spring for GraphQl](https://docs.spring.io/spring-graphql/reference/) library.
+
+
+The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition.
+
+## Pre-requisites
+* [AWS CLI](https://aws.amazon.com/cli/)
+* [SAM CLI](https://github.com/awslabs/aws-sam-cli)
+* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/)
+
+## Deployment
+In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package
+```
+$ sam build
+```
+
+This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory.
+
+To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen
+
+```
+$ sam deploy --guided
+```
+
+Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` to make a call to the URL
+
+```
+...
+---------------------------------------------------------------------------------------------------------
+OutputKey-Description OutputValue
+---------------------------------------------------------------------------------------------------------
+PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl
+---------------------------------------------------------------------------------------------------------
+
+$ curl -X POST https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl -d '{"query":"query petDetails {\n petById(id: \"pet-1\") {\n id\n name\n breed\n owner {\n id\n firstName\n lastName\n }\n }\n}","operationName":"petDetails"}' -H "Content-Type: application/json"
+
+```
\ No newline at end of file
diff --git a/samples/springboot4/graphql-pet-store/pom.xml b/samples/springboot4/graphql-pet-store/pom.xml
new file mode 100644
index 000000000..08835769c
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/pom.xml
@@ -0,0 +1,168 @@
+
+
+ 4.0.0
+
+ com.amazonaws.serverless.sample
+ serverless-springboot4-graphql-example
+ 2.0-SNAPSHOT
+ Spring Boot 4 GraphQL example for the aws-serverless-java-container library
+ GraphQL pet store written with Spring Framework 7.0 and Spring Boot 4.0
+ https://aws.amazon.com/lambda/
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.0
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ 25
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-graphql
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.graphql
+ spring-graphql-test
+ test
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ [2.0.0-SNAPSHOT,),[2.0.0-M1,)
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.18.2
+
+
+
+
+
+ shaded-jar
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+ org.apache.tomcat.embed:*
+
+
+
+
+
+
+
+
+
+
+ assembly-zip
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.4
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.8.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/lib
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+ zip-assembly
+ package
+
+ single
+
+
+ ${project.artifactId}-${project.version}
+
+ src${file.separator}assembly${file.separator}bin.xml
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/springboot4/graphql-pet-store/src/assembly/bin.xml b/samples/springboot4/graphql-pet-store/src/assembly/bin.xml
new file mode 100644
index 000000000..efc312c25
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/assembly/bin.xml
@@ -0,0 +1,27 @@
+
+ lambda-package
+
+ zip
+
+ false
+
+
+
+ ${project.build.directory}${file.separator}lib
+ lib
+
+ tomcat-embed*
+
+
+
+
+ ${project.build.directory}${file.separator}classes
+
+ **
+
+ ${file.separator}
+
+
+
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
new file mode 100644
index 000000000..b60367223
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
@@ -0,0 +1,43 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+import com.amazonaws.serverless.sample.springboot4.controller.PetsController;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+
+@SpringBootApplication
+@Import({ PetsController.class })
+public class Application {
+
+ // silence console logging
+ @Value("${logging.level.root:OFF}")
+ String message = "";
+
+ /*
+ * Create required HandlerMapping, to avoid several default HandlerMapping instances being created
+ */
+ @Bean
+ public HandlerMapping handlerMapping() {
+ return new RequestMappingHandlerMapping();
+ }
+
+ /*
+ * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
+ */
+ @Bean
+ public HandlerAdapter handlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java
new file mode 100644
index 000000000..863af6351
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java
@@ -0,0 +1,44 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+
+import com.amazonaws.serverless.exceptions.ContainerInitializationException;
+import com.amazonaws.serverless.proxy.internal.testutils.Timer;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler;
+import com.amazonaws.serverless.sample.springboot4.filter.CognitoIdentityFilter;
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.FilterRegistration;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.EnumSet;
+
+
+public class StreamLambdaHandler implements RequestStreamHandler {
+ private static SpringDelegatingLambdaContainerHandler handler;
+ static {
+ try {
+ handler = new SpringDelegatingLambdaContainerHandler(Application.class);
+ } 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 Spring Boot application", e);
+ }
+ }
+
+ public StreamLambdaHandler() {
+ // we enable the timer for debugging. This SHOULD NOT be enabled in production.
+ Timer.enable();
+ }
+
+ @Override
+ public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
+ throws IOException {
+ handler.handleRequest(inputStream, outputStream, context);
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
new file mode 100644
index 000000000..c76a624e1
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
@@ -0,0 +1,21 @@
+package com.amazonaws.serverless.sample.springboot4.controller;
+
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.graphql.data.method.annotation.SchemaMapping;
+import org.springframework.stereotype.Controller;
+import com.amazonaws.serverless.sample.springboot4.model.Owner;
+import com.amazonaws.serverless.sample.springboot4.model.Pet;
+
+@Controller
+public class PetsController {
+ @QueryMapping
+ public Pet petById(@Argument String id) {
+ return Pet.getById(id);
+ }
+
+ @SchemaMapping
+ public Owner owner(Pet pet) {
+ return Owner.getById(pet.ownerId());
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
new file mode 100644
index 000000000..705683ae2
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
@@ -0,0 +1,69 @@
+package com.amazonaws.serverless.sample.springboot4.filter;
+
+
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import java.io.IOException;
+
+
+/**
+ * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context
+ * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container
+ * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot4.StreamLambdaHandler} class.
+ */
+public class CognitoIdentityFilter implements Filter {
+ public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId";
+
+ private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // nothing to do in init
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
+ if (apiGwContext == null) {
+ log.warn("API Gateway context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+ if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) {
+ log.warn("API Gateway context object is not of valid type");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext;
+ if (ctx.getIdentity() == null) {
+ log.warn("Identity context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId();
+ if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) {
+ log.warn("Cognito identity id in request is null");
+ }
+ servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy() {
+ // nothing to do in destroy
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Owner.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Owner.java
new file mode 100644
index 000000000..5349a85b3
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Owner.java
@@ -0,0 +1,20 @@
+package com.amazonaws.serverless.sample.springboot4.model;
+
+import java.util.Arrays;
+import java.util.List;
+
+public record Owner (String id, String firstName, String lastName) {
+
+ private static List owners = Arrays.asList(
+ new Owner("owner-1", "Joshua", "Bloch"),
+ new Owner("owner-2", "Douglas", "Adams"),
+ new Owner("owner-3", "Bill", "Bryson")
+ );
+
+ public static Owner getById(String id) {
+ return owners.stream()
+ .filter(owner -> owner.id().equals(id))
+ .findFirst()
+ .orElse(null);
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
new file mode 100644
index 000000000..b14199453
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
@@ -0,0 +1,20 @@
+package com.amazonaws.serverless.sample.springboot4.model;
+
+import java.util.Arrays;
+import java.util.List;
+
+public record Pet (String id, String name, String breed, String ownerId) {
+
+ private static List pets = Arrays.asList(
+ new Pet("pet-1", "Alpha", "Bulldog", "owner-1"),
+ new Pet("pet-2", "Max", "German Shepherd", "owner-2"),
+ new Pet("pet-3", "Rockie", "Golden Retriever", "owner-3")
+ );
+
+ public static Pet getById(String id) {
+ return pets.stream()
+ .filter(pet -> pet.id().equals(id))
+ .findFirst()
+ .orElse(null);
+ }
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/resources/graphql/schema.graphqls b/samples/springboot4/graphql-pet-store/src/main/resources/graphql/schema.graphqls
new file mode 100644
index 000000000..293cdcc40
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/resources/graphql/schema.graphqls
@@ -0,0 +1,16 @@
+type Query {
+ petById(id: ID): Pet
+}
+
+type Pet {
+ id: ID
+ name: String
+ breed: String
+ owner: Owner
+}
+
+type Owner {
+ id: ID
+ firstName: String
+ lastName: String
+}
diff --git a/samples/springboot4/graphql-pet-store/src/main/resources/logback.xml b/samples/springboot4/graphql-pet-store/src/main/resources/logback.xml
new file mode 100644
index 000000000..8ff988992
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/src/main/resources/logback.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/samples/springboot4/graphql-pet-store/template.yml b/samples/springboot4/graphql-pet-store/template.yml
new file mode 100644
index 000000000..5db3eefd3
--- /dev/null
+++ b/samples/springboot4/graphql-pet-store/template.yml
@@ -0,0 +1,35 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: Example Pet Store API written with SpringBoot4, Spring for GraphQl and the aws-serverless-java-container library
+
+Globals:
+ Api:
+ # API Gateway regional endpoints
+ EndpointConfiguration: REGIONAL
+
+Resources:
+ PetStoreFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: com.amazonaws.serverless.sample.springboot4.StreamLambdaHandler::handleRequest
+ Runtime: java25
+ CodeUri: .
+ MemorySize: 1024
+ Policies: AWSLambdaBasicExecutionRole
+ Timeout: 60
+ Environment:
+ Variables:
+ MAIN_CLASS: com.amazonaws.serverless.sample.springboot4.Application
+ Events:
+ HttpApiEvent:
+ Type: HttpApi
+ Properties:
+ TimeoutInMillis: 20000
+ PayloadFormatVersion: '1.0'
+
+Outputs:
+ SpringBootPetStoreApi:
+ Description: URL for application
+ Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/graphql'
+ Export:
+ Name: SpringBootPetStoreApi
diff --git a/samples/springboot4/pet-store-native/.gitignore b/samples/springboot4/pet-store-native/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/samples/springboot4/pet-store-native/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.jar b/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..cb28b0e37
Binary files /dev/null and b/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.properties b/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..7d02699af
--- /dev/null
+++ b/samples/springboot4/pet-store-native/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/samples/springboot4/pet-store-native/Dockerfile b/samples/springboot4/pet-store-native/Dockerfile
new file mode 100644
index 000000000..ec8eb4a0d
--- /dev/null
+++ b/samples/springboot4/pet-store-native/Dockerfile
@@ -0,0 +1,37 @@
+FROM public.ecr.aws/amazonlinux/amazonlinux:2023
+
+RUN yum -y update \
+ && yum install -y unzip tar gzip bzip2-devel ed gcc gcc-c++ gcc-gfortran \
+ less libcurl-devel openssl openssl-devel readline-devel xz-devel \
+ zlib-devel glibc-static zlib-static \
+ && rm -rf /var/cache/yum
+
+# Graal VM
+ENV GRAAL_VERSION 25.0.1
+ENV ARCHITECTURE aarch64
+ENV GRAAL_FILENAME graalvm-community-jdk-${GRAAL_VERSION}_linux-${ARCHITECTURE}_bin.tar.gz
+RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAAL_VERSION}/${GRAAL_FILENAME} | tar -xvz
+RUN mv graalvm-community-openjdk-${GRAAL_VERSION}* /usr/lib/graalvm
+ENV JAVA_HOME /usr/lib/graalvm
+
+# Maven
+ENV MVN_VERSION 3.9.9
+ENV MVN_FOLDERNAME apache-maven-${MVN_VERSION}
+ENV MVN_FILENAME apache-maven-${MVN_VERSION}-bin.tar.gz
+RUN curl -4 -L https://archive.apache.org/dist/maven/maven-3/${MVN_VERSION}/binaries/${MVN_FILENAME} | tar -xvz
+RUN mv $MVN_FOLDERNAME /usr/lib/maven
+RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn
+
+# Gradle
+ENV GRADLE_VERSION 7.4.1
+ENV GRADLE_FOLDERNAME gradle-${GRADLE_VERSION}
+ENV GRADLE_FILENAME gradle-${GRADLE_VERSION}-bin.zip
+RUN curl -LO https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
+RUN unzip gradle-${GRADLE_VERSION}-bin.zip
+RUN mv $GRADLE_FOLDERNAME /usr/lib/gradle
+RUN ln -s /usr/lib/gradle/bin/gradle /usr/bin/gradle
+
+VOLUME /project
+WORKDIR /project
+
+WORKDIR /pet-store-native
diff --git a/samples/springboot4/pet-store-native/README.md b/samples/springboot4/pet-store-native/README.md
new file mode 100644
index 000000000..57994a4e6
--- /dev/null
+++ b/samples/springboot4/pet-store-native/README.md
@@ -0,0 +1,39 @@
+In this sample, you'll build a native GraalVM image for running Spring Boot 4.0 and Spring Framework 7.0 web workloads in AWS Lambda.
+
+**Important**: Spring Boot 4.0 requires GraalVM 25 for native image compilation. GraalVM 21 is not compatible.
+
+## To build the sample
+
+You first need to build the function, then you will deploy it to AWS Lambda.
+
+Please note that the sample is for `x86` architectures. In case you want to build and run it on ARM, e.g. Apple Mac M1, M2, ...
+you must change the according line in the `Dockerfile` to `ENV ARCHITECTURE aarch64`.
+In addition, uncomment the `arm64` Architectures section in `template.yml`.
+
+### Step 1 - Build the native image
+
+Before starting the build, you must clone or download the code in **pet-store-native**.
+
+1. Change into the project directory: `samples/springboot4/pet-store-native`
+2. Run the following to build a Docker container image with GraalVM 25 which will include all the necessary dependencies to build the application
+ ```
+ docker build -t al2023-graalvm25:native-web .
+ ```
+3. Build the application within the previously created build image
+ ```
+ docker run -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 al2023-graalvm25:native-web mvn clean native:compile -Pnative
+ ```
+4. After the build finishes, you need to deploy the function:
+ ```
+ sam deploy --guided
+ ```
+
+This will deploy your application and will attach an AWS API Gateway
+Once the deployment is finished you should see the following:
+```
+Key ServerlessWebNativeApi
+Description URL for application
+Value https://xxxxxxxx.execute-api.us-east-2.amazonaws.com/pets
+```
+
+You can now simply execute GET on this URL and see the listing fo all pets.
diff --git a/samples/springboot4/pet-store-native/mvnw b/samples/springboot4/pet-store-native/mvnw
new file mode 100755
index 000000000..8d937f4c1
--- /dev/null
+++ b/samples/springboot4/pet-store-native/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/samples/springboot4/pet-store-native/mvnw.cmd b/samples/springboot4/pet-store-native/mvnw.cmd
new file mode 100644
index 000000000..f80fbad3e
--- /dev/null
+++ b/samples/springboot4/pet-store-native/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/samples/springboot4/pet-store-native/pom.xml b/samples/springboot4/pet-store-native/pom.xml
new file mode 100644
index 000000000..fef6e7dfc
--- /dev/null
+++ b/samples/springboot4/pet-store-native/pom.xml
@@ -0,0 +1,140 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.0
+
+
+ com.amazonaws.serverless.sample
+ pet-store-native-springboot4
+ 0.0.1-SNAPSHOT
+ pet-store-native-springboot4
+ Sample of AWS with Spring Boot 4.0 Native
+
+ 25
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ [2.0.0-SNAPSHOT,),[2.0.0-M1,)
+
+
+
+ org.crac
+ crac
+ runtime
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.18.2
+
+
+ com.amazonaws
+ aws-lambda-java-events
+ 3.15.0
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.2.3
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ native
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/ --enable-preview
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+ pet-store-native
+
+ --enable-url-protocols=http
+ -march=compatibility
+
+
+
+
+
+ build
+
+ package
+
+
+ test
+
+ test
+
+ test
+
+
+
+
+ maven-assembly-plugin
+
+
+ native-zip
+ package
+
+ single
+
+ false
+
+
+
+
+ src/assembly/native.xml
+
+
+
+
+
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store-native/src/assembly/java.xml b/samples/springboot4/pet-store-native/src/assembly/java.xml
new file mode 100644
index 000000000..bd4961b58
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/assembly/java.xml
@@ -0,0 +1,31 @@
+
+ java-zip
+
+ zip
+
+
+
+
+ target/classes
+ /
+
+
+ src/shell/java
+ /
+ true
+ 0775
+
+ bootstrap
+
+
+
+
+
+ /lib
+ false
+ runtime
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store-native/src/assembly/native.xml b/samples/springboot4/pet-store-native/src/assembly/native.xml
new file mode 100644
index 000000000..9bd97a5b7
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/assembly/native.xml
@@ -0,0 +1,29 @@
+
+ native-zip
+
+ zip
+
+
+
+
+ src/shell/native
+ /
+ true
+ 0775
+
+ bootstrap
+
+
+
+ target
+ /
+ true
+ 0775
+
+ pet-store-native
+
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/DemoApplication.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/DemoApplication.java
new file mode 100644
index 000000000..3f1d42559
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/DemoApplication.java
@@ -0,0 +1,12 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DemoApplication {
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/HelloController.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/HelloController.java
new file mode 100644
index 000000000..4f0abad79
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/HelloController.java
@@ -0,0 +1,17 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloController {
+
+ public HelloController() {
+ System.out.println("Creating controller");
+ }
+
+ @GetMapping("/hello")
+ public String something(){
+ return "Hello World";
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
new file mode 100644
index 000000000..7576b7898
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.controller;
+
+
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import com.amazonaws.serverless.sample.springboot4.model.Pet;
+import com.amazonaws.serverless.sample.springboot4.model.PetData;
+
+import java.security.Principal;
+import java.util.Optional;
+import java.util.UUID;
+
+
+@RestController
+@EnableWebMvc
+public class PetsController {
+ @PostMapping(path = "/pets")
+ public Pet createPet(@RequestBody Pet newPet) {
+ System.out.println("==> Creating Pet: " + newPet);
+ if (newPet.getName() == null || newPet.getBreed() == null) {
+ return null;
+ }
+
+ Pet dbPet = newPet;
+ dbPet.setId(UUID.randomUUID().toString());
+ return dbPet;
+ }
+
+ @GetMapping(path = "/pets")
+ public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) {
+ System.out.println("==> Listing Pets");
+ int queryLimit = 10;
+ if (limit.isPresent()) {
+ queryLimit = limit.get();
+ }
+
+ Pet[] outputPets = new Pet[queryLimit];
+
+ for (int i = 0; i < queryLimit; i++) {
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setName(PetData.getRandomName());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ outputPets[i] = newPet;
+ }
+
+ return outputPets;
+ }
+
+ @GetMapping(path = "/pets/{petId}")
+ public Pet listPets() {
+ System.out.println("==> Listing Pets");
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ newPet.setName(PetData.getRandomName());
+ return newPet;
+ }
+
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
new file mode 100644
index 000000000..705683ae2
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
@@ -0,0 +1,69 @@
+package com.amazonaws.serverless.sample.springboot4.filter;
+
+
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import java.io.IOException;
+
+
+/**
+ * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context
+ * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container
+ * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot4.StreamLambdaHandler} class.
+ */
+public class CognitoIdentityFilter implements Filter {
+ public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId";
+
+ private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // nothing to do in init
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
+ if (apiGwContext == null) {
+ log.warn("API Gateway context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+ if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) {
+ log.warn("API Gateway context object is not of valid type");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext;
+ if (ctx.getIdentity() == null) {
+ log.warn("Identity context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId();
+ if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) {
+ log.warn("Cognito identity id in request is null");
+ }
+ servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy() {
+ // nothing to do in destroy
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
new file mode 100644
index 000000000..ddc63025b
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+public class Error {
+ private String message;
+
+ public Error(String errorMessage) {
+ message = errorMessage;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
new file mode 100644
index 000000000..b7e95ca97
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+import java.util.Date;
+
+
+public class Pet {
+ private String id;
+ private String breed;
+ private String name;
+ private Date dateOfBirth;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getBreed() {
+ return breed;
+ }
+
+ public void setBreed(String breed) {
+ this.breed = breed;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getDateOfBirth() {
+ return dateOfBirth;
+ }
+
+ public void setDateOfBirth(Date dateOfBirth) {
+ this.dateOfBirth = dateOfBirth;
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
new file mode 100644
index 000000000..66bdd3663
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+
+public class PetData {
+ private static List breeds = new ArrayList<>();
+ static {
+ breeds.add("Afghan Hound");
+ breeds.add("Beagle");
+ breeds.add("Bernese Mountain Dog");
+ breeds.add("Bloodhound");
+ breeds.add("Dalmatian");
+ breeds.add("Jack Russell Terrier");
+ breeds.add("Norwegian Elkhound");
+ }
+
+ private static List names = new ArrayList<>();
+ static {
+ names.add("Bailey");
+ names.add("Bella");
+ names.add("Max");
+ names.add("Lucy");
+ names.add("Charlie");
+ names.add("Molly");
+ names.add("Buddy");
+ names.add("Daisy");
+ names.add("Rocky");
+ names.add("Maggie");
+ names.add("Jake");
+ names.add("Sophie");
+ names.add("Jack");
+ names.add("Sadie");
+ names.add("Toby");
+ names.add("Chloe");
+ names.add("Cody");
+ names.add("Bailey");
+ names.add("Buster");
+ names.add("Lola");
+ names.add("Duke");
+ names.add("Zoe");
+ names.add("Cooper");
+ names.add("Abby");
+ names.add("Riley");
+ names.add("Ginger");
+ names.add("Harley");
+ names.add("Roxy");
+ names.add("Bear");
+ names.add("Gracie");
+ names.add("Tucker");
+ names.add("Coco");
+ names.add("Murphy");
+ names.add("Sasha");
+ names.add("Lucky");
+ names.add("Lily");
+ names.add("Oliver");
+ names.add("Angel");
+ names.add("Sam");
+ names.add("Princess");
+ names.add("Oscar");
+ names.add("Emma");
+ names.add("Teddy");
+ names.add("Annie");
+ names.add("Winston");
+ names.add("Rosie");
+ }
+
+ public static List getBreeds() {
+ return breeds;
+ }
+
+ public static List getNames() {
+ return names;
+ }
+
+ public static String getRandomBreed() {
+ return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1));
+ }
+
+ public static String getRandomName() {
+ return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1));
+ }
+
+ public static Date getRandomDoB() {
+ GregorianCalendar gc = new GregorianCalendar();
+
+ int year = ThreadLocalRandom.current().nextInt(
+ Calendar.getInstance().get(Calendar.YEAR) - 15,
+ Calendar.getInstance().get(Calendar.YEAR)
+ );
+
+ gc.set(Calendar.YEAR, year);
+
+ int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
+
+ gc.set(Calendar.DAY_OF_YEAR, dayOfYear);
+ return gc.getTime();
+ }
+}
diff --git a/samples/springboot4/pet-store-native/src/main/resources/META-INF/.gitignore b/samples/springboot4/pet-store-native/src/main/resources/META-INF/.gitignore
new file mode 100644
index 000000000..0726bbaa2
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/resources/META-INF/.gitignore
@@ -0,0 +1 @@
+/native-image/
diff --git a/samples/springboot4/pet-store-native/src/main/resources/application.properties b/samples/springboot4/pet-store-native/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/samples/springboot4/pet-store-native/src/shell/java/bootstrap b/samples/springboot4/pet-store-native/src/shell/java/bootstrap
new file mode 100644
index 000000000..e30ee22e9
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/shell/java/bootstrap
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+cd ${LAMBDA_TASK_ROOT:-.}
+
+java -Dspring.main.web-application-type=none -Dlogging.level.org.springframework=DEBUG \
+ -noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \
+ -cp .:`echo lib/*.jar | tr ' ' :` com.amazonaws.serverless.sample.springboot4.DemoApplication
\ No newline at end of file
diff --git a/samples/springboot4/pet-store-native/src/shell/native/bootstrap b/samples/springboot4/pet-store-native/src/shell/native/bootstrap
new file mode 100644
index 000000000..0156b090b
--- /dev/null
+++ b/samples/springboot4/pet-store-native/src/shell/native/bootstrap
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cd ${LAMBDA_TASK_ROOT:-.}
+
+./pet-store-native -Dlogging.level.org.springframework=DEBUG -Dlogging.level.com.amazonaws.serverless.proxy.spring=DEBUG
diff --git a/samples/springboot4/pet-store-native/template.yaml b/samples/springboot4/pet-store-native/template.yaml
new file mode 100644
index 000000000..d0b63d9a6
--- /dev/null
+++ b/samples/springboot4/pet-store-native/template.yaml
@@ -0,0 +1,33 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: Serverless Java Container GraalVM with Spring Boot 4
+Resources:
+ ServerlessWebNativeFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ MemorySize: 512
+ FunctionName: pet-store-native-springboot4
+ Timeout: 15
+ CodeUri: ./target/pet-store-native-springboot4-0.0.1-SNAPSHOT-native-zip.zip
+ Handler: NOP
+ Runtime: provided.al2023
+ Architectures:
+ - arm64
+ Events:
+ HttpApiEvent:
+ Type: HttpApi
+ Properties:
+ TimeoutInMillis: 20000
+ PayloadFormatVersion: '1.0'
+
+Globals:
+ Api:
+ # API Gateway regional endpoints
+ EndpointConfiguration: REGIONAL
+Outputs:
+ ServerlessWebNativeApi:
+ Description: URL for application
+ Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets'
+ Export:
+ Name: ServerlessWebNativeApi
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store/README.md b/samples/springboot4/pet-store/README.md
new file mode 100644
index 000000000..40955e968
--- /dev/null
+++ b/samples/springboot4/pet-store/README.md
@@ -0,0 +1,36 @@
+# Serverless Spring Boot 4 example
+A basic pet store written with the [Spring Boot 4 framework](https://projects.spring.io/spring-boot/) and Spring Framework 7.0. The `StreamLambdaHandler` object is the main entry point for Lambda.
+
+The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition.
+
+## Pre-requisites
+* [AWS CLI](https://aws.amazon.com/cli/)
+* [SAM CLI](https://github.com/awslabs/aws-sam-cli)
+* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/)
+
+## Deployment
+In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package
+```
+$ sam build
+```
+
+This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory.
+
+To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen
+
+```
+$ sam deploy --guided
+```
+
+Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL
+
+```
+...
+---------------------------------------------------------------------------------------------------------
+OutputKey-Description OutputValue
+---------------------------------------------------------------------------------------------------------
+PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets
+---------------------------------------------------------------------------------------------------------
+
+$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets
+```
\ No newline at end of file
diff --git a/samples/springboot4/pet-store/build.gradle b/samples/springboot4/pet-store/build.gradle
new file mode 100644
index 000000000..7a6b13a49
--- /dev/null
+++ b/samples/springboot4/pet-store/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'java'
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_25
+ targetCompatibility = JavaVersion.VERSION_25
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {url "https://repo.spring.io/milestone"}
+ maven {url "https://repo.spring.io/snapshot"}
+}
+
+dependencies {
+ implementation (
+ implementation('org.springframework.boot:spring-boot-starter-web:3.4.5') {
+ exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
+ },
+ 'com.amazonaws.serverless:aws-serverless-java-container-springboot4:[2.0-SNAPSHOT,)',
+ 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
+ )
+}
+
+task buildZip(type: Zip) {
+ from compileJava
+ from processResources
+ into('lib') {
+ from(configurations.compileClasspath) {
+ exclude 'tomcat-embed-*'
+ }
+ }
+}
+
+build.dependsOn buildZip
diff --git a/samples/springboot4/pet-store/pom.xml b/samples/springboot4/pet-store/pom.xml
new file mode 100644
index 000000000..937998eaf
--- /dev/null
+++ b/samples/springboot4/pet-store/pom.xml
@@ -0,0 +1,160 @@
+
+
+ 4.0.0
+
+ com.amazonaws.serverless.sample
+ serverless-springboot4-example
+ 2.0-SNAPSHOT
+ Spring Boot 4 example for the aws-serverless-java-container library
+ Simple pet store written with Spring Framework 7.0 and Spring Boot 4.0
+ https://aws.amazon.com/lambda/
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.0
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ 25
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ [2.0.0-SNAPSHOT,),[2.0.0-M1,)
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.18.2
+
+
+
+
+
+ shaded-jar
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+ org.apache.tomcat.embed:*
+
+
+
+
+
+
+
+
+
+
+ assembly-zip
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.4
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.8.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/lib
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+ zip-assembly
+ package
+
+ single
+
+
+ ${project.artifactId}-${project.version}
+
+ src${file.separator}assembly${file.separator}bin.xml
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/springboot4/pet-store/src/assembly/bin.xml b/samples/springboot4/pet-store/src/assembly/bin.xml
new file mode 100644
index 000000000..1e085057d
--- /dev/null
+++ b/samples/springboot4/pet-store/src/assembly/bin.xml
@@ -0,0 +1,27 @@
+
+ lambda-package
+
+ zip
+
+ false
+
+
+
+ ${project.build.directory}${file.separator}lib
+ lib
+
+ tomcat-embed*
+
+
+
+
+ ${project.build.directory}${file.separator}classes
+
+ **
+
+ ${file.separator}
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
new file mode 100644
index 000000000..13a80b32a
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/Application.java
@@ -0,0 +1,49 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+import com.amazonaws.serverless.sample.springboot4.controller.PetsController;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+
+@SpringBootApplication
+@Import({ PetsController.class })
+public class Application {
+
+ // silence console logging
+ @Value("${logging.level.root:OFF}")
+ String message = "";
+
+ /*
+ * Create required HandlerMapping, to avoid several default HandlerMapping instances being created
+ */
+ @Bean
+ public HandlerMapping handlerMapping() {
+ return new RequestMappingHandlerMapping();
+ }
+
+ /*
+ * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
+ */
+ @Bean
+ public HandlerAdapter handlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java
new file mode 100644
index 000000000..61cde4fac
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/StreamLambdaHandler.java
@@ -0,0 +1,50 @@
+package com.amazonaws.serverless.sample.springboot4;
+
+
+import com.amazonaws.serverless.exceptions.ContainerInitializationException;
+import com.amazonaws.serverless.proxy.internal.testutils.Timer;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
+import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
+import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
+import com.amazonaws.serverless.sample.springboot4.filter.CognitoIdentityFilter;
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.FilterRegistration;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.EnumSet;
+
+
+public class StreamLambdaHandler implements RequestStreamHandler {
+ private static SpringBootLambdaContainerHandler handler;
+ static {
+ try {
+ handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
+
+ // we use the onStartup method of the handler to register our custom filter
+ handler.onStartup(servletContext -> {
+ FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class);
+ registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
+ });
+ } 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 Spring Boot application", e);
+ }
+ }
+
+ public StreamLambdaHandler() {
+ // we enable the timer for debugging. This SHOULD NOT be enabled in production.
+ Timer.enable();
+ }
+
+ @Override
+ public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
+ throws IOException {
+ handler.proxyStream(inputStream, outputStream, context);
+ }
+}
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
new file mode 100644
index 000000000..cb80068bc
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/controller/PetsController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.controller;
+
+
+
+import com.amazonaws.serverless.sample.springboot4.model.Pet;
+import com.amazonaws.serverless.sample.springboot4.model.PetData;
+
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import java.security.Principal;
+import java.util.Optional;
+import java.util.UUID;
+
+
+@RestController
+@EnableWebMvc
+public class PetsController {
+ @RequestMapping(path = "/pets", method = RequestMethod.POST)
+ public Pet createPet(@RequestBody Pet newPet) {
+ if (newPet.getName() == null || newPet.getBreed() == null) {
+ return null;
+ }
+
+ Pet dbPet = newPet;
+ dbPet.setId(UUID.randomUUID().toString());
+ return dbPet;
+ }
+
+ @RequestMapping(path = "/pets", method = RequestMethod.GET)
+ public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) {
+ int queryLimit = 10;
+ if (limit.isPresent()) {
+ queryLimit = limit.get();
+ }
+
+ Pet[] outputPets = new Pet[queryLimit];
+
+ for (int i = 0; i < queryLimit; i++) {
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setName(PetData.getRandomName());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ outputPets[i] = newPet;
+ }
+
+ return outputPets;
+ }
+
+ @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
+ public Pet listPets() {
+ Pet newPet = new Pet();
+ newPet.setId(UUID.randomUUID().toString());
+ newPet.setBreed(PetData.getRandomBreed());
+ newPet.setDateOfBirth(PetData.getRandomDoB());
+ newPet.setName(PetData.getRandomName());
+ return newPet;
+ }
+
+}
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
new file mode 100644
index 000000000..705683ae2
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/filter/CognitoIdentityFilter.java
@@ -0,0 +1,69 @@
+package com.amazonaws.serverless.sample.springboot4.filter;
+
+
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+import java.io.IOException;
+
+
+/**
+ * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context
+ * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container
+ * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot4.StreamLambdaHandler} class.
+ */
+public class CognitoIdentityFilter implements Filter {
+ public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId";
+
+ private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // nothing to do in init
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY);
+ if (apiGwContext == null) {
+ log.warn("API Gateway context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+ if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) {
+ log.warn("API Gateway context object is not of valid type");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext;
+ if (ctx.getIdentity() == null) {
+ log.warn("Identity context is null");
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId();
+ if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) {
+ log.warn("Cognito identity id in request is null");
+ }
+ servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy() {
+ // nothing to do in destroy
+ }
+}
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
new file mode 100644
index 000000000..ddc63025b
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Error.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+public class Error {
+ private String message;
+
+ public Error(String errorMessage) {
+ message = errorMessage;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
new file mode 100644
index 000000000..b7e95ca97
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/Pet.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+import java.util.Date;
+
+
+public class Pet {
+ private String id;
+ private String breed;
+ private String name;
+ private Date dateOfBirth;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getBreed() {
+ return breed;
+ }
+
+ public void setBreed(String breed) {
+ this.breed = breed;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getDateOfBirth() {
+ return dateOfBirth;
+ }
+
+ public void setDateOfBirth(Date dateOfBirth) {
+ this.dateOfBirth = dateOfBirth;
+ }
+}
diff --git a/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
new file mode 100644
index 000000000..66bdd3663
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot4/model/PetData.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package com.amazonaws.serverless.sample.springboot4.model;
+
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+
+public class PetData {
+ private static List breeds = new ArrayList<>();
+ static {
+ breeds.add("Afghan Hound");
+ breeds.add("Beagle");
+ breeds.add("Bernese Mountain Dog");
+ breeds.add("Bloodhound");
+ breeds.add("Dalmatian");
+ breeds.add("Jack Russell Terrier");
+ breeds.add("Norwegian Elkhound");
+ }
+
+ private static List names = new ArrayList<>();
+ static {
+ names.add("Bailey");
+ names.add("Bella");
+ names.add("Max");
+ names.add("Lucy");
+ names.add("Charlie");
+ names.add("Molly");
+ names.add("Buddy");
+ names.add("Daisy");
+ names.add("Rocky");
+ names.add("Maggie");
+ names.add("Jake");
+ names.add("Sophie");
+ names.add("Jack");
+ names.add("Sadie");
+ names.add("Toby");
+ names.add("Chloe");
+ names.add("Cody");
+ names.add("Bailey");
+ names.add("Buster");
+ names.add("Lola");
+ names.add("Duke");
+ names.add("Zoe");
+ names.add("Cooper");
+ names.add("Abby");
+ names.add("Riley");
+ names.add("Ginger");
+ names.add("Harley");
+ names.add("Roxy");
+ names.add("Bear");
+ names.add("Gracie");
+ names.add("Tucker");
+ names.add("Coco");
+ names.add("Murphy");
+ names.add("Sasha");
+ names.add("Lucky");
+ names.add("Lily");
+ names.add("Oliver");
+ names.add("Angel");
+ names.add("Sam");
+ names.add("Princess");
+ names.add("Oscar");
+ names.add("Emma");
+ names.add("Teddy");
+ names.add("Annie");
+ names.add("Winston");
+ names.add("Rosie");
+ }
+
+ public static List getBreeds() {
+ return breeds;
+ }
+
+ public static List getNames() {
+ return names;
+ }
+
+ public static String getRandomBreed() {
+ return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1));
+ }
+
+ public static String getRandomName() {
+ return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1));
+ }
+
+ public static Date getRandomDoB() {
+ GregorianCalendar gc = new GregorianCalendar();
+
+ int year = ThreadLocalRandom.current().nextInt(
+ Calendar.getInstance().get(Calendar.YEAR) - 15,
+ Calendar.getInstance().get(Calendar.YEAR)
+ );
+
+ gc.set(Calendar.YEAR, year);
+
+ int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
+
+ gc.set(Calendar.DAY_OF_YEAR, dayOfYear);
+ return gc.getTime();
+ }
+}
diff --git a/samples/springboot4/pet-store/src/main/resources/logback.xml b/samples/springboot4/pet-store/src/main/resources/logback.xml
new file mode 100644
index 000000000..14a3a84fa
--- /dev/null
+++ b/samples/springboot4/pet-store/src/main/resources/logback.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/springboot4/pet-store/template.yml b/samples/springboot4/pet-store/template.yml
new file mode 100644
index 000000000..789057a50
--- /dev/null
+++ b/samples/springboot4/pet-store/template.yml
@@ -0,0 +1,35 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: Example Pet Store API written with SpringBoot4 with the aws-serverless-java-container library
+
+Globals:
+ Api:
+ # API Gateway regional endpoints
+ EndpointConfiguration: REGIONAL
+
+Resources:
+ PetStoreFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler::handleRequest
+ Runtime: java25
+ CodeUri: .
+ MemorySize: 1512
+ Policies: AWSLambdaBasicExecutionRole
+ Timeout: 60
+ Environment:
+ Variables:
+ MAIN_CLASS: com.amazonaws.serverless.sample.springboot4.Application
+ Events:
+ HttpApiEvent:
+ Type: HttpApi
+ Properties:
+ TimeoutInMillis: 20000
+ PayloadFormatVersion: '1.0'
+
+Outputs:
+ SpringBootPetStoreApi:
+ Description: URL for application
+ Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets'
+ Export:
+ Name: SpringBootPetStoreApi