{"id":5212,"date":"2025-05-03T19:24:34","date_gmt":"2025-05-03T19:24:34","guid":{"rendered":"http:\/\/lockitsoft.com\/?p=5212"},"modified":"2025-05-03T19:24:34","modified_gmt":"2025-05-03T19:24:34","slug":"optimizing-aws-lambda-performance-a-deep-dive-into-graalvm-native-image-for-enhanced-java-applications","status":"publish","type":"post","link":"https:\/\/lockitsoft.com\/?p=5212","title":{"rendered":"Optimizing AWS Lambda Performance: A Deep Dive into GraalVM Native Image for Enhanced Java Applications"},"content":{"rendered":"<p>In the ongoing quest to maximize efficiency and minimize latency in serverless architectures, developers are continuously exploring advanced techniques. Following an examination of strategies such as Lambda SnapStart and various priming methods in previous installments, this article delves into another potent approach for accelerating Java-based AWS Lambda functions: GraalVM Native Image. This exploration builds upon prior insights, specifically focusing on how to integrate GraalVM&#8217;s Ahead-Of-Time (AOT) compilation into an existing serverless application to achieve significant performance gains, particularly in cold start scenarios.<\/p>\n<p>The journey to optimizing serverless Java functions has seen previous discussions revolving around techniques aimed at mitigating the inherent latency associated with initializing serverless environments. In prior analyses, the impact of Lambda SnapStart, a feature designed to pre-initialize function code and data, was evaluated. Alongside SnapStart, various &quot;priming&quot; techniques were explored to further reduce cold start times. These methods proved effective, notably in reducing latency for the latter 70 measurements when the tiered cache effect of snapshots was in play. Furthermore, substantial improvements were observed in minimizing the peak latency experienced during warm starts. This current investigation introduces GraalVM Native Image as a novel and powerful alternative for enhancing Lambda function performance.<\/p>\n<h3>Understanding GraalVM Native Image<\/h3>\n<p>GraalVM Native Image is a cutting-edge technology that enables Java applications to be compiled into standalone native executables. Unlike traditional Just-In-Time (JIT) compilation, where code is compiled during runtime, Native Image performs Ahead-Of-Time (AOT) compilation. This process analyzes the entire application code, including all dependencies, and generates a highly optimized, self-contained executable. The key advantage of this approach is the elimination of the Java Virtual Machine (JVM) startup overhead and the JIT compilation phase during runtime, leading to dramatically reduced startup times and lower memory footprints.<\/p>\n<p>For developers new to GraalVM and its native image capabilities, a foundational understanding is crucial. GraalVM&#8217;s architecture is designed for high performance and flexibility, supporting multiple programming languages. The native image feature specifically targets the creation of executables that can run without a JVM. Comprehensive resources are available, including the official GraalVM documentation, which provides in-depth introductions to GraalVM and its architecture. For those seeking a practical introduction tailored to serverless environments, previous articles have detailed the process of setting up GraalVM and its native image capabilities for AWS Lambda, including installation instructions. The specific version used in the examples provided is GraalVM 25.0.2, though users are encouraged to leverage the latest stable release for optimal features and security.<\/p>\n<h3>Adapting a Sample Application for GraalVM Native Image<\/h3>\n<p>To demonstrate the practical application of GraalVM Native Image in an AWS Lambda context, a previously developed sample application serving as a foundation for serverless applications on AWS, utilizing Java 25, API Gateway, and DynamoDB, has been adapted. This existing application, initially presented in part 1 of a series, serves as a robust starting point. The primary modification involves transforming the application to be compatible with GraalVM Native Image compilation and subsequently deploying it as a Custom Runtime on AWS Lambda.<\/p>\n<p>The adapted application, now available in a dedicated repository, retains the core business logic \u2013 the <code>Entity<\/code>, <code>ProductDao<\/code>, and Lambda handlers \u2013 unchanged. All modifications are strategically concentrated within the <code>pom.xml<\/code> (Maven Project Object Model) file, the AWS SAM (Serverless Application Model) template, and through the provision of specific GraalVM configuration files. This focused approach ensures that the inherent functionality of the application remains intact while enabling its transformation into a highly optimized native executable.<\/p>\n<p>The adaptation process necessitates careful consideration of how Java reflection mechanisms operate within the AOT compilation environment. Since the GraalVM AOT compiler analyzes code statically, it must be explicitly informed about all classes that might be instantiated via reflection at runtime. This is achieved through a reflection configuration file, typically named <code>reflect-config.json<\/code>. In this project, this file lists the necessary classes that the compiler needs to be aware of. Without this explicit declaration, the application would likely fail during runtime when attempting to access these classes through reflection.<\/p>\n<p>Furthermore, the management of logging frameworks, such as SLF4J, during the build process requires specific attention. Class initialization in a native image context can behave differently than in a standard JVM environment. To prevent initialization errors with loggers, GraalVM Native Image build arguments are specified in a <code>native-image.properties<\/code> file. This file, placed within the <code>META-INF\/native-image\/$MavenGroupId\/$MavenArtifactId<\/code> directory structure, instructs the AOT compiler on how to handle class initialization for logging components like <code>org.slf4j.simple.SimpleLogger<\/code> and <code>org.slf4j.LoggerFactory<\/code>. The arguments <code>--initialize-at-build-time<\/code> and <code>--trace-class-initialization<\/code> are employed to ensure these classes are correctly processed during the build phase. If an alternative logging framework, such as Log4j or Logback, is used, the <code>native-image.properties<\/code> file and its placement would need to be adjusted accordingly.<\/p>\n<p>To streamline the generation of this metadata, GraalVM offers a Tracing Agent. This agent can dynamically observe the application&#8217;s runtime behavior and automatically generate the necessary configuration files for reflection, serialization, and JNI. While this article details the manual configuration, leveraging the tracing agent can significantly reduce the manual effort involved in identifying and declaring all required metadata, especially for larger and more complex applications.<\/p>\n<h3>Navigating Lambda Custom Runtimes<\/h3>\n<p>A critical aspect of deploying GraalVM Native Images on AWS Lambda is the absence of a managed runtime specifically for this technology. AWS Lambda provides a set of managed runtimes for various programming languages, but a native GraalVM runtime is not among them. Therefore, to deploy a native image, a <strong>custom runtime<\/strong> approach is necessary.<\/p>\n<p>A custom runtime in AWS Lambda involves packaging the application and its dependencies into a <code>.zip<\/code> file. This archive must contain a special executable file named <code>bootstrap<\/code>. The <code>bootstrap<\/code> file acts as the entry point for the Lambda function and is responsible for invoking the application code. In the context of GraalVM Native Image, the <code>bootstrap<\/code> file can either be the compiled native executable itself or a script that launches the native executable. The latter approach is employed here, offering greater flexibility. The <code>bootstrap<\/code> script is designed to execute the GraalVM Native Image, which has been compiled into a standalone binary.<\/p>\n<h3>The Build Process: Crafting the GraalVM Native Image<\/h3>\n<p>The automated building of the GraalVM Native Image is integrated into the Maven <code>package<\/code> phase through the <code>native-image-maven-plugin<\/code>. This plugin, provided by GraalVM, is configured within the <code>pom.xml<\/code> file. The relevant configuration specifies the main class to be used, which in this case is <code>com.formkiq.lambda.runtime.graalvm.LambdaRuntime<\/code> from the <code>lambda-runtime-graalvm<\/code> library. This library is a crucial dependency, acting as an adapter that simplifies the conversion of Java-based AWS Lambda functions to GraalVM Native Images by providing the necessary runtime environment and entry point.<\/p>\n<p>The build process involves several key configurations within the <code>native-image-maven-plugin<\/code>:<\/p>\n<ul>\n<li><strong><code>mainClass<\/code><\/strong>: <code>com.formkiq.lambda.runtime.graalvm.LambdaRuntime<\/code> &#8211; This defines the entry point for the native executable, leveraging the <code>lambda-runtime-graalvm<\/code> library to manage the Lambda runtime interaction.<\/li>\n<li><strong><code>imageName<\/code><\/strong>: <code>aws-lambda-java-25-with-dynamodb-as-graalvm-native-image<\/code> &#8211; This sets the name for the generated native executable.<\/li>\n<li><strong><code>buildArgs<\/code><\/strong>: This parameter allows for passing arguments directly to the <code>native-image<\/code> tool. Essential arguments include:\n<ul>\n<li><code>--no-fallback<\/code>: Instructs the native image builder not to generate a fallback JVM executable, ensuring a truly native binary.<\/li>\n<li><code>--enable-http<\/code>: Enables HTTP client functionality within the native image.<\/li>\n<li><code>-H:ReflectionConfigurationFiles=..\/src\/main\/reflect-config.json<\/code>: Specifies the path to the reflection configuration file, ensuring classes used via reflection are correctly included.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The <code>native-image-maven-plugin<\/code> orchestrates the AOT compilation process, transforming the Java bytecode into a native executable. For packaging this executable into a deployable artifact for AWS Lambda&#8217;s custom runtime, the <code>maven-assembly-plugin<\/code> is utilized. This plugin is configured to create a zip archive named <code>function.zip<\/code>, which is the standard expected format for Lambda deployment packages.<\/p>\n<p>The assembly descriptor, <code>src\/assembly\/native.xml<\/code>, plays a vital role in defining the contents of the <code>function.zip<\/code>. It specifies that the native executable generated by the <code>native-image<\/code> tool (named <code>aws-lambda-java-25-with-dynamodb-as-graalvm-native-image<\/code> in this configuration) and the <code>bootstrap<\/code> script should be included in the zip file. The <code>bootstrap<\/code> script, a simple shell script, is responsible for setting the execution environment and launching the native executable. The <code>fileMode<\/code> set to <code>0775<\/code> ensures that both the <code>bootstrap<\/code> script and the native executable have the necessary execute permissions on the Linux environment within AWS Lambda.<\/p>\n<p>The complete build process, from compiling the Java code to generating the deployable <code>function.zip<\/code> artifact, is initiated by running <code>mvn clean package<\/code>. This command triggers all configured Maven plugins, including the <code>native-image-maven-plugin<\/code> and the <code>maven-assembly-plugin<\/code>, to produce the final artifact ready for deployment.<\/p>\n<h3>Deployment to AWS Lambda<\/h3>\n<p>The deployment of the GraalVM Native Image as a Lambda Custom Runtime is managed through an AWS SAM template. In the <code>template.yaml<\/code> file, the Lambda function is configured with the <code>Runtime<\/code> set to <code>provided.al2023<\/code>. This specifies that AWS Lambda should use Amazon Linux 2023 as the execution environment, which is the latest generation of AWS-provided base operating systems for Lambda custom runtimes. The <code>CodeUri<\/code> property is set to <code>target\/function.zip<\/code>, pointing to the generated zip archive containing the native executable and the <code>bootstrap<\/code> script.<\/p>\n<p>This configuration effectively tells AWS Lambda to treat the deployed artifact as a custom runtime. When the Lambda function is invoked, Lambda will execute the <code>bootstrap<\/code> file within the provided runtime environment. The <code>bootstrap<\/code> script, as detailed earlier, will then launch the GraalVM Native Image, bypassing the need for a traditional JVM and significantly reducing startup latency.<\/p>\n<p>The deployment itself is executed using the AWS SAM CLI command <code>sam deploy -g<\/code>. This command packages the application and deploys it to the AWS cloud, creating or updating the necessary AWS resources, including the Lambda function, API Gateway endpoint, and any associated IAM roles.<\/p>\n<h3>Performance Benchmarking: GraalVM Native Image vs. SnapStart<\/h3>\n<p>To quantify the performance improvements offered by GraalVM Native Image, a series of measurements were conducted on the <code>GetProductById<\/code> Lambda function, mapped to the <code>GetProductByIdHandler<\/code>. This function is triggered via an API Gateway endpoint using a <code>curl<\/code> command. The experimental setup mirrors that of previous performance analyses to ensure a fair and consistent comparison.<\/p>\n<p>The measurements were performed using the <code>provided.al2023.v124<\/code> runtime, and the deployed artifact size for this GraalVM Native Image application was approximately 25.186 KB. This relatively small size is a testament to the efficiency of native executables, which do not include the overhead of a full JVM.<\/p>\n<p>The performance data is presented in the following table, detailing both cold (<code>c<\/code>) and warm (<code>w<\/code>) start times across various percentiles (p50, p75, p90, p99, p99.9) and maximum values:<\/p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Approach<\/th>\n<th style=\"text-align: left;\">c p50<\/th>\n<th style=\"text-align: left;\">c p75<\/th>\n<th style=\"text-align: left;\">c p90<\/th>\n<th style=\"text-align: left;\">c p99<\/th>\n<th style=\"text-align: left;\">c p99.9<\/th>\n<th style=\"text-align: left;\">c max<\/th>\n<th style=\"text-align: left;\">w p50<\/th>\n<th style=\"text-align: left;\">w p75<\/th>\n<th style=\"text-align: left;\">w p90<\/th>\n<th style=\"text-align: left;\">w p99<\/th>\n<th style=\"text-align: left;\">w p99.9<\/th>\n<th style=\"text-align: left;\">w max<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\">Lambda Custom Runtime with GraalVM Native Image<\/td>\n<td style=\"text-align: left;\">559ms<\/td>\n<td style=\"text-align: left;\">568ms<\/td>\n<td style=\"text-align: left;\">593ms<\/td>\n<td style=\"text-align: left;\">692ms<\/td>\n<td style=\"text-align: left;\">739ms<\/td>\n<td style=\"text-align: left;\">739ms<\/td>\n<td style=\"text-align: left;\">3.84ms<\/td>\n<td style=\"text-align: left;\">4.23ms<\/td>\n<td style=\"text-align: left;\">4.88ms<\/td>\n<td style=\"text-align: left;\">10.00ms<\/td>\n<td style=\"text-align: left;\">55.92ms<\/td>\n<td style=\"text-align: left;\">124ms<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>This data reveals a significant reduction in cold start times compared to previous approaches, including Lambda SnapStart with priming techniques. While SnapStart aims to mitigate cold starts by keeping functions initialized, GraalVM Native Image offers an even more aggressive optimization by eliminating the JVM startup overhead entirely. The cold start times for the GraalVM Native Image approach are substantially lower across all measured percentiles, with the maximum cold start time being remarkably constrained.<\/p>\n<p>Warm start times also show impressive improvements. Although warm starts are inherently faster due to cached execution environments, GraalVM Native Image further optimizes this by reducing the overhead associated with the Lambda Runtime GraalVM library itself. The difference in warm start latency, while smaller in absolute terms compared to cold starts, still contributes to a more responsive application.<\/p>\n<h3>Conclusion: GraalVM Native Image &#8211; A Powerful but Complex Optimization<\/h3>\n<p>The exploration of GraalVM Native Image for AWS Lambda functions demonstrates its potential to deliver substantial performance enhancements, particularly in reducing cold start latency. By compiling Java applications into standalone native executables, developers can bypass the traditional JVM startup overhead, leading to faster function initialization and a more responsive serverless experience.<\/p>\n<p>However, this performance comes at a cost. The process of creating a GraalVM Native Image is not trivial. It requires a significant scaling of the CI\/CD pipeline to accommodate the build process, which can be resource-intensive, demanding substantial memory and considerable build times. Furthermore, accurately configuring the native image, especially for applications that rely heavily on reflection or dynamic class loading, introduces additional complexity. While tools like the GraalVM Tracing Agent can automate much of this metadata generation, it still represents an added layer of complexity compared to fully managed services like Lambda SnapStart.<\/p>\n<p>The trade-off between the performance gains offered by GraalVM Native Image and the increased development and operational complexity is a key consideration for teams. Lambda SnapStart provides a more managed and potentially simpler path to performance optimization, whereas GraalVM Native Image offers a more granular level of control and potentially superior performance for applications where every millisecond counts. A direct comparison of these approaches will be the subject of future articles, providing a holistic view for developers making critical architectural decisions.<\/p>\n<p>In parallel, research continues into other serverless architectures, including those utilizing relational databases like Amazon Aurora DSQL with frameworks like Hibernate ORM, to explore performance characteristics in diverse application stacks. Developers interested in further content and updates are encouraged to follow the author&#8217;s work on GitHub and explore their personal website for additional technical insights and speaking engagements.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the ongoing quest to maximize efficiency and minimize latency in serverless architectures, developers are continuously exploring advanced techniques. Following an examination of strategies such as Lambda SnapStart and various priming methods in previous installments, this article delves into another potent approach for accelerating Java-based AWS Lambda functions: GraalVM Native Image. This exploration builds upon &hellip;<\/p>\n","protected":false},"author":8,"featured_media":5211,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[136],"tags":[376,138,371,372,340,373,374,375,370,10,369,282,139,137],"class_list":["post-5212","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-development","tag-applications","tag-coding","tag-deep","tag-dive","tag-enhanced","tag-graalvm","tag-image","tag-java","tag-lambda","tag-native","tag-optimizing","tag-performance","tag-programming","tag-software"],"_links":{"self":[{"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/posts\/5212","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5212"}],"version-history":[{"count":0,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/posts\/5212\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=\/wp\/v2\/media\/5211"}],"wp:attachment":[{"href":"https:\/\/lockitsoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5212"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5212"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lockitsoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5212"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}