Contents

There are two practical ways to run Spring Boot code on AWS Lambda. Choosing the right one depends on whether you are migrating an existing HTTP API or building a purpose-built serverless function.

ApproachHow it worksBest for
Lambda Web AdapterA thin Lambda extension that proxies API Gateway events to an HTTP server running on localhost:8080 inside the same execution environmentMigrating existing Spring Boot REST APIs; familiar MVC/WebFlux code with no changes
Function HandlerImplement RequestHandler<I,O> or Spring Cloud Function's Function<I,O>; Spring context starts on first invocationLightweight event processors — SQS, SNS, EventBridge triggers with simple logic
Both approaches benefit from SnapStart. Lambda Web Adapter is the lower-friction path for teams already writing Spring Boot services and wanting serverless deployment without rewriting their code.

The AWS Lambda Web Adapter (open source, maintained by AWS) wraps any HTTP server process. It starts your Spring Boot app as a subprocess listening on port 8080, then translates Lambda invocation events (API Gateway v1/v2, ALB) into HTTP requests and sends responses back. From Spring's perspective it is a normal HTTP server startup.

<!-- pom.xml — standard Spring Boot Web dependency; no Lambda SDK needed --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> // Standard Spring Boot application — zero Lambda-specific code @SpringBootApplication public class ProductApi { public static void main(String[] args) { SpringApplication.run(ProductApi.class, args); } } @RestController @RequestMapping("/products") public class ProductController { @GetMapping("/{id}") public ResponseEntity<Product> getProduct(@PathVariable Long id) { return ResponseEntity.ok(productService.findById(id)); } @PostMapping public ResponseEntity<Product> create(@RequestBody @Valid Product product) { return ResponseEntity.status(HttpStatus.CREATED) .body(productService.save(product)); } } # Dockerfile — include Lambda Web Adapter extension layer FROM public.ecr.aws/lambda/java:21 # Copy the Lambda Web Adapter binary from the official layer COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 \ /lambda-adapter /opt/extensions/lambda-adapter # Tell the adapter which port Spring Boot listens on (default 8080) ENV PORT=8080 ENV AWS_LAMBDA_EXEC_WRAPPER=/opt/bootstrap COPY target/product-api-1.0.jar ${LAMBDA_TASK_ROOT}/app.jar CMD ["software.amazon.awssdk.lambda.LambdaBootstrap"]

For event-driven workloads (SQS, SNS, S3 triggers) where the overhead of a full MVC stack is unnecessary, implement a slim Spring-managed RequestHandler using Spring Cloud Function. The framework wires the Spring context once and routes invocations to a Function bean by name.

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-adapter-aws</artifactId> </dependency> @SpringBootApplication public class OrderProcessor { public static void main(String[] args) { SpringApplication.run(OrderProcessor.class, args); } // Spring Cloud Function — the Lambda handler routes here by function name @Bean public Function<Order, ProcessingResult> processOrder(OrderService svc) { return order -> { svc.validate(order); svc.save(order); svc.publishEvent(order); return new ProcessingResult("OK", order.getId()); }; } } # application.yml spring: cloud: function: definition: processOrder # tells the AWS adapter which Function bean to invoke # Lambda configuration Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest Runtime: java21 Memory: 512 MB Timeout: 30 s

SnapStart works by taking a Firecracker MicroVM snapshot of the Lambda execution environment after the Init phase (class loading + Spring context startup) completes. Subsequent invocations restore from the snapshot instead of re-initialising, reducing cold start latency from 5–15 seconds (typical Spring Boot on JVM) to under 1 second.

# SAM template.yaml — enable SnapStart on a function Resources: ProductApi: Type: AWS::Serverless::Function Properties: Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest Runtime: java21 MemorySize: 1024 SnapStart: ApplyOn: PublishedVersions # snapshot taken on publish, not $LATEST AutoPublishAlias: live # always route alias to latest published version Environment: Variables: SPRING_PROFILES_ACTIVE: prod Uniqueness after restore: After snapshot restore, random seeds, timestamps, and network connections may be stale. Implement CRaC-compatible hooks (@PreSnapshot / @AfterRestore via SnapshotHook) to close and reopen connections. Spring Boot 3.2+ integrates with CRaC automatically for JDBC pools and caches.

AWS SAM (Serverless Application Model) provides a CLI that can invoke Lambda functions locally using Docker, emulate API Gateway, and deploy the full stack via CloudFormation. It is the standard tool for the Lambda development loop.

# Install SAM CLI (macOS) brew tap aws/tap brew install aws-sam-cli # Build the project (compiles, packages, copies to .aws-sam/build/) sam build # Start local API Gateway (emulates HTTP triggers on localhost:3000) sam local start-api # Invoke a single function with a test event sam local invoke ProductApi --event events/get-product.json # Tail logs from a deployed function sam logs -n ProductApi --stack-name my-stack --tail # Deploy with guided prompts (first time) sam deploy --guided # Deploy using existing samconfig.toml (subsequent deployments) sam deploy // events/get-product.json — sample API Gateway v2 event { "version": "2.0", "routeKey": "GET /products/{id}", "rawPath": "/products/42", "pathParameters": { "id": "42" }, "requestContext": { "http": { "method": "GET", "path": "/products/42" }, "stage": "$default" }, "isBase64Encoded": false }

Even with SnapStart, understanding cold start causes helps you build faster functions. Cold start time = JVM launch + class loading + Spring context init + first-request logic. Each phase can be optimised independently.

TechniqueImpactNotes
SnapStart⬇ 80–95% cold startEnable via ApplyOn: PublishedVersions; requires Java 11+ managed runtime
Increase memory⬇ 20–50% init timeMore memory = more CPU. 512–1024 MB is the sweet spot for Spring Boot
spring.main.lazy-initialization=true⬇ 30–60% context initBeans initialise on first use; trades cold start for slightly slower first request
Reduce classpath / dependencies⬇ 10–30% class loadingRemove unused Spring Boot starters; use spring-boot-starter-web not spring-boot-starter
GraalVM Native Image⬇ 95%+ cold start, ⬇ memorySub-100 ms starts; requires native build pipeline; no SnapStart needed
Provisioned ConcurrencyEliminates cold starts entirelyPre-warms N instances; costs money even at zero traffic
# application-lambda.yml — Lambda-optimised Spring Boot settings spring: main: lazy-initialization: true banner-mode: off jpa: open-in-view: false # disable OSIV — saves a database connection per request datasource: hikari: minimum-idle: 1 # cold Lambda doesn't need pool depth maximum-pool-size: 5 connection-timeout: 3000

Lambda functions read configuration from environment variables. Spring Boot's externalized configuration picks these up automatically — no code changes needed. For secrets, always use AWS Systems Manager Parameter Store or Secrets Manager rather than plain environment variable values.

// application.yml references environment variables // Spring Boot reads them automatically via @Value or @ConfigurationProperties spring: datasource: url: ${DB_URL} # set in Lambda environment username: ${DB_USERNAME} password: ${DB_PASSWORD} # or: resolved from Secrets Manager via AWS extension cloud: aws: region: static: ${AWS_DEFAULT_REGION:us-east-1} # Use AWS AppConfig or Parameter Store extension for dynamic config # Extension reads SSM params and exposes them as localhost HTTP at startup # SAM template — inject config from SSM Parameter Store Environment: Variables: DB_URL: !Sub "{{resolve:ssm:/myapp/prod/db-url}}" # Or use the AWS Parameters and Secrets Lambda Extension (layer) # for runtime fetching without SDK calls in application code

Lambda automatically sends stdout/stderr to CloudWatch Logs. For structured logs and distributed tracing, add the AWS X-Ray SDK and configure Micrometer for CloudWatch metrics. Spring Boot 3's Micrometer integration makes this straightforward.

<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-spring</artifactId> <version>2.15.3</version> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-cloudwatch2</artifactId> </dependency> // Structured JSON logging — Lambda CloudWatch Logs Insights can query fields @Configuration public class LoggingConfig { @PostConstruct void configure() { // Logback JSON encoder (net.logstash.logback:logstash-logback-encoder) // Outputs: {"timestamp":"...","level":"INFO","message":"...","traceId":"..."} } } // X-Ray active tracing annotation @XRayEnabled @Service public class ProductService { public Product findById(Long id) { // X-Ray creates a subsegment automatically for this method return repository.findById(id).orElseThrow(); } } # Enable X-Ray active tracing in SAM template Properties: Tracing: Active Policies: - AWSXRayDaemonWriteAccess