Java Serverless on AWS Lambda · Java on Lambda: Runtime Fundamentals
Packaging: Uber-JARs, Dependencies and Size
Why a Java function's artifact grows past Lambda's size limit, and how to package lean JARs.
Scenario
Example app: a Java function is built with the Maven Shade plugin into one uber-JAR. It depends on the full AWS SDK v2 bundle "to be safe", plus a web framework only used in tests and several logging implementations.
What you see in production
After adding the SDK bundle, deployment fails with Unzipped size must be smaller than 262144000 bytes. Before that change the artifact was already large, deployments were slow, cold starts were longer than for similar functions, and logs showed warnings about multiple SLF4J bindings with log output going nowhere.
The naive code
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bundle</artifactId> <!-- every AWS service client -->
<version>2.29.0</version>
</dependency>
Reproduce it
import java.util.*;
public class Main {
record Dep(String name, int mb, boolean needed) {}
public static void main(String[] args) {
List<Dep> deps = List.of(
new Dep("application classes", 1, true),
new Dep("aws-sdk bundle (all services)", 520, false),
new Dep("dynamodb + sqs clients + core", 9, true),
new Dep("test web framework (wrong scope)", 14, false),
new Dep("logback + log4j2 + slf4j-simple", 4, false),
new Dep("log4j2 + aws-lambda-java-log4j2", 3, true));
int naive = deps.stream().filter(d -> !d.name().startsWith("dynamodb")).mapToInt(Dep::mb).sum();
int lean = deps.stream().filter(Dep::needed).mapToInt(Dep::mb).sum();
System.out.println("naive uber-jar (unzipped): " + naive + " MB, Lambda limit 250 MB -> " + (naive > 250 ? "deploy fails" : "ok"));
System.out.println("lean artifact (unzipped): " + lean + " MB");
System.out.println("conflicting logging bindings removed: " + deps.stream().filter(d -> d.name().startsWith("logback")).count());
}
}
Output
naive uber-jar (unzipped): 542 MB, Lambda limit 250 MB -> deploy fails lean artifact (unzipped): 13 MB conflicting logging bindings removed: 1
Run this on your computer — see “Get Started” for setup.
Root cause
Build tools include every dependency in the runtime classpath unless told otherwise. The AWS SDK "bundle" contains every service client; test dependencies with the wrong scope leak into production; and multiple logging backends conflict. A larger artifact means more to download and more classes on the classpath, slowing deployments and start-up.
The fix
- Depend only on the specific SDK modules you use (
dynamodb,sqs) through the SDK BOM, and exclude the unused HTTP clients (for example excludeapache-clientandnetty-nio-clientwhen you use the CRT or URL connection client). - Fix dependency scopes (
test,provided); check withmvn dependency:treeorgradle dependencies. - Use one logging implementation (for example Log4j2 with
aws-lambda-java-log4j2). - Package as a ZIP with a
lib/directory (the format AWS recommends for Java) or a shaded JAR; use container images when you need more space or native libraries. - Keep the artifact reproducible and scanned (OWASP Dependency-Check, Amazon Inspector).
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId><artifactId>bom</artifactId>
<version>2.29.0</version><type>pom</type><scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId><artifactId>dynamodb</artifactId>
<exclusions>
<exclusion><groupId>software.amazon.awssdk</groupId><artifactId>netty-nio-client</artifactId></exclusion>
<exclusion><groupId>software.amazon.awssdk</groupId><artifactId>apache-client</artifactId></exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId><artifactId>aws-crt-client</artifactId>
</dependency>
</dependencies>
Trade-offs
- Shading can break libraries that rely on service files or resources; use the appropriate transformers.
- Layers can share large dependencies across functions but split versioning; prefer self-contained artifacts.
- Container images are larger but give full control and up to 10 GB.
Detect it in production
- Artifact size budget in CI; fail the build when exceeded.
Init Durationtrends after dependency changes.- SLF4J multiple-binding warnings in start-up logs.
Key points
- Depend on specific SDK modules via the BOM; exclude unused HTTP clients.
- Fix scopes; one logging backend.
- ZIP with lib/ or a lean shaded JAR; images for large needs.
Further reading: Deploy Java Lambda functions with .zip or JAR file archives · AWS SDK for Java: setting up a Maven project
Practice questions
Why should a Java function depend on specific AWS SDK modules rather than the SDK bundle?
Answer
The bundle contains every service client, inflating the artifact by hundreds of megabytes uncompressed, slowing deployments and start-up. Specific modules managed through the SDK BOM include only what the function uses.
How do you find dependencies that should not be in the production artifact?
Answer
Inspect the dependency tree (mvn dependency:tree or gradle dependencies), check scopes so test-only libraries use test, and remove duplicate or conflicting libraries such as multiple logging backends.
When would you use a container image instead of a ZIP for a Java function?
Answer
When the artifact exceeds ZIP limits, needs native libraries or a custom runtime, or the team wants the same image tooling as other services; images support up to 10 GB.
Open in the app to mark lessons complete, take notes and track your progress.