<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vadym Kazulkin</title>
    <description>The latest articles on DEV Community by Vadym Kazulkin (@vkazulkin).</description>
    <link>https://dev.to/vkazulkin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501061%2Ffa23b5ee-9c5f-48bd-b3bb-341a26a773c6.JPG</url>
      <title>DEV Community: Vadym Kazulkin</title>
      <link>https://dev.to/vkazulkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vkazulkin"/>
    <language>en</language>
    <item>
      <title>AWS Lambda Managed Instances with Java 25 and AWS SAM – Part 7 Implement scheduled scaling</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 01 Jun 2026 15:36:09 +0000</pubDate>
      <link>https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-7-implement-scheduled-scaling-4df9</link>
      <guid>https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-7-implement-scheduled-scaling-4df9</guid>
      <description>&lt;h2&gt;
  
  
  Implement scheduled scaling
&lt;/h2&gt;

&lt;p&gt;Recently, &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/05/amazon-eventbridge-sdk-integrations/" rel="noopener noreferrer"&gt;Amazon EventBridge Scheduler added 619 new SDK API actions&lt;/a&gt;. &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-scaling.html#lambda-managed-instances-scheduled-scaling" rel="noopener noreferrer"&gt;One of these actions&lt;/a&gt; adjusts the Lambda function's minimum and maximum execution environments for Lambda Managed Instances (LMI) on a recurring or one-time schedule. This is useful for predictable traffic patterns, such as scaling up before peak hours and scaling down during off-peak hours. We can use CloudFormation or the latest versions of AWS CDK or AWS CLI to perform this action. In our example, we'll use AWS CLI to adjust the Lambda Function Scaling Configuration, for which we'll use the &lt;em&gt;PutFunctionScalingConfig API&lt;/em&gt; as a universal target. We'll use the Lambda function with the name &lt;em&gt;GetProductByIdJava25WithLMI&lt;/em&gt;, which we introduced in &lt;a href="https://dev.to/aws-heroes/aws-lambda-managed-instances-with-java-25-and-aws-sam-part-1-introduction-and-sample-application-1eb7"&gt;part 1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all, let's create an SQS dead-letter queue, which we'll use for our action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sqs create-queue &lt;span class="nt"&gt;--queue-name&lt;/span&gt; scheduler-dlq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create an EventBridge Scheduler IAM execution role with the name &lt;em&gt;scale-lambda-managed-instances-eventbridge-scheduler-role&lt;/em&gt;. This role grants permission to call the &lt;em&gt;lambda:PutFunctionScalingConfig&lt;/em&gt; and send the message to the dead-letter queue on our target function.&lt;/p&gt;

&lt;p&gt;This is how the trusted policy looks for the IAM role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scheduler.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how the IAM policy looks (use the ARNs of the Lambda function and SQS dead-letter queue here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"lambda:PutFunctionScalingConfig"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI:$LATEST.PUBLISHED"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"sqs:SendMessage"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sqs:{aws_region}:{aws_account_id}:scheduler-dlq"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace the values of &lt;em&gt;{aws_region}&lt;/em&gt; and &lt;em&gt;{aws_account_id}&lt;/em&gt; with your own values, and adjust the Lambda function and SQS Queue names if needed. &lt;/p&gt;

&lt;p&gt;Now, let's create the scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws scheduler create-schedule &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"ScaleLambdaManagedInstances"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--schedule-expression&lt;/span&gt; &lt;span class="s2"&gt;"at(2026-05-18T08:10:00)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--flexible-time-window&lt;/span&gt; &lt;span class="s1"&gt;'{"Mode": "OFF"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s1"&gt;'{
     {"DeadLetterConfig": {"Arn": "arn:aws:sqs:{aws_region}:{aws_account_id}:scheduler-dlq"},
    "Arn": "arn:aws:scheduler:::aws-sdk:lambda:PutFunctionScalingConfig",
    "RoleArn": "arn:aws:iam::{aws_account_id}:role/scale-lambda-managed-instances-eventbridge-scheduler-role",
    "Input": "{\"FunctionName\": \"GetProductByIdJava25WithLMI\", \"Qualifier\": \"$LATEST.PUBLISHED\", \"FunctionScalingConfig\": {\"MinExecutionEnvironments\": 5, \"MaxExecutionEnvironments\": 10}}"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain what happens here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we create the schedule with the name &lt;em&gt;ScaleLambdaManagedInstances&lt;/em&gt; using a one-time schedule (executes at 08:10 on May 18, 2026). You can use other &lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html" rel="noopener noreferrer"&gt;Schedule types in EventBridge Scheduler&lt;/a&gt;, like cron-based expressions.&lt;/li&gt;
&lt;li&gt;Then, we target the PutFunctionScalingConfig Scheduler API as a universal target. &lt;/li&gt;
&lt;li&gt;Next, we specify the SQS dead-letter queue ARN created above&lt;/li&gt;
&lt;li&gt;Then, we specify the IAM execution Role ARN created above&lt;/li&gt;
&lt;li&gt;Next, we specify the new MinExecutionEnvironments and MaxExecutionEnvironments values in the Input payload&lt;/li&gt;
&lt;li&gt;Finally, as the scheduler input, we specify the name of the Lambda function and its qualifier (usually &lt;em&gt;$LATEST.PUBLISHED&lt;/em&gt;), for which we'd like to change the Function Scaling Configuration. In our case, we set &lt;em&gt;MinExecutionEnvironments&lt;/em&gt; to 5 and &lt;em&gt;MaxExecutionEnvironments&lt;/em&gt; to 10.&lt;/li&gt;
&lt;li&gt;We can also optionally set the retry policy and encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the schedule, we'll see something similar in the &lt;a href="https://us-east-1.console.aws.amazon.com/scheduler/home?region=us-east-1#schedules" rel="noopener noreferrer"&gt;Amazon EventBridge Scheduler Service&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6f4txd2cnukcowm5uy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6f4txd2cnukcowm5uy1.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ujj0wnaw07l7b9m0p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ujj0wnaw07l7b9m0p8.png" alt=" " width="772" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m3ca0w0gx6g5838yx2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m3ca0w0gx6g5838yx2m.png" alt=" " width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the scheduler has run, we can verify that the Lambda function Scaling Configuration has changed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akscybp4lcegyf73a46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akscybp4lcegyf73a46.png" alt=" " width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also very important to configure the dead-letter queue. First, I didn't do it, and configured the Lambda function Resource ARN in the IAM policy like &lt;em&gt;arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI&lt;/em&gt;. I observed that the scheduler hasn't been invoked and saw the errors in Amazon CloudWatch.&lt;br&gt;
By configuring the dead-letter queue, I saw the exact error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR_CODE: AccessDeniedException
ERROR_MESSAGE :

User: arn:aws:sts::{aws_account_id}:assumed-role/scale-lambda-managed-instances-eventbridge-scheduler-role/f375e2c757da339a8d593587ce800265 
is not authorized to perform: lambda:PutFunctionScalingConfig on resource: 
arn:aws:lambda:{aws_region}:{aws_account_id}:function:GetProductByIdJava25WithLMI:$LATEST.PUBLISHED 
because no identity-based policy allows the lambda:PutFunctionScalingConfig action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, it was clear to me that I needed to append &lt;em&gt;$LATEST.PUBLISHED&lt;/em&gt; to the ARN of the Lambda function. Please also use &lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/troubleshooting.html" rel="noopener noreferrer"&gt;Troubleshooting Amazon EventBridge Scheduler&lt;/a&gt;, in case you experience some issues.&lt;/p&gt;

&lt;p&gt;Here, we scaled up the capacity at the given time, the same way we can scale it down. Things to pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For workloads with predictable peaks, create multiple schedules to match your traffic pattern: one to scale up your function before peak hours, and another to scale down after peak hours. Each schedule follows the same pattern with updated MinExecutionEnvironments and MaxExecutionEnvironments values.&lt;/li&gt;
&lt;li&gt;Scheduled scaling adjusts the provisioned floor and ceiling of execution environments, but actual scaling between min and max still responds to CPU utilization and concurrency saturation.&lt;/li&gt;
&lt;li&gt;If your traffic more than doubles within 5 minutes of a scheduled scale-up, you might still experience throttling as capacity is provisioned.&lt;/li&gt;
&lt;li&gt;When scaling to zero to deactivate a function, remember that reactivation requires an explicit PutFunctionScalingConfig call with non-zero values.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>scheduledscaling</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 5 Deploy MCP client for Conference application on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Tue, 26 May 2026 14:40:49 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In this article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime. Later, in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt;, we looked at how to provide the MCP Tools for the Conference application via AgentCore Gateway in a centralized way.&lt;/p&gt;

&lt;p&gt;As we saw in previous articles, the local MCP client for the Conference application, to talk to AgentCore Runtime or Gateway, became quite big. If we have many customers using such a client, changing and operating it can become quite challenging. That's why, in this article, we look at how to deploy and run our MCP client on AgentCore Runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement the MCP client for the Conference application to be deployable on AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;We'll reuse the MCP client based on Spring AI that we implemented in parts 3 and 4. But as we need to make some small changes to deploy it on AgentCore Runtime, I created a new &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime" rel="noopener noreferrer"&gt;spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime&lt;/a&gt;. It consists of the agent and Infrastructure as Code subfolders.&lt;/p&gt;

&lt;p&gt;Let's first look at the changes that we need to make to the client.  AgentCore Runtime also supports the HTTP protocol contract, which we'll use to deploy our MCP client and talk to it. This contract puts some requirements on the client:&lt;/p&gt;

&lt;p&gt;Container requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host : 0.0.0.0&lt;/li&gt;
&lt;li&gt;Port : 8080 - Standard port for HTTP-based agent communication &lt;/li&gt;
&lt;li&gt;Platform : ARM64 Docker container - Required for compatibility with the AgentCore Runtime environment. I usually borrow t4g small EC2 instance on AWS to build it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path requirements: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/invocations endpoint: POST endpoint for agent interactions&lt;/li&gt;
&lt;li&gt;/ping endpoint: GET endpoint for health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read more about this topic in the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-http-protocol-contract.html" rel="noopener noreferrer"&gt;HTTP protocol contract&lt;/a&gt; article.&lt;/p&gt;

&lt;p&gt;The only changes we need to make to our &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;REST Controller &lt;/a&gt; are to implement these path requirements. If we use asynchronous communication, the entry point looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/invocations"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"*/*"&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invocations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCallbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;concatWithStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For synchronous communication, the entry point looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/invocations"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"*/*"&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;invocations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCallbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;concatWithStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For adding the path to &lt;em&gt;/ping&lt;/em&gt;, we have different options. We can either add such a simple method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ping"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"{\"status\": \"healthy\"}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use &lt;a href="https://spring.io/guides/gs/actuator-service" rel="noopener noreferrer"&gt;Spring Boot Actuator service&lt;/a&gt; and add some properties to the application.properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;management.endpoints.web.exposure.include&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;
&lt;span class="py"&gt;management.endpoints.web.base-path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;
&lt;span class="py"&gt;management.endpoints.web.path-mapping.health&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we need to deploy our MCP client as an ARM64 Docker container, I also added a simple &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/Dockerfile" rel="noopener noreferrer"&gt;Docker file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; amazoncorretto:25&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime-0.0.1-SNAPSHOT.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java","-jar","/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's build the Docker file and upload it to the &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build the application&lt;/span&gt;
mvn clean package 

&lt;span class="c"&gt;# build the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 

&lt;span class="c"&gt;# Login to ECR&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com  

&lt;span class="c"&gt;# Create ECR repository (if it doesn't exist)&lt;/span&gt;
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; spring-ai-conference-app-agent-bedrock-agentcore-runtime &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;  

&lt;span class="c"&gt;# Tag the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1

&lt;span class="c"&gt;# Push the Docker Image to the ECR repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace AWS {account_id} and {region} with our own values. Also, your version may not be &lt;em&gt;v1&lt;/em&gt; but a different one.&lt;/p&gt;

&lt;p&gt;We can also build the Docker image by using Buildpack support built into Spring instead of a Dockerfile. Just use the Maven task &lt;a href="https://docs.spring.io/spring-boot/maven-plugin/build-image.html" rel="noopener noreferrer"&gt;spring-boot:build-image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We don't need to make any other changes on the MCP client itself. &lt;/p&gt;

&lt;p&gt;Let's now cover the IaC part with CDK for Java, which I implemented in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; stack. We've already covered many steps in creating the CDK App and Stack, and even the AgentCore Runtime with the MCP protocol, in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;. For a more detailed explanation, I refer to this article.&lt;/p&gt;

&lt;p&gt;First, let's take a look at the creation of the AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-125"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;protocolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProtocolType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgenCore Runtime with MCP protocol for running conference app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we set some common properties, such as the runtime name, description, and protocol (in our case, HTTP).&lt;/p&gt;

&lt;p&gt;Now let's look at the relevant code parts to assign this code artifact to the AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ecrImageURIForConferenceSearchAndApplicationAgent"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;    
    &lt;span class="nc"&gt;AgentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromImageUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="o"&gt;....&lt;/span&gt;

&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-125"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we get the value of the variable &lt;em&gt;ecrImageURI&lt;/em&gt;, which points to the imageURI in the ECR we pushed previously.  This is typically done in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="err"&gt;ecrImageUR&lt;/span&gt;&lt;span class="s2"&gt;": "&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;.dkr.ecr.us-east&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="err"&gt;.amazonaws.com/spring-ai-conference-app-agent-bedrock-agentcore-runtime:v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s2"&gt;"
 }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please adjust the value so that it matches your imageURI. We use the placeholder {AWS_ACCOUNT_ID} there. The reason for it is that I don't want to expose the AWS account ID publicly. That's why I wrote the following utility method &lt;em&gt;getContextVariableValueWithReplacedAccountId&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/ConventionalDefaults.java" rel="noopener noreferrer"&gt;ConventionalDefaults&lt;/a&gt; class to replace the placeholder with the real value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"awsAccountId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;awsAccountId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"please provide your aws account id as as content to the call, for example: cdk deploy -c awsAccountId=1234567890101"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{AWS_ACCOUNT_ID}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we create &lt;em&gt;AgentRuntimeArtifact&lt;/em&gt; from the image URI and set it as AgentCore Runtime &lt;em&gt;agentRuntimeArtifact&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;Now let's cover the next part - defining the IAM execution role. It's very difficult to automate this part as it takes plenty of time. If I find it, I'll provide the IaC part in the future :). I refer you to the article &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html" rel="noopener noreferrer"&gt;IAM Permissions for AgentCore Runtime&lt;/a&gt; for more information. You can also read my article &lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-2-deploy-the-agent-with-the-agentcore-runtime-starter-3706"&gt;Amazon Bedrock AgentCore Runtime - Part 2 Using Bedrock AgentCore Runtime Starter Toolkit with Strands Agents SDK&lt;/a&gt;, where I explained this part. In that article, we developed the agent in Python with the Strands Agents framework and deployed it on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;Once we have defined the IAM role, we need to configure it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{AWS_ACCOUNT_ID}:role/service-role/spring-ai-conference-search-application-agentcore-runtime-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the placeholder for the AWS account ID as explained above.  Here is the relevant code to grab the value of the &lt;em&gt;roleArnForTheAgentCoreRuntime&lt;/em&gt; variable and set it to the execution role of the Runtime from the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRoleArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"roleArnForTheAgentCoreRuntimeRole"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executionRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeAuthorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingIAM&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we also use an &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html" rel="noopener noreferrer"&gt;IAM authorizer&lt;/a&gt; for the inbound AgentCore Runtime authentication. This is the default authentication and authorization mechanism that works automatically without additional configuration. You can also use JSON Web Tokens (JWT) as we showed in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we are ready to deploy our MCP client on the AgentCore Runtime.  The command to do it is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk deploy -c awsAccountId={YOUR_AWS_ACCOUINT_ID}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is how the AgentCore Runtime looks in the console after its creation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoi9zh0ojkkdkhacs2v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoi9zh0ojkkdkhacs2v3.png" alt=" " width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jf34i7p9o95skgqo67r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jf34i7p9o95skgqo67r.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll need the Runtime ARN, which we see in the output of this command.  Or we can grab it in the service console.  &lt;/p&gt;

&lt;p&gt;Now we still need to write a client that communicates with our MCP client on the Runtime. I provided such an &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-bedrock-agentcore-runtime/agent/src/main/java/dev/vkazulkin/agent/sdk/InvokeRuntimeAgent.java" rel="noopener noreferrer"&gt;InvokeRuntimeAgent&lt;/a&gt; client written in Java, but you can use any programming language for which AWS provides a (bedrockagentcore) SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;AGENT_RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:runtime/spring_ai_conference_search_application_runtime-143wvBghklZ"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

 &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
   &lt;span class="s"&gt;"{\"prompt\":\"Please provide me with the list of conferences, including their IDs, 
with the Java topic happening in 2027, with the call for papers open today. 
Also, provide me with the list of my talks with this topic in the title. 
Finally, for each conference and talk retrieved, apply individually for the conference.\"}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ApacheHttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectionTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socketTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bedrockAgentCoreClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;US_EAST_1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invokeAgentRuntimeRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InvokeAgentRuntimeRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;                 
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AGENT_RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;                               
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEFAULT"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SdkBytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromUtf8String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responseStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrockAgentCoreClient&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeAgentRuntime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invokeAgentRuntimeRequest&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAllBytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go step-by-step through it. First of all, we define RUNTIME_ARN, which we deployed in the step before. Please still use the &lt;em&gt;{AWS_ACCOUNT_ID}&lt;/em&gt; placeholder, which will be dynamically replaced with your AWS Account ID. When we create &lt;em&gt;BedrockAgentCoreClient&lt;/em&gt;. We also explicitly set the Apache HTTP client with the extended connection and socket timeouts. Default 30-second timeouts maybe to short for communication with the Runtime. Then we create &lt;em&gt;InvokeAgentRuntimeRequest&lt;/em&gt; and set the agent Runtime ARN, qualifier (always DEFAULT), content type, and payload. The payload is our prompt. You can see the examples of the prompts in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf"&gt;part 4&lt;/a&gt; as we're communicating with the same MCP client, but deployed elsewhere. When we invoke the &lt;em&gt;invokeAgentRuntime&lt;/em&gt; method on the &lt;em&gt;bedrockAgentCoreClient&lt;/em&gt; by providing the &lt;em&gt;invokeAgentRuntimeRequest&lt;/em&gt; and convert the agent response to a string. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at how to deploy and run our MCP client on AgentCore Runtime. With that, our MCP client now scales nicely within the Runtime. &lt;/p&gt;

&lt;p&gt;Of course, you can create a nicer client by providing UI for entering the prompt and providing the agent response as a result. My goal was only to demonstrate how to implement such a client. Now we can change and redeploy our MCP client based on Spring AI on the AgentCore Runtime as often as we want. The client code remains unchanged as long as the Runtime ARN remains unchanged.&lt;/p&gt;

&lt;p&gt;Starting from the next article, we'll look at the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore" rel="noopener noreferrer"&gt;Spring AI AgentCore&lt;/a&gt; functionality. Spring AI AgentCore SDK is an open-source library that brings Amazon Bedrock AgentCore capabilities into Spring AI through familiar patterns: annotations, auto-configuration, and composable advisors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 4 Provide MCP tools for Conference application via AgentCore Gateway</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 18 May 2026 14:09:12 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-2odf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In this article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime. Later, in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In this article, we'll look at another alternative to AgentCore Runtime to host MCP servers on AgentCore - AgentCore Gateway. &lt;/p&gt;

&lt;h2&gt;
  
  
  Provide the MCP Tools for the Conference application via AgentCore Gateway
&lt;/h2&gt;

&lt;p&gt;Let's imagine a hypothetical situation: we not only want to search for the conferences, but also create, search, and apply for the talks for them. With this, our conference application now supports not only the attendee role but also the speakers. This is the reason why I added functionality to support conference search by the open call for papers criteria, see part 2. This is required for the conference speakers to determine whether it's still possible to apply for the conference with their talks. &lt;/p&gt;

&lt;p&gt;When searching for conferences, we didn't have a public API, which is why we created MCP. On the other hand, for creating, searching, and applying the talks for the conferences, we indeed have a public API. Let's assume this API is hosted on the Amazon API Gateway. But it could also be any external application that exposes an OpenAPI specification. How to implement such a use case? Of course, we can use &lt;a href="https://docs.aws.amazon.com/bedrockagentcore/latest/devguide/gateway.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway&lt;/a&gt; to securely connect our API to the AgentCore Gateway. The AgentCore Gateway can expose API functionality as MCP tools. But with this, we'll need to authenticate and hold the connection to multiple sources: AgentCore Runtime and Gateway. Without a centralized approach, customers face significant challenges: discovering and sharing tools across organizations becomes fragmented, managing authentication across multiple MCP servers grows increasingly complex, and maintaining separate gateway instances for each server quickly becomes unmanageable. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/de/blogs/machine-learning/transform-your-mcp-architecture-unite-mcp-servers-through-agentcore-gateway/" rel="noopener noreferrer"&gt;The centralized approach&lt;/a&gt;, which exposes all the tools from the central (MCP server) endpoint, would be a much better solution for our use case. Luckily, AgentCore Gateway helps to solve these challenges by treating existing MCP servers as native targets. This gives us a single point of control for routing, authentication, and tool management. It makes it as simple to integrate MCP servers as to add other targets to the gateway. AgentCore made it possible by supporting multiple targets. Those are, as of now: OpenAPI, Smithy, Amazon API Gateway, AWS Lambda, MCP Servers, and Integrations: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtx2vqmvatg9evkaldku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtx2vqmvatg9evkaldku.png" alt=" " width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference Talks and Applications Demo
&lt;/h2&gt;

&lt;p&gt;For creating, searching, and applying the talks for the conferences, I implemented a small &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/conference-talks-and-applications-app" rel="noopener noreferrer"&gt;conference-talks-and-applications-demo&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27stzmtpcrkfji5ktf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27stzmtpcrkfji5ktf7.png" alt=" " width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I currently don't use any database to store the talks and conference applications for simplicity reasons.  My goal is only to demonstrate the approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; I maintain a static list of the talks in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/GetConferenceTalksByTitleSubstring.java" rel="noopener noreferrer"&gt;GetConferenceTalksByTitleSubstring&lt;/a&gt; class. The search consists of looking for the provided substring of the title.&lt;/li&gt;
&lt;li&gt; When creating a new talk, I generate its random ID between 1 and 100 in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/CreateConferenceTalk.java" rel="noopener noreferrer"&gt;CreateConferenceTalk&lt;/a&gt; class and return the talk with ID, title, and description.&lt;/li&gt;
&lt;li&gt;When applying for a talk for a specific conference, I simply acknowledge that the application is created in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/src/main/java/software/amazonaws/example/conference/handler/CreateConferenceApplication.java" rel="noopener noreferrer"&gt;CreateConferenceTalk&lt;/a&gt; class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I prefer to use AWS SAM as IaC for pure Serverless applications. Unfortunately, AWS SAM doesn't provide any IaC for Amazon Bedrock AgentCore yet.  Also, SAM has some limitations, as it's, for example, not possible to create the response codes for each API. And those response codes are required by the OpenAPI specification to be present. That's why I created &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/ConferenceTalksAndApplicationsAppAPI-OpenAPISpec.yaml" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt; on my own for it. We can refer to this specification when defining the API like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Api&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;Stage&lt;/span&gt;
    &lt;span class="na"&gt;DefinitionBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;Fn::Transform&lt;/span&gt;
           &lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Include&lt;/span&gt;
           &lt;span class="s"&gt;Parameters&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
             &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConferenceTalksAndApplicationsAppAPI-OpenAPISpec.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also secured our API with an API key, whose value is by definition passed as the HTTP header parameter "x-api-key". This will play a role when we configure the outbound authentication of the AgentCore Gateway API Gateway target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyApiKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::ApiKey&lt;/span&gt;
  &lt;span class="s"&gt;....&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConferenceTalksAndApplicationsAppAPIKey"&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConferenceTalksAndApplicationsApp&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Key"&lt;/span&gt;
    &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;GenerateDistinctId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a6ZbcDgjkQW10BN56ASR25&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also defined an API stage with the name &lt;em&gt;prod&lt;/em&gt;. Now, we can deploy this application by executing &lt;code&gt;sam deploy -g&lt;/code&gt;, and we will see the individual URL in the response. For example, &lt;em&gt;&lt;a href="https://k370s19lk3.execute-api.us-east-1.amazonaws.com/prod" rel="noopener noreferrer"&gt;https://k370s19lk3.execute-api.us-east-1.amazonaws.com/prod&lt;/a&gt;&lt;/em&gt;.  We'll need the REST API ID, which is in our case k370s19lk3, later when creating the IaC for the AgentCore Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create AgentCore Gateway with different targets
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we started to create the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk" rel="noopener noreferrer"&gt;IaC for the Conference (Search) application&lt;/a&gt;. It consisted mainly of the AgentCore Runtime with the MCP protocol and everything needed for that, like the Cognito User (Client) Pool. We used CDK for Java for it. We'll now call this application the Conference application, as we are extending its functionality beyond the search. Our goal is now to create AgentCore Gateway with 2 targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existing AgentCore Runtime with MCP protocol for the conference search (MCP) tools&lt;/li&gt;
&lt;li&gt;conference talks and applications demo deployed on Amazon Gateway API to expose all its APIs as (MCP) tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the full source code in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/gateway/GatewayTargetStack.java" rel="noopener noreferrer"&gt;GatewayTargetStack&lt;/a&gt; class. &lt;/p&gt;

&lt;p&gt;Let's go step-by-step through it. We first create the AgentCore Gateway itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Gateway-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"-gateway"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomJwtAuthorizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;allowedClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
               &lt;span class="n"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserPoolClientId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;discoveryUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COGNITO_DISCOVERY_URL&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeWithMCPStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgenCore Runtime with MCP protocol for running conference search app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most interesting part is configuring the custom JWT authorizer as an inbound authentication. Here we reuse the Cognito User (Client) Pool created in part 2. We set the same user client pool ID and discovery URL. We also reuse the same AWS IAM role that we used to create AgentCore  Runtime in part 2. Please also read the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/policy-getting-started.html" rel="noopener noreferrer"&gt;Getting started with Policy in AgentCore&lt;/a&gt; in addition to the resources from part 2 on how to create one.&lt;/p&gt;

&lt;p&gt;Now, let's create the AgentCore Gateway target of our MCP Server running on AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCP-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;McpServerTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;         
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayTargetName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mcp-target"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgentCore Runtime MCP Server Target "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set &lt;em&gt;McpServerTargetConfiguration&lt;/em&gt;, which defines that the Gateway target is the MCP Server running on AgentCore Runtime. Also, we set the target name and description, and provide the AgentCore Gateway to which this target belongs.  We need to set the endpoint URL, which always follows the same schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://bedrock-agentcore."&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="s"&gt;".amazonaws.com/runtimes/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nc"&gt;RuntimeWithMCPStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAgentRuntimeId&lt;/span&gt;&lt;span class="o"&gt;()+&lt;/span&gt;
       &lt;span class="s"&gt;"/invocations? 
       qualifier=DEFAULT&amp;amp;accountId="&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getAccount&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We obtain the runtime ID property from the created AgentCore Runtime in the &lt;em&gt;RuntimeWithMCPStack&lt;/em&gt; stack. The next part is to configure the outbound authentication. This means to configure how the Agentcore Gateway MCP target authenticates with the AgentCore Runtime with the MCP protocol. For this, we need to use AgentCore Identity.&lt;br&gt;
As described in the following &lt;a href="https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/23" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, it's currently not possible to create the AgentCore Identity with CloudFormation. That's why CDK also can't provide this functionality. That's why we need to create it manually and then provide the configuration for this stack. Let's secure it with the existing OAuth Client. Let's go to AgentCore Identity and click on "Add Outbound Auth" -&amp;gt; "Add OAuth Client". Then select "Custom Provider" -&amp;gt; "Discovery URL" :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrsvb1oprnutpatb4el2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrsvb1oprnutpatb4el2.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can reuse the Cognito User Pool Client ID, Client Secret, and Discovery URL from &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After we created the AgentCore Identity, let's grab its ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncju8yxda8r98p3n05ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncju8yxda8r98p3n05ft.png" alt=" " width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client Secret will be automatically stored as a Secret in the AWS Secrets Manager. Let's also grab Secret ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf555ge962hqi15p4bhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf555ge962hqi15p4bhu.png" alt=" " width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's configure both in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agentcoreIdentityOutboundOAuthArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:token-vault/default/oauth2credentialprovider/resource-provider-oauth-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"oAuthSecretArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:{AWS_ACCOUNT_ID}:secret:bedrock-agentcore-identity!default/oauth2/resource-provider-oauth-gateway-ba3b089d-toYfaV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace both values with your individual ARNs. I explained in part 2 how we handle the AWS Account ID. Now, let's create and configure the credential provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CloudFormation, see the issue https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/2391&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oAuthProviderArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"agentcoreIdentityOutboundOAuthArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oAuthSecretArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"oAuthSecretArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GatewayCredentialProvider&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromOauthIdentityArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuthConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;providerArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oAuthProviderArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;secretArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oAuthSecretArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

  &lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCP-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauthCredentialProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab the AgentCore Identity and Secret ARNs and use them to create an OAuth Credential Provider. We then set it when creating the AgentCore Target credential provider configuration.&lt;/p&gt;

&lt;p&gt;Now we are done with creating the AgentCore MCP Target.  The next step is to create an Amazon API Gateway target. Please also read the article &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-api-gateway.html" rel="noopener noreferrer"&gt;AgentCore Gateway Amazon API Gateway stages&lt;/a&gt; to gain an understanding of how AgentCore Gateway obtains the OpenAPI spec from the Amazon Gateway stage.&lt;/p&gt;

&lt;p&gt;First of all, let's define the API stage name in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"restApiStageName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll pass the restApiId via the console parameter. We created it above when we deployed the conference talks and applications demo. Similar to AWS Account ID, which is public, we don't want to configure it in cdk.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;restApiId&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"restApiId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;restApiStageName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"restApiStageName"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;         
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;restApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRestApiId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-ID"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;restApiId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restApiStageName&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;...&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gatewayTargetName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apigateway-target"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Amazon ApiGateway Target "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we create the AgentCore Gateway Target as an Amazon API Gateway Target, set the target name and description. We also provide the REST API ID, stage, and AgentCore Gateway to which this target belongs.&lt;/p&gt;

&lt;p&gt;We can define the tool filters. With that, we can shrink what Amazon API Gateway APIs will be exposed as MCP tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolFilters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks/{titleSubstring}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;                 
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/apply"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ApiGatewayToolFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                    
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our example, we expose all 3 APIs (/apply, /talks, //talks/{titleSubstring}) as MCP tools.&lt;/p&gt;

&lt;p&gt;Next, let's use the tool override to give the MCP tools the proper names and descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayToolConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolOverrides&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apply-to-conferences-w-conference-id-talk-id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/apply"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apply to the conference with conference Id and talk Id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
         &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create-new-talk"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create a new talk with talk Id, title and description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;                    
         &lt;span class="nc"&gt;ApiGatewayToolOverride&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayHttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get-talks-by-title-substring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/talks/{titleSubstring}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get talks by their title substring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, LLM can easily find the right tool for the job.&lt;/p&gt;

&lt;p&gt;The last part is to define how AgentCore Gateway handles the outbound authentication to the Amazon API Gateway. As described above and in the following &lt;a href="https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/23" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, it's currently not possible to create the AgentCore Identity with CloudFormation. That's why CDK also can't provide this functionality. That's why we need to create it manually and then provide the configuration for this stack. Let's secure this Target with the API Key, as it is how we secured our Amazon Gateway API. Let's go to AgentCore Identity and click on "Add Outbound Auth" -&amp;gt; "Add API Key" :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2os77ekoke7z1d0cdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2os77ekoke7z1d0cdh.png" alt=" " width="744" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please put the same API Key that we used to secure our API. We defined it in the  &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/conference-talks-and-applications-app/template.yaml" rel="noopener noreferrer"&gt;SAM template&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
After we created the AgentCore Identity, let's grab its ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i3n44bxl7e81xevky0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i3n44bxl7e81xevky0y.png" alt=" " width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client Secret will be automatically stored as a Secret in the AWS Secrets Manager. Let's also grab Secret ARN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npa4hw9j3qf2ni3o3zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0npa4hw9j3qf2ni3o3zb.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's configure both in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agentcoreIdentityOutboundApiKeyArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:us-east-1:{AWS_ACCOUNT_ID}:token-vault/default/apikeycredentialprovider/resource-provider-api-key-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKeySecretArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:us-east-1:{AWS_ACCOUNT_ID}:secret:bedrock-agentcore-identity!default/apikey/resource-provider-api-key-gateway-02d581b0-L9scmD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace both values with your individual ARNs. I explained in part 2 how we handle the AWS Account ID. Now, let's create and configure the credential provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeyProviderArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"agentcoreIdentityOutboundApiKeyArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeySecretArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"apiKeySecretArn"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKeyProviderConfigs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GatewayCredentialProvider&lt;/span&gt;          
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromApiKeyIdentityArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyCredentialProviderProps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;providerArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeyProviderArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;secretArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeySecretArn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialLocation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyCredentialLocation&lt;/span&gt;                    
                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiKeyAdditionalConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialParameterName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialPrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

&lt;span class="nc"&gt;GatewayTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"APIGATEWAY-Target-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetConfiguration&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;ApiGatewayTargetConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialProviderConfigurations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKeyProviderConfigs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab the AgentCore Identity and Secret ARNs and use them to create an API Key Credential Provider. Then we define to set the credentials within the HTTP header with the name &lt;em&gt;x-api-key&lt;/em&gt;. This is how we secured the Amazon API Gateway. Another option that AgentCore Gateway supports is to set them as query parameters. We then set them when creating the AgentCore Target credential provider configuration.&lt;/p&gt;

&lt;p&gt;To deploy the AgentCore Gateway, please invoke &lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-gateway-with-mcp-server-target-stack -c awsAccountId={YOUR_AWS_ACCOUNT_ID} -c restApiId={YOUR_API_ID}&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;After having successfully executed the AgentCore Gateway deployment, we'll see our Gateway in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0fnro4uqhadcnrtsu84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0fnro4uqhadcnrtsu84.png" alt=" " width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to grab the Gateway URL, which ends with &lt;em&gt;/mcp&lt;/em&gt;.  We also see both Gateway targets we created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bgts2vqw5d9fzhmi4nz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bgts2vqw5d9fzhmi4nz.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This AgentCore exposes 7 MCP tools in total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; 4 tools for the conference search provided by the MCP server from part 2 and deployed on AgentCore Runtime.&lt;/li&gt;
&lt;li&gt; 3 tools to create a talk, search for existing talks, and apply for the conference with the talk. This 3 tools are provided through the Amazon API Gateway we deployed in this article.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's extend our Conference Application MCP client that we developed in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a"&gt;part 3&lt;/a&gt;, so it can use this AgentCore Gateway MCP endpoint.&lt;/p&gt;

&lt;p&gt;The important remaining topic is designing the IAM role and permissions so that AgentCore Gateway can handle inbound and outbound authentication and communicate with the Amazon API Gateway. I'll refer you to the articles, which cover those topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html#gateway-inbound-auth-iam" rel="noopener noreferrer"&gt;inbound authentication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html#gateway-outbound-auth-oauth" rel="noopener noreferrer"&gt;outbound authorization with an OAuth client&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html#gateway-outbound-auth-api-key" rel="noopener noreferrer"&gt;outbound authorization with an API Key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-api-gateway.html#gateway-target-api-gateway-outbound" rel="noopener noreferrer"&gt;outbound authorization methods for an API Gateway API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extend our local Conference Application MCP client
&lt;/h2&gt;

&lt;p&gt;In part 3, we developed a generic &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-agent-local" rel="noopener noreferrer"&gt;local MCP client&lt;/a&gt; capable of talking to each MCP server. I decided to extend it to be able to configure the AgentCore Gateway endpoint. This gives us the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;by configuring the &lt;em&gt;amazon.bedrock.agentcore.runtime.id&lt;/em&gt; property in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; to be not a blank string, we'll still connect to the MCP server running on AgentCore Runtime. It exposes only 4 MCP tools for the conference search.&lt;/li&gt;
&lt;li&gt;by configuring the &lt;em&gt;amazon.bedrock.agentcore.gateway.url&lt;/em&gt; property in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; to be not a blank string, we'll connect to the AgentCore Gateway created previously, which exposes all 7 MCP tools. This is how we'll use it to show what is possible with that. Please make sure that &lt;em&gt;amazon.bedrock.agentcore.runtime.id=&lt;/em&gt; is set to an empty string.&lt;/li&gt;
&lt;li&gt;by configuring both properties, &lt;em&gt;amazon.bedrock.agentcore.runtime.id&lt;/em&gt; take precedence. This is how I implemented the logic in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; class:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getMCPServerEndpoint&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="no"&gt;AGENTCORE_RUNTIME_ID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"https://bedrock-agentcore."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;awsRegion&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".amazonaws.com/runtimes/"&lt;/span&gt;
       &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;AGENTCORE_RUNTIME_ID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/invocations?qualifier=DEFAULT&amp;amp;accountId="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAccountId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="no"&gt;AGENTCORE_GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;AGENTCORE_GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" no AgentCore Runtime Id or AgentCore Gateway URL defined"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change this logic if you wish.&lt;/p&gt;

&lt;p&gt;Now we can use CURL or &lt;a href="https://httpie.io/docs/cli/installation" rel="noopener noreferrer"&gt;HTTPie&lt;/a&gt; to send some prompts. For example:&lt;/p&gt;

&lt;p&gt;"Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference".&lt;/p&gt;

&lt;p&gt;Here is an example of the request with HTTPie:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027, with the call for papers open today. Also, provide me with the list of my talks with this topic in the title. Finally, for each conference and talk retrieved, apply individually for the conference." Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahrxf31uh3ctynvjgvs4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahrxf31uh3ctynvjgvs4.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try another prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please create a talk with a cool title (max 60 characters long) and description (max 300 characters long) about using Spring AI on the Amazon Bedrock AgentCore service. Then provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today. Finally, for each conference, apply individually for it with the talk just created." Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response again: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuaovv9kzlbkc5os1it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuaovv9kzlbkc5os1it.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, we created AgentCore Gateway, which gives us centralized access to the MCP tools that we need or the agent needs to accomplish the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at how to provide the MCP Tools for the Conference application via AgentCore Gateway in a centralized way.&lt;/p&gt;

&lt;p&gt;As we saw in this and previous articles, the local MCP client for the Conference application, to talk to AgentCore Runtime or Gateway, became quite big. If we have many customers using such a client, changing and operating it can become quite challenging. That's why, in the next article, we look at how to deploy and run our MCP client on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 3 Develop local MCP client for Conference application</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 11 May 2026 15:03:58 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-560a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In this article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Develop local MCP client for Conference application
&lt;/h2&gt;

&lt;p&gt;You can find the source code of the MCP client in my &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-agent-local" rel="noopener noreferrer"&gt;spring-ai-1.1-conference-app-agent-local&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;Let's go step-by-step through it.&lt;/p&gt;

&lt;p&gt;First, in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;, we include,  among others, those dependencies: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spring-ai-bom - to include the general Spring AI functionality.&lt;/li&gt;
&lt;li&gt;spring-boot-starter-web - as we develop the MCP client as a web application.&lt;/li&gt;
&lt;li&gt;spring-ai-starter-model-bedrock-converse -as we use foundational models on Amazon Bedrock.&lt;/li&gt;
&lt;li&gt;spring-ai-starter-mcp-client-webflux - to develop an &lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html" rel="noopener noreferrer"&gt;asynchronous Spring AI MCP Client&lt;/a&gt;. We can use spring-ai-starter-mcp-client to develop a synchronous one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/java/dev/vkazulkin/SpringAIConferenceLocalMCPClient.java" rel="noopener noreferrer"&gt;SpringAIConferenceLocalMCPClient&lt;/a&gt; class is the main entry point to our application.&lt;/p&gt;

&lt;p&gt;Second, in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;, we define some properties. Those are Spring AI-related:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.bedrock.aws.region&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.bedrock.aws.timeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10m&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.bedrock.converse.chat.options.max-tokens&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.bedrock.converse.chat.options.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;amazon.nova-lite-v1:0&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.mcp.client.type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ASYNC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define the region where we host our application, and the timeout when talking to the Amazon Bedrock models. Then we also set the default Amazon Bedrock to use and a maximal number of tokens, and the MCP client type to ASYNC. We can also set SYNC instead, but we need to use another Spring AI MCP client dependency as described above.&lt;/p&gt;

&lt;p&gt;We also include some application-related properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cognito.user.pool.name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;UserPoolForAgentCoreMCP&lt;/span&gt;
&lt;span class="py"&gt;cognito.user.pool.client.name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;UserPoolClientWithUserAndPasswordForAgentCoreMCP&lt;/span&gt;
&lt;span class="py"&gt;cognito.auth.token.resource.server.id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;AgentCoreResourceServerId&lt;/span&gt;
&lt;span class="py"&gt;amazon.bedrock.agentcore.runtime.id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;spring_ai_conference_search_agentcore_runtime-6dnMIL9455&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are individual properties, whose values we need to set from the deployment of the Conference search MCP server. We described the configuration, creation process, and those properties of the MCP server in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please ignore other properties like &lt;em&gt;amazon.bedrock.agentcore.gateway.url&lt;/em&gt; as we will need them when we extend our application in the next articles.&lt;/p&gt;

&lt;p&gt;The whole application logic is in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/java/dev/vkazulkin/agent/controller/SpringAIAgentController.java" rel="noopener noreferrer"&gt;SpringAIAgentController&lt;/a&gt; class. &lt;/p&gt;

&lt;p&gt;We inject the values of individual properties and build AWS service clients (STS and Cognito). This is how we create the ChatClient, which is the main interface of Spring AI to talk to the LLMs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SpringAIAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ChatMemory&lt;/span&gt; &lt;span class="n"&gt;chatMemory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${aws.region}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;awsRegion&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ToolCallingChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We show here that we can optionally build &lt;em&gt;ToolCallingChatOptions&lt;/em&gt; and override the default model name and the maximum number of tokens defined in application.properties. Then, we build the &lt;em&gt;ChatClient&lt;/em&gt;, and can optionally set &lt;em&gt;ToolCallingChatOptions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Below is how the code for the method looks, which will receive the prompt from the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/conference"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;conferenceSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolsResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listTools&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolsResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool found "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mcpClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;


  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this code down and explain it. First, we need to obtain the JWT token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This, in turn, uses a bunch of Amazon Cognito services to achieve this goal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getAuthTokenViaHttpClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getUserPool&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userPoolClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getUserPoolClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPool&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userPoolClientType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;describeUserPoolClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userPoolId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userPool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;userPoolId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userPoolId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userPoolId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".auth."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;US_EAST_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".amazoncognito.com/oauth2/token"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="no"&gt;SCOPE_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RESOURCE_SERVER_ID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"grant_type=client_credentials&amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"client_id="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userPoolClientType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"client_secret="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userPoolClientType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"scope="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;SCOPE_STRING&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClients&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDefault&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClassicRequestBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpPost&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthTokenResponseHandler&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;       
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use the configuration of the user (client ) names and the resource server ID from &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; to obtain the user (client) pool. Then we construct the URL and the body (entity) of the HTTP request to obtain the authentication token. After it, we execute this request and obtain the token from the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthTokenResponseHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HttpClientResponseHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;handleResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClassicHttpResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;HttpException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntity&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getContent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responseString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAllBytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responseMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseString&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeReference&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class="o"&gt;{});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;responseMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"access_token"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we have obtained the token, we're ready to create the (asynchronous as configured) MCP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's describe what happens when we invoke the &lt;em&gt;getMcpClientTransport&lt;/em&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;McpClientTransport&lt;/span&gt; &lt;span class="nf"&gt;getMcpClientTransport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;        
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="no"&gt;MCP_SERVER_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMCPServerEndpoint&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;webClientBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headerValue&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"accept"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"application/json, text/event-stream"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WebClientStreamableHttpTransport&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MCP_SERVER_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first construct the &lt;em&gt;MCP_SERVER_ENDPOINT&lt;/em&gt; URL from the in application.properties configured AgentCore Runtime ID. In the next article, I'll add the use case to also add the AgentCore Gateway URL. Then, we create the &lt;em&gt;WebClientBuilder&lt;/em&gt; by passing some HTTP headers, including the bearer token. After it, we create &lt;em&gt;WebClientStreamableHttpTransport&lt;/em&gt; and set the web client builder and the MCP server endpoint. It's important to use the HTTP Streamable web client because AgentCore Runtime (and Gateway) only supports it.&lt;/p&gt;

&lt;p&gt;Now we are ready to initialize our MCP client and obtain the list of tools from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolsResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listTools&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolsResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool found "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get all 4 tools that our Conference Search application from &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8"&gt;part 2&lt;/a&gt; exposes, which we deployed on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;Next, we need to create the list of tool callbacks from the MCP Client to pass to the &lt;em&gt;ChatClient&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mcpClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't need all the tools, you can filter them and, for example, only leave those tools whose name contains &lt;em&gt;Conference_Search_Tool_By_Topic&lt;/em&gt; as a substring, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
&lt;span class="nc"&gt;AsyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;mcpClients&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpToolFilter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;              
       &lt;span class="nd"&gt;@Override&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;McpConnectionInfo&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Conference_Search_Tool_By_Topic"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
             &lt;span class="o"&gt;}&lt;/span&gt; 
         &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, to enable search prompts such as "Please provide me with the list of conferences including their IDs, with Java topic happening in 2027, with call for papers open today", we need to obtain the current date. LLM doesn't know the current date, and for this, I wrote a small tool with the name &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/spring-ai-1.1-conference-app-agent-local/src/main/java/dev/vkazulkin/agent/tools/DateTimeTools.java" rel="noopener noreferrer"&gt;DateTimeTools&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Get the current date "&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getLocalDate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;       
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It contains only one tool to get the current date. Then, we pass this local tool to the &lt;em&gt;ChatClient&lt;/em&gt; by invoking the &lt;em&gt;tools&lt;/em&gt; method. We also pass the tool callback list from the &lt;em&gt;AsyncMcpToolCallbackProvider&lt;/em&gt; by invoking the &lt;em&gt;toolCallbacks&lt;/em&gt; method. The last step is to use the &lt;em&gt;ChatClient&lt;/em&gt; with the given prompt and tool (callbacks) to produce an answer to the prompt. This answer will be streamed back to the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeTools&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncMcpToolCallbackProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's build our application with &lt;code&gt;mvn clean package&lt;/code&gt; and start it with: &lt;code&gt;mvn spring-boot:run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can use CURL or &lt;a href="https://httpie.io/docs/cli/installation" rel="noopener noreferrer"&gt;HTTPie&lt;/a&gt; to send some prompts. For example:&lt;/p&gt;

&lt;p&gt;"Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027".&lt;/p&gt;

&lt;p&gt;Here is an example of the request with HTTPie:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please provide me with the list of conferences, including their IDs, with Java topics happening in 2027" Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg21z0unwb6bsrctb6nl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg21z0unwb6bsrctb6nl.png" alt=" " width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the description and logs, LLM used the tool &lt;em&gt;Conference_Search_Tool_By_Topic_And_Date&lt;/em&gt; from the MCP server to produce the answer. Let's try another prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http GET http://localhost:8080/conference?prompt="Please provide me with the list of conferences, including their IDs, with Java topics happening in 2026 and 2027, with the call for papers open today" Content-Type:text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the correct LLM response again: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2obqjj9bxe7dya6lt2qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2obqjj9bxe7dya6lt2qq.png" alt=" " width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the description and logs, LLM used the tools to produce the answer. &lt;em&gt;Conference_Search_Tool_By_Topic_Date_CFP_Open&lt;/em&gt; from the MCP server and the local tool &lt;em&gt;Get_The_Current_Date&lt;/em&gt; to produce the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we developed the (MCP-) client, capable of talking to our application running on AgentCore Runtime. In the next article, we'll look at another alternative to AgentCore Runtime to host MCP servers on AgentCore - AgentCore Gateway. We'll also compare both alternatives. In one of the next articles, I'll show you how to deploy and run this MCP client on the AgentCore Runtime as well, using the HTTP protocol. It's not always appropriate to work with the client locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 2 Deploy Conference Search application on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 04 May 2026 14:23:19 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-2bo8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the article &lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-1-introduction-and-the-sample-application-4hof"&gt;Introduction to Spring AI&lt;/a&gt;, we introduced the sample application to search for conferences. We also exposed its functionality as a set of MCP-compatible tools. In the article &lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-4-exploring-model-context-protocol-streamable-http-transport-2o5h"&gt;Explore Spring AI MCP Server with Streamable HTTP protocol&lt;/a&gt;, we ran this application as an MCP-Server locally and connected to it using the MCP Inspector or Amazon Q Developer.&lt;br&gt;
Of course, running the application locally is not an enterprise-ready solution. That's why, in this article, we'll explain how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. &lt;/p&gt;
&lt;h2&gt;
  
  
  Adjustments of the conference search application
&lt;/h2&gt;

&lt;p&gt;I decided to make some adjustments to the conference search application, which will act as the MCP server by exposing its functionality through tools. Please review the above-mentioned to gain a basic understanding of how to use the Spring AI framework with its rich set of features, including the MCP client and server functionality. You can find the updated version of our application in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server" rel="noopener noreferrer"&gt;spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;I updated the examples to use Java 25 and recent Spring Boot 4 and Spring AI version 1.1.x versions. You can update it to the newest minor versions if you wish. And there is the Spring AI 2.x branch for Spring Boot 4 applications. The last one is currently in development and not GA. When it's released, I'll also provide my examples for the Spring AI 2.x version. &lt;/p&gt;

&lt;p&gt;I also adjusted the static list of &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server/src/main/resources/conferences.json" rel="noopener noreferrer"&gt;conferences&lt;/a&gt; to search for. Additionally, I updated the conference properties to set the conference (fake) dates in the future (the previous ones were mostly for 2025). And I also added 3 additional properties: conferenceId, callForPapersStartDate, and callForPapersEndDate. This enables us to search not only for all conferences, conferences by topic, and additionally by the date range, but also for the date when the call for papers is still open. In the course of the series, I'll use this functionality to apply for the conference when we extend our application.&lt;/p&gt;

&lt;p&gt;With this, the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server/src/main/java/dev/vkazulkin/conference/Conference.java" rel="noopener noreferrer"&gt;Conference&lt;/a&gt; domain class looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Conference&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;conferenceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;topics&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;homepage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
        &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;endDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersStartDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersEndDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;linkToCallforPapers&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added one additional tool in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server/src/main/java/dev/vkazulkin/conference/ConferenceSearchTool.java" rel="noopener noreferrer"&gt;ConferenceSearchTool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's responsible for answering the following prompt: "Please provide me with the list of conferences, including their IDs, with a Java topic happening in 2027, with a call for papers open today."  Here is how it's implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Conference_Search_Tool_By_Topic_Date_CFP_Open"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Search for the conference list for exactly one topic provided, conference dates and the call for papers still open on the given date"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Conference&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"conference topic"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;" the conference earliest start date"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;earliestStartDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;" the conference latest start date"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;latestStartDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;" the call for papers still open on this date"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;callForPapersStillOpenOnThisDate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;conferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;topics&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;isConferenceStartDateInDateRange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;earliestStartDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latestStartDate&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;isCallForPapersOpenOnThisDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callForPapersStillOpenOnThisDate&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toSet&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run this updated version of the application locally as the MCP server, as described in the &lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-4-exploring-model-context-protocol-streamable-http-transport-2o5h"&gt;Explore Spring AI MCP Server with Streamable HTTP protocol&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the conference search application on the Amazon Bedrock AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;I've written the whole &lt;a href="https://dev.to/vkazulkin/series/34351"&gt;article series&lt;/a&gt; about this service. So I refer to it for the overview of this service. AgentCore Runtime also lets us deploy and run Model Context Protocol (MCP) servers in the AgentCore Runtime, see &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp.html" rel="noopener noreferrer"&gt;Deploying MCP servers in AgentCore Runtime&lt;/a&gt;. In the next article, we'll develop the (MCP-) client, capable of talking to our application (MCP server).&lt;/p&gt;

&lt;p&gt;One important thing is to understand &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp.html#runtime-mcp-how-it-works" rel="noopener noreferrer"&gt;How Amazon Bedrock AgentCore supports MCP&lt;/a&gt;. AgentCore supports both stateless and stateful streamable-HTTP MCP servers. By default, stateless mode (stateless_http=True) is recommended for basic MCP servers. The platform automatically adds an Mcp-Session-Id header for any request without one, so MCP clients can maintain connection continuity to the same Amazon Bedrock AgentCore Runtime session. Spring AI also supports &lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-stateless-server-boot-starter-docs.html#_stateless_streamable_http_mcp_servers" rel="noopener noreferrer"&gt;Stateless Streamable-HTTP MCP Servers&lt;/a&gt;. To configure them, we need to add some properties to &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-search-app-bedrock-agentcore-runtime-mcp-server/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.mcp.server.type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SYNC&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.mcp.server.protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STATELESS&lt;/span&gt;
&lt;span class="py"&gt;server.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8000&lt;/span&gt;
&lt;span class="py"&gt;server.address&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tried to automate as much as possible for the IaC with AWS CDK for Java. Please read the article &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html" rel="noopener noreferrer"&gt;Getting started with the AWS CDK&lt;/a&gt; for the overview and installation. I usually use AWS SAM, but it currently doesn't have Bedrock AgentCore support. &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/java/software/amazon/awscdk/services/bedrock/agentcore/alpha/package-summary.html" rel="noopener noreferrer"&gt;CDK AgentCore&lt;/a&gt; L2 construct is currently still alpha. Even with CDK, it turned out to be challenging because of the discovered issues and difficulties in fully automating roles, permissions, and Docker image creation with IaC. I'll give some guidance on how to proceed. You can find the whole IaC in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk" rel="noopener noreferrer"&gt;spring-ai-1.1-conference-app-bedrock-agentcore-cdk&lt;/a&gt; repository. &lt;/p&gt;

&lt;p&gt;Let's first comment out the definition of the GatewayTargetStack stack in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/CDKApp.java" rel="noopener noreferrer"&gt;CDKApp&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;CDKApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"spring-ai-conference-search-agentcore"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackProperties&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeWithMCPStack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackProperties&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
   &lt;span class="c1"&gt;//new GatewayTargetStack(app, appName, stackProperties());&lt;/span&gt;
   &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;synth&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It contains the Stack using the AgentCore Gateway service, which we'll explore in the later articles. AgentCore Gateway also provides the functionality of the managed MCP server. I'll then give some recommendations on when to use the Runtime (directly) and when to use the Gateway. &lt;/p&gt;

&lt;p&gt;Now, let's take a look at the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt;, which we'll deploy later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;               
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeAuthorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingJWT&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COGNITO_DISCOVERY_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserPoolClientId&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AgenCore Runtime with MCP protocol for running conference search app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;protocolConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProtocolType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executionRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"RuntimeIdOutput"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAgentRuntimeId&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;           
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start with some easy parts: we give the AgentCore runtime a name, description, and define the protocol as MCP. Finally, the deployed runtime ID will be placed as the output variable &lt;em&gt;RuntimeIdOutput&lt;/em&gt;. We'll see it in the console after the deployment of this stack is finished.&lt;/p&gt;

&lt;p&gt;Let's cover the artifact part. You can automate the steps of building the Docker file, uploading it to the &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry&lt;/a&gt;, and referencing the image URL completely. The AgentRuntimeArtifact class offers different &lt;em&gt;from*&lt;/em&gt; methods (fromCode, fromAsset, and so on). I prefer to do those steps separately and only reference the image URI. This is how publishing to ECR works :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build the application&lt;/span&gt;
mvn clean package 

&lt;span class="c"&gt;# build the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server:v1 

&lt;span class="c"&gt;# Login to ECR&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com  

&lt;span class="c"&gt;# Create ECR repository (if it doesn't exist)&lt;/span&gt;
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;  

&lt;span class="c"&gt;# Tag the Docker image&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server:v1 &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server:v1

&lt;span class="c"&gt;# Push the Docker Image to the ECR repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push &lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server:v1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace AWS {account_id} and {region} with our own values. Also, your version may not be &lt;em&gt;v1&lt;/em&gt; but a different one.&lt;/p&gt;

&lt;p&gt;We can also build the Docker image by using Buildpack support built into Spring instead of a Dockerfile. Just use the Maven task &lt;a href="https://docs.spring.io/spring-boot/maven-plugin/build-image.html" rel="noopener noreferrer"&gt;spring-boot:build-image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at the relevant code parts to assign this code artifact to the AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ecrImageURIForConferenceSearchAndApplicationAppAsMCPServer"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;          
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromImageUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ecrImageURI&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;                                
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentRuntimeArtifact&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we get the value of the variable &lt;em&gt;ecrImageURIForConferenceSearchAndApplicationAppAsMCPServer&lt;/em&gt;, which points to the imageURI in the ECR.  This is typically done in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ecrImageURIForConferenceSearchAndApplicationAppAsMCPServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/spring-ai-1.1-conference-search-bedrock-agentcore-runtime-mcp-server:v17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's ignore all the content variables we defined there for a moment. Please adjust the value so that it matches your imageURI. We use the placeholder {AWS_ACCOUNT_ID} there. The reason for it is that I don't want to expose the AWS account ID publicly. That's why I wrote the following utility method &lt;em&gt;getContextVariableValueWithReplacedAccountId&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/ConventionalDefaults.java" rel="noopener noreferrer"&gt;ConventionalDefaults&lt;/a&gt; class to replace the placeholder with the real value :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"awsAccountId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;awsAccountId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"please provide your aws account id as as content to the call, for example: cdk deploy -c awsAccountId=1234567890101"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableValue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;tryGetContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextVariableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;replaceAWSAccountID&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configParam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{AWS_ACCOUNT_ID}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;awsAccountId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command to deploy this stack later is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-runtime-with-mcp-server-stack  -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Don't do it now, as we first need to create inbound authentication.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;spring-ai-conference-search-agentcore-runtime-with-mcp-server-stack&lt;/em&gt;  is the name of our stack, and you need to pass your AWS account ID. Here, I assume you operate everything (AgentCore, IAM Role, ECR) in the same AWS account. Otherwise, you'll need to adjust the code to adapt to your needs.&lt;/p&gt;

&lt;p&gt;Now, let's cover the part of defining the authorizer configuration and assigning it to the AgentCore Runtime.  This configuration is responsible for creating the inbound authentication type. As we deploy our application on the Runtime publicly, we need to secure the access. For the Runtime MCP protocol, there are inbound authentication types :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM permissions - This will use the IAM username that you used to sign-in to the AWS console)&lt;/li&gt;
&lt;li&gt;JSON Web Tokens (JWT) - Configure JWT (like an OAuth token) as the Inbound Auth to validate incoming token signatures and scopes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll use JWT issues by Amazon Cognito as an identity provider. For this, we need to define the  Discovery URL from Cognito and JWT Authorization Configuration ( "allowed clients IDS"), see the relevant code from the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;               
 &lt;span class="o"&gt;....&lt;/span&gt; 
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizerConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeAuthorizerConfiguration&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usingJWT&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COGNITO_DISCOVERY_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  
 &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserClientPoolStack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserPoolClientId&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
 &lt;span class="o"&gt;...&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how do we get those values? For this, we need to set up an Amazon Cognito user pool, user domain (to use the JWT token), and user client pool. I define all this in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/cognito/UserClientPoolStack.java" rel="noopener noreferrer"&gt;UserClientPoolStack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll leave it up to you to understand this stack in detail, because it requires Cognito knowledge and is not strictly related to the AgentCore. But basic steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a user pool with the given ID.&lt;/li&gt;
&lt;li&gt;Create a resource server with the scope (I created the full access scope).&lt;/li&gt;
&lt;li&gt;Add the resource server to the already created user pool.&lt;/li&gt;
&lt;li&gt;Construct the discovery URL, which always has a predefined schema: &lt;a href="https://cognito-idp.%22+%7Bregion%7D+%22.amazonaws.com/%22+%7BuserPoolId%7D+%22/.well-known/openid-configuration" rel="noopener noreferrer"&gt;https://cognito-idp."+{region}+".amazonaws.com/"+{userPoolId}+"/.well-known/openid-configuration&lt;/a&gt;".&lt;/li&gt;
&lt;li&gt;Create a user client pool with the given name with only the default user flow, client credentials, and the scopes with the resource server we defined for the user pool. Add the already created user pool to the user client pool and generate the secret for it.&lt;/li&gt;
&lt;li&gt;Add the domain to the user pool we created to issue the token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially, I thought I could automate this part completely. We don't even need to run the UserClientPoolStack stack individually, as we made the COGNITO_DISCOVERY_URL and the userPoolClient publicly there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;COGNITO_DISCOVERY_URL&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;UserPoolClient&lt;/span&gt; &lt;span class="n"&gt;userPoolClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also used them both from the RuntimeWithMCPStack as shown in the example above. With that, CDK understands that the RuntimeWithMCPStack stack depends on the UserClientPoolStack stack and executes the latter automatically first. &lt;/p&gt;

&lt;p&gt;What should have worked out of the box is to create the domain prefix from the user pool ID. By definition, the prefix should be the user pool ID with lowercase letters, and the character _ stripped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;userPool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addDomain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UserPoolForAgentCoreMCPDomain"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserPoolDomainOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cognitoDomain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CognitoDomainOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;domainPrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPoolId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cognito doesn't accept setting other domain prefixes.  But unfortunately, I encountered one issue with the creation of the user pool domain, which I described &lt;a href="https://github.com/aws/aws-cdk/issues/37514" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The only workaround I currently found was to comment out the user domain creation in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/cognito/UserClientPoolStack.java" rel="noopener noreferrer"&gt;UserClientPoolStack&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
userPool.addDomain("UserPoolForAgentCoreMCPDomain", UserPoolDomainOptions.builder()
   .cognitoDomain(CognitoDomainOptions.builder()
   .domainPrefix(cognitoDomainPrefix.replace("_", "")
      .toLowerCase()).build()).build());
*/&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, execute this stack individually: &lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-user-client-pool-stack  -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, grab the value of the output variable &lt;em&gt;CognitoUserPoolIdOutput&lt;/em&gt; and configure it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cognitoDomainPrefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1_JbjQPT5GJ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, uncomment the user domain creation, which uses the value of this variable to construct the domain name :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cognitoDomainPrefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cognitoDomainPrefix"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;userPool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addDomain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UserPoolForAgentCoreMCPDomain"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserPoolDomainOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cognitoDomain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CognitoDomainOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;domainPrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cognitoDomainPrefix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And re-run the command: &lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-user-client-pool-stack  -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;br&gt;
When AWS has fixed the issue with the domain prefixes, we can completely remove this workaround. We also won't need to deploy this stack separately. &lt;/p&gt;

&lt;p&gt;Now let's cover the last missing part - defining the IAM execution role.&lt;br&gt;
It's very difficult to automate this part as it takes plenty of time. If I find it, I'll provide the IaC part in the future :). I refer you to the article &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html" rel="noopener noreferrer"&gt;IAM Permissions for AgentCore Runtime&lt;/a&gt; for more information. You can also read my article &lt;a href="https://dev.to/aws-heroes/amazon-bedrock-agentcore-runtime-part-2-deploy-the-agent-with-the-agentcore-runtime-starter-3706"&gt;Amazon Bedrock AgentCore Runtime - Part 2 Using Bedrock AgentCore Runtime Starter Toolkit with Strands Agents SDK&lt;/a&gt;, where I explained this part. In that article, we developed the agent in Python with the Strands Agents framework and deployed it on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;Once we have defined the IAM role, we need to configure it in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/cdk.json" rel="noopener noreferrer"&gt;cdk.json&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mvn -e -q compile exec:java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{AWS_ACCOUNT_ID}:role/service-role/AmazonBedrockAgentCoreRuntimeDefaultServiceRole-q8xp1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the placeholder for the AWS account ID as explained above.  Here is the relevant code to grab the value of the &lt;em&gt;roleArnForTheAgentCoreRuntime&lt;/em&gt; variable and set it to the execution role of the Runtime from the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConventionalDefaults&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextVariableValueWithReplacedAccountId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"roleArnForTheAgentCoreRuntime"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRoleArn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"roleArnForTheAgentCoreRuntimeRole"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roleArnForTheAgentCoreRuntime&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MCPRuntime-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runtimeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;&lt;span class="o"&gt;)+&lt;/span&gt; &lt;span class="s"&gt;"_runtime"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;...&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executionRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're completely ready and can deploy the conference-search-agentcore-runtime-with-mcp-server-stack stack with: &lt;code&gt;cdk deploy spring-ai-conference-search-agentcore-runtime-with-mcp-server-stack  -c awsAccountId={YOUR_AWS_ACCOUNT_ID}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll need some values for the configuration of the (Spring AI) MCP client, which we'll cover in the next article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user pool name, user client pool name, and auth token resource server ID, which we defined as constants in the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/cognito/UserClientPoolStack.java" rel="noopener noreferrer"&gt;UserClientPoolStack&lt;/a&gt;. You can also see them in the output of the &lt;em&gt;cdk deploy&lt;/em&gt; command.&lt;/li&gt;
&lt;li&gt;agentcore runtime ID from the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/spring-ai-1.1-conference-app-bedrock-agentcore-cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeWithMCPStack.java" rel="noopener noreferrer"&gt;RuntimeWithMCPStack&lt;/a&gt;. This value will only be there after the deployment of this stack. You can also see it in the output of the &lt;em&gt;cdk deploy&lt;/em&gt; command (variable name &lt;em&gt;RuntimeIdOutput&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how the AgentCore Runtime looks in the console after its creation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtr9rix49cf970u37w61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtr9rix49cf970u37w61.png" alt=" " width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explained how to deploy and run our conference search application on the Amazon Bedrock AgentCore Runtime as the MCP server. In the next article, we'll develop the (MCP-) client, capable of talking to our application running on AgentCore Runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI and Amazon Bedrock AgentCore - Part 1 Introduction to the series</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 27 Apr 2026 12:16:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-1-introduction-to-the-series-phg</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-1-introduction-to-the-series-phg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to the series
&lt;/h2&gt;

&lt;p&gt;I've already started the article series &lt;a href="https://dev.to/vkazulkin/series/34771"&gt;Spring AI with Amazon Bedrock&lt;/a&gt;, where I've published articles to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-1-introduction-and-the-sample-application-4hof"&gt;Introduce to Spring AI&lt;/a&gt; and the sample application to search for technical conferences.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-2-exploring-model-context-protocol-stdio-transport-3o89"&gt;Explore Spring AI MCP Server with STDIO protocol&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-3-exploring-model-context-protocol-sse-transport-48ah"&gt;Explore Spring AI MCP Server with SSE protocol&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-4-exploring-model-context-protocol-streamable-http-transport-2o5h"&gt;Explore Spring AI MCP Server with Streamable HTTP protocol&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-5-spring-ai-meets-amazon-bedrock-agentcore-2n6n"&gt;Explore Amazon Bedrock AgentCore with Spring AI&lt;/a&gt;. Here, I used another sample application, which I implemented in Python with the Strands Agents framework and ported the parts of it to Spring AI. I deployed the application on the AgentCore Runtime. The application also used Spring AI MCP Client to talk to the AgentCore Gateway. It acts as the managed MCP server to expose the functionality provided by another application as MCP-compatible tools. This application stores and retrieves orders, which I implemented with API Gateway, Lambda, and Aurora DSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why did I decide to start a separate article series to cover building AI Agents with Spring AI and Amazon Bedrock AgentCore if the last-mentioned article also covers it? Because there is more to cover and to be released by AWS and Broadcom (the company behind the Spring development). I'll use the conference application example to cover the following topics in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Deploy Spring AI MCP server on AgentCore Runtime with MCP protocol instead of locally, as mentioned in the examples above. &lt;/li&gt;
&lt;li&gt;Discuss the use case where it's more beneficial to deploy the MCP server on AgentCore Gateway instead of AgentCore Runtime.&lt;/li&gt;
&lt;li&gt; Use Spring AI MCP client to talk to the MCP server deployed on AgentCore Runtime and AgentCore Gateway. This MCP client itself can be deployed either locally or on the AgentCore Runtime.&lt;/li&gt;
&lt;li&gt; Explore the functionality of the &lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore" rel="noopener noreferrer"&gt;Spring AI AgentCore&lt;/a&gt;. It provides easier integration between Spring AI and the Amazon Bedrock AgentCore services, such as the Runtime, but also 
&lt;a href="https://github.com/spring-ai-community/spring-ai-agentcore/tree/main/spring-ai-agentcore-memory" rel="noopener noreferrer"&gt;short-term, long-term, and episodic AgentCory Memory&lt;/a&gt;. In my example to &lt;a href="https://dev.to/aws-heroes/spring-ai-with-amazon-bedrock-part-5-spring-ai-meets-amazon-bedrock-agentcore-2n6n"&gt;explore Amazon Bedrock AgentCore with Spring AI&lt;/a&gt;, I didn't use AgentCore Memory at all, as such integration was an immense effort. With Spring AI AgentCore Memory, this integration becomes much easier.&lt;/li&gt;
&lt;li&gt; I'll rewrite the agent using &lt;a href="https://github.com/embabel/embabel-agent" rel="noopener noreferrer"&gt;Embabel&lt;/a&gt;, which builds upon Spring AI. Embabel is a framework for authoring agentic flows on the JVM that seamlessly mix LLM-prompted interactions with code and domain models. Supports intelligent path finding towards goals.&lt;/li&gt;
&lt;li&gt;How to set up so-called collector-less &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-configure.html" rel="noopener noreferrer"&gt;Observability for the Amazon Bedrock AgentCore resources&lt;/a&gt; using &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLP-UsingADOT.html" rel="noopener noreferrer"&gt;AWS Distro for OpenTelemetry (ADOT) SDK&lt;/a&gt; for Spring AI applications hosted in AgentCore Runtime.&lt;/li&gt;
&lt;li&gt;Last but not least, I'll update the examples to use Java 25, Spring Boot 4, and the recent Spring AI version. There is a Spring AI 1.1* branch for Spring Boot versions before Spring Boot 4. And there is the Spring AI 2.x branch for Spring Boot 4 applications. The last one is currently in development and not GA. When it's released, I'll also provide my examples for the Spring AI 2.x version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can already preview some of my examples, where I'll cover the above-mentioned topics in my &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai" rel="noopener noreferrer"&gt;amazon-bedrock-agentcore-spring-ai&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>springai</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and Aurora DSQL - Part 6 Using GraalVM Native Image</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Wed, 22 Apr 2026 14:02:36 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-6-34ni</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-6-34ni</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt;, we introduced our sample application. In parts 2-5, we measured Lambda function performance using different approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;without activation of Lambda SnapStart&lt;/li&gt;
&lt;li&gt;with activation of Lambda SnapStart, but without using any priming techniques&lt;/li&gt;
&lt;li&gt;with activation of Lambda SnapStart and using different priming techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We observed that by activating the SnapSart and applying different priming techniques, we could significantly further reduce the Lambda cold start times. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could significantly reduce the maximal value for the Lambda warm start times.&lt;/p&gt;

&lt;p&gt;In this article, we'll introduce another approach to improve the performance of the Lambda function - GraalVM Native Image.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;This article assumes prior knowledge of GraalVM and its native image capabilities. For a concise overview of them and how to get both installed, please refer to the following articles: &lt;a href="https://www.graalvm.org/latest/introduction/" rel="noopener noreferrer"&gt;Introduction to GraalVM&lt;/a&gt;, &lt;a href="https://www.graalvm.org/22.2/docs/introduction/" rel="noopener noreferrer"&gt;GraalVM Architecture&lt;/a&gt;, and &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/" rel="noopener noreferrer"&gt;GraalVM Native Image&lt;/a&gt; or read my article &lt;a href="https://dev.to/aws-builders/lambda-function-with-graalvm-native-image-part-1-introduction-to-graalvm-and-its-native-image-capabilities-5d17"&gt;Introduction to GraalVM and its native image capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To install GraalVM and native image, please follow the instructions in the article &lt;a href="https://www.graalvm.org/latest/getting-started/#installing" rel="noopener noreferrer"&gt;Installing GraalVM&lt;/a&gt;. In my example, I used the 25.0.2-graal version, but you can use the newest one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with JDBC and Hikari connection pool using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-aurora-dsql" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt;, but adjust it. The goal is to be able to build GraalVM Native Image and deploy it on AWS Lambda as a Custom Runtime.&lt;/p&gt;

&lt;p&gt;Here is the final version of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-aurora-dsql-as-graalvm-native-image&lt;/a&gt; application.&lt;/p&gt;

&lt;p&gt;Let's go step-by-step through the changes compared to the initial application from part 1. The business logic (Entity, ProductDao, and the Lambda handlers) remains completely the same.  All changes are made in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;, &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;, and by providing additional GraalVM configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sample application GraalVM Native Image capable
&lt;/h2&gt;

&lt;p&gt;For our sample application to run as a GraalVM Native Image, we need to declare all classes whose objects will be instantiated by reflection. These classes needed to be known by the AOT compiler at compile time. This happens in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/main/reflect-config.json" rel="noopener noreferrer"&gt;reflect-config.json&lt;/a&gt;.  As we can see, we need to declare there the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; all our Lambda functions like GetProductByIdHandler] and CreateProductHandler&lt;/li&gt;
&lt;li&gt; entities like Product that Jackson converts from JSON payload and back&lt;/li&gt;
&lt;li&gt; APIGatewayProxyRequestEvent and all its inner classes because we declared this event type as a request event in our Lambda functions, like GetProductByIdHandler and CreateProductHandler&lt;/li&gt;
&lt;li&gt;org.joda.time.DateTime, which will be used to convert a timestamp from a string and back. Such a timestamp is a part of the API Gateway proxy request and response events. In my opinion, it's time to switch from  &lt;a href="https://www.joda.org/joda-time/" rel="noopener noreferrer"&gt;Joda-Time&lt;/a&gt; to the Java Date/Time API for this.&lt;/li&gt;
&lt;li&gt;classes for Hikari Connection Pool creation (HikariConfig, HikariDataSource, ConcurrentBag$IConcurrentBagEntry[])&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are multiple ways, how, and where to define GraalVM Native configuration, like reflection configuration. For this, I refer you to the article &lt;a href="https://www.graalvm.org/22.2/reference-manual/native-image/guides/build-with-reflection/" rel="noopener noreferrer"&gt;Build a Native Executable with Reflection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We used these ways to define reflection configuration for the dependencies in use : &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/native-image/org.postgresql/postgresql/reflect-config.json" rel="noopener noreferrer"&gt;postgresql&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/native-image/software.amazon.dsql/aurora-dsql-jdbc-connector/reflect-config.json" rel="noopener noreferrer"&gt;aurora-dsql-jdbc-connector&lt;/a&gt;. Many providers of the open source libraries ship this configuration for the GraalVM metadata directly. Unfortunately, not all of them do it, so we don't need to define it on our own.&lt;/p&gt;

&lt;p&gt;To avoid errors with Loggers during the initialization described in the article &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/ClassInitialization/" rel="noopener noreferrer"&gt;Class Initialization in Native Image&lt;/a&gt;, we need to add GraalVM Native Image build arguments in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/native-image/org.slf4j/slf4j-simple/native-image.properties" rel="noopener noreferrer"&gt;native-image.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;--allow-incomplete-classpath &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="s"&gt;--initialize-at-build-time=org.slf4j.simple.SimpleLogger,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;      &lt;span class="s"&gt;org.slf4j.LoggerFactory&lt;/span&gt;
    &lt;span class="err"&gt;--&lt;/span&gt; &lt;span class="py"&gt;--trace-class-initialization&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;org.slf4j.simple.SimpleLogger,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;      &lt;span class="s"&gt;org.slf4j.LoggerFactory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native-image.properties should be placed in the META-INF/native-image/${MavenGroupIid}/${MavenArtifactId}.&lt;/p&gt;

&lt;p&gt;As we use slf4j-simple Logger in our application, we need to place native-image.properties in the path META-INF/native-image/org.slf4j/slf4j-simple.&lt;br&gt;
If you use another Logger implementation (e.g., log4j or logback), you need to adjust this file and place it accordingly.&lt;/p&gt;

&lt;p&gt;Also, by default, no resources (schemas, services, and others) will be a part of the native image. We need to define them manually. There are multiple ways to &lt;a href="https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/Resources/" rel="noopener noreferrer"&gt;Include Resources in Native Image &lt;/a&gt;. We have to place them in the resource-config.json file in the META-INF/native-image/${MavenGroupIid}/${MavenArtifactId}. We need it to include the file containing the correct implementation of the java.sql.Driver. In our case, it's aurora-dsql-jdbc-connector. We defined it in the following &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/native-image/software.amazon.dsql/aurora-dsql-jdbc-connector/resource-config.json" rel="noopener noreferrer"&gt;resource-config.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To save the manual work of defining all this metadata, you can use &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/" rel="noopener noreferrer"&gt;GraalVM Tracing Agent&lt;/a&gt; to generate it for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lambda Custom Runtime
&lt;/h2&gt;

&lt;p&gt;There is no managed GraalVM (Native Image) on AWS Lambda. To deploy the native image on AWS Lambda, we need a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html" rel="noopener noreferrer"&gt;custom runtime&lt;/a&gt;. For this, we need to package everything into a file with a &lt;strong&gt;.zip&lt;/strong&gt; extension, which includes the file with the name &lt;strong&gt;bootstrap&lt;/strong&gt;. This file can either be the GraalVM Native Image or contain instructions on how to invoke the GraalVM Native Image placed in another file. We'll use the latter way; let's explore it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll build GraalVM Native Image automatically in the package phase defined in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;. The relevant part is defined in the following plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.graalvm.nativeimage&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;native-image-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;21.2.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;native-image&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;skip&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/skip&amp;gt;&lt;/span&gt;              
       &lt;span class="nt"&gt;&amp;lt;mainClass&amp;gt;&lt;/span&gt;
           com.formkiq.lambda.runtime.graalvm.LambdaRuntime
       &lt;span class="nt"&gt;&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;imageName&amp;gt;&lt;/span&gt;
           aws-lambda-java-25-with-aurora-dsql-as-graalvm-native-image 
       &lt;span class="nt"&gt;&amp;lt;/imageName&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;buildArgs&amp;gt;&lt;/span&gt;
         --no-fallback
         --enable-http
         -H:ReflectionConfigurationFiles=../src/main/reflect-config.json
      &lt;span class="nt"&gt;&amp;lt;/buildArgs&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;strong&gt;native-image-maven-plugin&lt;/strong&gt; from &lt;em&gt;org.graalvm.nativeimage&lt;/em&gt; tools and execute native-image in the package phase. You can also use the alternative &lt;a href="https://graalvm.github.io/native-build-tools/latest/maven-plugin.html" rel="noopener noreferrer"&gt;native-maven-plugin&lt;/a&gt; plugin, whose configuration is very similar. This plugin requires the definition of the main class, which a Lambda function doesn't have. That's why we use &lt;a href="https://github.com/formkiq/lambda-runtime-graalvm" rel="noopener noreferrer"&gt;Lambda Runtime GraalVM&lt;/a&gt; and define its main class &lt;em&gt;com.formkiq.lambda.runtime.graalvm.LambdaRuntime&lt;/em&gt;. Lambda Runtime GraalVM is a Java library that makes it easy to convert AWS Lambda written in the Java programming language to the GraalVM. We defined it previously in pom.xml as a dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.formkiq&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;lambda-runtime-graalvm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.6.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then give the native image name &lt;strong&gt;aws-lambda-java-25-with-aurora-dsql-as-graalvm-native-image&lt;/strong&gt; (the default one will also be an artifact name). After it, we include some GraalVM Native Image arguments and previously defined &lt;strong&gt;reflection-config&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;buildArgs&amp;gt;&lt;/span&gt;
    --no-fallback
    --enable-http
   -H:ReflectionConfigurationFiles=../src/main/reflect-config.json
&lt;span class="nt"&gt;&amp;lt;/buildArgs&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To zip the built GraalVM Native Image as function.zip required by Lambda Custom Runtime, we use the maven-assembly plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-assembly-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;native-zip&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;single&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;inherited&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/inherited&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;finalName&amp;gt;&lt;/span&gt;function&lt;span class="nt"&gt;&amp;lt;/finalName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;appendAssemblyId&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/appendAssemblyId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;descriptors&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;descriptor&amp;gt;&lt;/span&gt;src/assembly/native.xml&lt;span class="nt"&gt;&amp;lt;/descriptor&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/descriptors&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;finalName&lt;/em&gt; is the name of the zip file, in our case, &lt;strong&gt;function&lt;/strong&gt;. We also include &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/assembly/native.xml" rel="noopener noreferrer"&gt;native.xml&lt;/a&gt; descriptor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;assembly&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;native-zip&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formats&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;format&amp;gt;&lt;/span&gt;zip&lt;span class="nt"&gt;&amp;lt;/format&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/formats&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;baseDirectory/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fileSets&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;src/shell/native&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;useDefaultExcludes&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/useDefaultExcludes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0775&lt;span class="nt"&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;includes&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;include&amp;gt;&lt;/span&gt;bootstrap&lt;span class="nt"&gt;&amp;lt;/include&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/includes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;target&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;useDefaultExcludes&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/useDefaultExcludes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0775&lt;span class="nt"&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;includes&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;include&amp;gt;&lt;/span&gt;aws-lambda-java-25-with-aurora-dsql-as-graalvm-native-image&lt;span class="nt"&gt;&amp;lt;/include&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/includes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fileSets&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/assembly&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This descriptor defines what files from which directories with what permissions will be added to the zip as an assembly format. &lt;em&gt;fileMode&lt;/em&gt; equal to &lt;strong&gt;0775&lt;/strong&gt; means it has permission to be executable on the Linux operating system.  We include previously built GraalVM Native Image with the name &lt;strong&gt;aws-lambda-java-25-with-aurora-dsql-as-graalvm-native-image&lt;/strong&gt; there. We also include the already defined &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/src/shell/native/bootstrap" rel="noopener noreferrer"&gt;bootstrap&lt;/a&gt; file, which basically invokes the GraalVM Native Image :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LAMBDA_TASK_ROOT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

./aws-lambda-java-25-with-aurora-dsql-as-graalvm-native-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, we have to build GraalVM Native Image packaged as a zip file, which can be built as a Lambda Custom Runtime with &lt;code&gt;mvn clean package&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying GraalVM Native Image as a Lambda Custom Runtime
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;, we set the Lambda runtime as &lt;strong&gt;provided.al2023&lt;/strong&gt;, which is the newest version of the &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/lambda.html" rel="noopener noreferrer"&gt;custom runtime&lt;/a&gt;, and provide the path to the previously built GraalVM Native Image as target/function.zip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;target/function.zip&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;provided.al2023&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to deploy our application with &lt;code&gt;sam deploy -g&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with JDBC and Hikari connection pool using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;GetProductById&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdHandler.java" rel="noopener noreferrer"&gt;GetProductByIdHandler&lt;/a&gt;. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEDQGVNI25" https://{$API_GATEWAY_URL}/prod/products/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with provided:al2023.v124 version, and the deployed artifact size of this application was 20.459 KB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda Custom Runtime with GraalVM Native Image&lt;/td&gt;
&lt;td&gt;629&lt;/td&gt;
&lt;td&gt;802&lt;/td&gt;
&lt;td&gt;885&lt;/td&gt;
&lt;td&gt;943&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;946&lt;/td&gt;
&lt;td&gt;4.65&lt;/td&gt;
&lt;td&gt;5.16&lt;/td&gt;
&lt;td&gt;5.64&lt;/td&gt;
&lt;td&gt;9.16&lt;/td&gt;
&lt;td&gt;136.50&lt;/td&gt;
&lt;td&gt;668&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sample application with Hibernate and Hikari connection pool and using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;The same is true for the sample application using JDBC instead of Hibernate. We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-hibernate-aurora-dsql" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt;, but adjust it. The goal is to be able to build GraalVM Native Image and deploy it on AWS Lambda as a Custom Runtime.&lt;/p&gt;

&lt;p&gt;Here is the final version of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image&lt;/a&gt; application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sample application GraalVM Native Image capable
&lt;/h2&gt;

&lt;p&gt;Many steps (creating a native image zip, building, and deploying) described above are implemented in exactly the same way. Additional complexity comes from using the Hibernate framework, which doesn't ship native image metadata. Also, Hibernate comes with a lot of internal loggers, which usually don't play well with the native image. It took me a lot of time to figure out the required GraalVM metadata (native-image.properties and reflect.json). You can review them &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/native-image/org.hibernate.orm" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I also had to include the whole package &lt;em&gt;org.hibernate.event.spi&lt;/em&gt; into the native image, see &lt;code&gt;-H:Preserve=package=org.hibernate.event.spi&lt;/code&gt; in the native image configuration in the pom.xml.&lt;/p&gt;

&lt;p&gt;It's probable that if you update the Hibernate version in use, something will break. And as a result, you'll need to adjust the native image metadata, for example, by re-running the &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/" rel="noopener noreferrer"&gt;GraalVM Tracing Agent&lt;/a&gt; to generate them for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;The biggest challenge was the fact that Hibernate uses &lt;a href="https://bytebuddy.net/" rel="noopener noreferrer"&gt;Byte Buddy&lt;/a&gt;. Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application, without the help of a compiler. This obviously doesn't work at runtime in the GraalVM native image. Hibernate also ships no-op &lt;em&gt;org.hibernate.bytecode.internal.none.BytecodeProviderImpl&lt;/em&gt;, but for many versions, it doesn't allow its configuration from outside. The Byte Buddy implementation was always found in the classpath and broke at runtime. Frameworks like Quarkus and Spring Boot can do this magic with the AOT, but as I don't use them, I was seeking the possibilities of how to solve it. Please, read my &lt;a href="https://discourse.hibernate.org/t/how-to-correctly-disable-byte-buddy-for-graalvm-native-image/12163/6" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; in the Hibernate forum.&lt;/p&gt;

&lt;p&gt;Even if I'm not very happy with the solution, which feels more like a workaround, I'll describe how I solved it. &lt;/p&gt;

&lt;p&gt;I first defined &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider" rel="noopener noreferrer"&gt;org.hibernate.bytecode.spi.BytecodeProvider&lt;/a&gt; with a no-op bytecode implementation in my application. Then I built the uber-jar with maven-shade-plugin. With that, I saw that it, of course, flattens META-INF/services and puts all service files into one shared META-INF/services folder, so that only one file per service can be there. And it indeed by default puts the org.hibernate.bytecode.spi.BytecodeProvider from my application with a no-op implementation, which I defined there. But even if it would put the ByteBuddy implementation from the &lt;em&gt;hibernate-core&lt;/em&gt; dependency, I could use the filtering feature of the maven-shade-plugin to exclude it in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;filters&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;filter&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifact&amp;gt;&lt;/span&gt;*:*&lt;span class="nt"&gt;&amp;lt;/artifact&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;excludes&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;exclude&amp;gt;&lt;/span&gt;META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider
      &lt;span class="nt"&gt;&amp;lt;/exclude&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/excludes&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/filters&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then also need to include the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/src/main/resources/META-INF/services/org.hibernate.bytecode.spi.BytecodeProvider" rel="noopener noreferrer"&gt;no-op byte implementation&lt;/a&gt; in the  &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/src/main/resource-config.json" rel="noopener noreferrer"&gt;resource-config.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This will then default to no-op implementation if ServiceLoader doesn’t find any implementation.  Now, to build the GraalVM Native Image from the uber-jar, I had to use the &lt;em&gt;native-maven-plugin&lt;/em&gt; Maven plugin &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt; because it supports classpath definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.graalvm.buildtools&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;native-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   ....
   &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;classpath&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;param&amp;gt;&lt;/span&gt;         
             ${project.build.directory}/${project.artifactId}-${project.version}.jar
      &lt;span class="nt"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;/classpath&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
 ....
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the native-maven-plugin Maven plugin configuration looks in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt; with all changes that I described above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.graalvm.buildtools&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;native-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.11.4&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mainClass&amp;gt;&lt;/span&gt;com.formkiq.lambda.runtime.graalvm.LambdaRuntime&lt;span class="nt"&gt;&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;imageName&amp;gt;&lt;/span&gt;aws-lambda-java-25-with-hibernate-and-aurora-dsql-as-graalvm-native-image&lt;span class="nt"&gt;&amp;lt;/imageName&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;buildArgs&amp;gt;&lt;/span&gt;
             &lt;span class="nt"&gt;&amp;lt;buildArg&amp;gt;&lt;/span&gt; --no-fallback &lt;span class="nt"&gt;&amp;lt;/buildArg&amp;gt;&lt;/span&gt;
             &lt;span class="nt"&gt;&amp;lt;buildArg&amp;gt;&lt;/span&gt; --enable-http &lt;span class="nt"&gt;&amp;lt;/buildArg&amp;gt;&lt;/span&gt;
             &lt;span class="nt"&gt;&amp;lt;buildArg&amp;gt;&lt;/span&gt; -H:ReflectionConfigurationFiles=src/main/reflect-config.json &lt;span class="nt"&gt;&amp;lt;/buildArg&amp;gt;&lt;/span&gt;
             &lt;span class="nt"&gt;&amp;lt;buildArg&amp;gt;&lt;/span&gt; -H:ResourceConfigurationFiles=src/main/resource-config.json &lt;span class="nt"&gt;&amp;lt;/buildArg&amp;gt;&lt;/span&gt;
             &lt;span class="nt"&gt;&amp;lt;buildArg&amp;gt;&lt;/span&gt; -H:Preserve=package=org.hibernate.event.spi &lt;span class="nt"&gt;&amp;lt;/buildArg&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/buildArgs&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;classpath&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;param&amp;gt;&lt;/span&gt;                   
    ${project.build.directory}/${project.artifactId}-${project.version}.jar
          &lt;span class="nt"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/classpath&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
  ....      
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with Hibernate and Hikari connection pool using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;GetProductById&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql-as-graalvm-native-image/src/main/java/software/amazonaws/example/product/handler/GetProductByIdHandler.java" rel="noopener noreferrer"&gt;GetProductByIdHandler&lt;/a&gt;. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEHDQGVNI25" https://{$API_GATEWAY_URL}/prod/products/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with provided:al2023.v124 version, and the deployed artifact size of this application was 33.377 KB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda Custom Runtime with GraalVM Native Image&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;995&lt;/td&gt;
&lt;td&gt;1115&lt;/td&gt;
&lt;td&gt;1192&lt;/td&gt;
&lt;td&gt;1214&lt;/td&gt;
&lt;td&gt;1215&lt;/td&gt;
&lt;td&gt;4.69&lt;/td&gt;
&lt;td&gt;5.04&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;9.38&lt;/td&gt;
&lt;td&gt;38.76&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of the series, we first introduced GraalVM Native Image. Then we explained step-by-step how to convert the sample applications to those where the native image can be built. When we deployed the native image on AWS Lambda using the Lambda Custom Runtime. Finally, we measured the performance of the Lambda function. We observed that the cold and warm start times vary compared to using the Lambda SnapStart. Sometimes GraalVM, sometimes SnapStart provides better performance. Sometimes it depends on which priming techniques we use, see the measurements table in &lt;a href="https://dev.tourl"&gt;part 5&lt;/a&gt;. On the other hand, creating a Native Image is not for free, as we need to scale CI/CD pipeline to build a native image. Building it requires many GBs of memory, and the process takes many minutes depending on the hardware. Creating a full set of the Native Image metadata, even using GraalVM Tracing Agent, means introducing additional complexity. Lambda SnapStart, on the other hand, is fully managed. Especially if we use Hibernate in our application without using any frameworks (Spring Boot, Quarkus) on top, it makes things very complicated. That's why I'd advocate against using Hibernate alone in such scenarios. &lt;/p&gt;

&lt;p&gt;We'll compare both approaches in one of the next articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36298"&gt;series&lt;/a&gt; where I use a NoSQL serverless &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; database instead of Aurora DSQL to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>java</category>
      <category>graalvm</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 6 Using GraalVM Native Image</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 20 Apr 2026 16:31:10 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-6-using-1ji</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-6-using-1ji</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt;, we introduced our sample application. In parts 2-5, we measured Lambda function performance using different approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;without activation of Lambda SnapStart&lt;/li&gt;
&lt;li&gt;with activation of Lambda SnapStart, but without using any priming techniques&lt;/li&gt;
&lt;li&gt;with activation of Lambda SnapStart and using different priming techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We observed that by activating the SnapSart and applying different priming techniques, we could significantly further reduce the Lambda cold start times. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could significantly reduce the maximal value for the Lambda warm start times.&lt;/p&gt;

&lt;p&gt;In this article, we'll introduce another approach to improve the performance of the Lambda function - GraalVM Native Image.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;This article assumes prior knowledge of GraalVM and its native image capabilities. For a concise overview of them and how to get both installed, please refer to the following articles: &lt;a href="https://www.graalvm.org/latest/introduction/" rel="noopener noreferrer"&gt;Introduction to GraalVM&lt;/a&gt;, &lt;a href="https://www.graalvm.org/22.2/docs/introduction/" rel="noopener noreferrer"&gt;GraalVM Architecture&lt;/a&gt;, and &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/" rel="noopener noreferrer"&gt;GraalVM Native Image&lt;/a&gt; or read my article &lt;a href="https://dev.to/aws-builders/lambda-function-with-graalvm-native-image-part-1-introduction-to-graalvm-and-its-native-image-capabilities-5d17"&gt;Introduction to GraalVM and its native image capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To install GraalVM and native image, please follow the instructions in the article &lt;a href="https://www.graalvm.org/latest/getting-started/#installing" rel="noopener noreferrer"&gt;Installing GraalVM&lt;/a&gt;. In my example, I used the 25.0.2-graal version, but you can use the newest one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-dynamodb" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt;, but adjust it. The goal is to be able to build GraalVM Native Image and deploy it on AWS Lambda as a Custom Runtime.&lt;/p&gt;

&lt;p&gt;Here is the final version of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-dynamodb-as-graalvm-native-image&lt;/a&gt; application.&lt;/p&gt;

&lt;p&gt;Let's go step-by-step through the changes compared to the initial application from part 1. The business logic (Entity, ProductDao, and the Lambda handlers) remains completely the same.  All changes are made in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;, &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;, and by providing additional GraalVM configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sample application GraalVM Native Image capable
&lt;/h2&gt;

&lt;p&gt;For our sample application to run as a GraalVM Native Image, we need to declare all classes whose objects will be instantiated by reflection. These classes needed to be known by the AOT compiler at compile time. This happens in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/src/main/reflect-config.json" rel="noopener noreferrer"&gt;reflect-config.json&lt;/a&gt;.  As we can see, we need to declare there the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; all our Lambda functions like GetProductByIdHandler] and CreateProductHandler&lt;/li&gt;
&lt;li&gt; entities like Product that Jackson converts from JSON payload and back&lt;/li&gt;
&lt;li&gt; APIGatewayProxyRequestEvent and all its inner classes because we declared this event type as a request event in our Lambda functions, like GetProductByIdHandler and CreateProductHandler&lt;/li&gt;
&lt;li&gt;org.joda.time.DateTime, which will be used to convert a timestamp from a string and back. Such a timestamp is a part of the API Gateway proxy request and response events. In my opinion, it's time to switch from  &lt;a href="https://www.joda.org/joda-time/" rel="noopener noreferrer"&gt;Joda-Time&lt;/a&gt; to the Java Date/Time API for this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are multiple ways, how, and where to define GraalVM Native configuration, like reflection configuration. For this, I refer you to the article &lt;a href="https://www.graalvm.org/22.2/reference-manual/native-image/guides/build-with-reflection/" rel="noopener noreferrer"&gt;Build a Native Executable with Reflection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To avoid errors with Loggers during the initialization described in the article &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/ClassInitialization/" rel="noopener noreferrer"&gt;Class Initialization in Native Image&lt;/a&gt;, we need to add GraalVM Native Image build arguments in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/src/main/resources/META-INF/native-image/org.slf4j/slf4j-simple/native-image.properties" rel="noopener noreferrer"&gt;native-image.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;--allow-incomplete-classpath &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="s"&gt;--initialize-at-build-time=org.slf4j.simple.SimpleLogger,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;      &lt;span class="s"&gt;org.slf4j.LoggerFactory&lt;/span&gt;
    &lt;span class="err"&gt;--&lt;/span&gt; &lt;span class="py"&gt;--trace-class-initialization&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;org.slf4j.simple.SimpleLogger,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;      &lt;span class="s"&gt;org.slf4j.LoggerFactory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native-image.properties should be placed in the META-INF/native-image/${MavenGroupIid}/${MavenArtifactId}&lt;/p&gt;

&lt;p&gt;As we use slf4j-simple Logger in our application, we need to place native-image.properties in the path META-INF/native-image/org.slf4j/slf4j-simple.&lt;br&gt;
If you use another Logger implementation (e.g., log4j or logback), you need to adjust this file and place it accordingly.&lt;/p&gt;

&lt;p&gt;To save the manual work of defining all this metadata, you can use &lt;a href="https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/" rel="noopener noreferrer"&gt;GraalVM Tracing Agent&lt;/a&gt; to generate it for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lambda Custom Runtime
&lt;/h2&gt;

&lt;p&gt;There is no managed GraalVM (Native Image) on AWS Lambda. To deploy the native image on AWS Lambda, we need a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html" rel="noopener noreferrer"&gt;custom runtime&lt;/a&gt;. For this, we need to package everything into a file with a &lt;strong&gt;.zip&lt;/strong&gt; extension, which includes the file with the name &lt;strong&gt;bootstrap&lt;/strong&gt;. This file can either be the GraalVM Native Image or contain instructions on how to invoke the GraalVM Native Image placed in another file. We'll use the latter way; let's explore it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll build GraalVM Native Image automatically in the package phase defined in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;. The relevant part is defined in the following plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.graalvm.nativeimage&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;native-image-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;21.2.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;native-image&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;skip&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/skip&amp;gt;&lt;/span&gt;              
       &lt;span class="nt"&gt;&amp;lt;mainClass&amp;gt;&lt;/span&gt;
           com.formkiq.lambda.runtime.graalvm.LambdaRuntime
       &lt;span class="nt"&gt;&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;imageName&amp;gt;&lt;/span&gt;
           aws-lambda-java-25-with-dynamodb-as-graalvm-native-image
       &lt;span class="nt"&gt;&amp;lt;/imageName&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;buildArgs&amp;gt;&lt;/span&gt;
         --no-fallback
         --enable-http
         -H:ReflectionConfigurationFiles=../src/main/reflect-config.json
      &lt;span class="nt"&gt;&amp;lt;/buildArgs&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;strong&gt;native-image-maven-plugin&lt;/strong&gt; from &lt;em&gt;org.graalvm.nativeimage&lt;/em&gt; tools and execute native-image in the package phase. You can also use the alternative &lt;a href="https://graalvm.github.io/native-build-tools/latest/maven-plugin.html" rel="noopener noreferrer"&gt;native-maven-plugin&lt;/a&gt; plugin, whose configuration is very similar. This plugin requires the definition of the main class, which a Lambda function doesn't have. That's why we use &lt;a href="https://github.com/formkiq/lambda-runtime-graalvm" rel="noopener noreferrer"&gt;Lambda Runtime GraalVM&lt;/a&gt; and define its main class &lt;em&gt;com.formkiq.lambda.runtime.graalvm.LambdaRuntime&lt;/em&gt;. Lambda Runtime GraalVM is a Java library that makes it easy to convert AWS Lambda written in the Java programming language to the GraalVM. We defined it previously in pom.xml as a dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.formkiq&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;lambda-runtime-graalvm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.6.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then give the native image name &lt;strong&gt;aws-lambda-java-25-with-dynamodb-as-graalvm-native-image&lt;/strong&gt; (the default one will also be an artifact name). After it, we include some GraalVM Native Image arguments and previously defined &lt;strong&gt;reflect-config&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;buildArgs&amp;gt;&lt;/span&gt;
    --no-fallback
    --enable-http
   -H:ReflectionConfigurationFiles=../src/main/reflect-config.json
&lt;span class="nt"&gt;&amp;lt;/buildArgs&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To zip the built GraalVM Native Image as function.zip required by Lambda Custom Runtime, we use the maven-assembly plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-assembly-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;native-zip&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;single&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;inherited&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/inherited&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;finalName&amp;gt;&lt;/span&gt;function&lt;span class="nt"&gt;&amp;lt;/finalName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;appendAssemblyId&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/appendAssemblyId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;descriptors&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;descriptor&amp;gt;&lt;/span&gt;src/assembly/native.xml&lt;span class="nt"&gt;&amp;lt;/descriptor&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/descriptors&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;finalName&lt;/em&gt; is the name of the zip file, in our case, &lt;strong&gt;function&lt;/strong&gt;. We also include &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/src/assembly/native.xml" rel="noopener noreferrer"&gt;native.xml&lt;/a&gt; descriptor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;assembly&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;native-zip&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formats&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;format&amp;gt;&lt;/span&gt;zip&lt;span class="nt"&gt;&amp;lt;/format&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/formats&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;baseDirectory/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fileSets&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;src/shell/native&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;useDefaultExcludes&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/useDefaultExcludes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0775&lt;span class="nt"&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;includes&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;include&amp;gt;&lt;/span&gt;bootstrap&lt;span class="nt"&gt;&amp;lt;/include&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/includes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;target&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;useDefaultExcludes&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/useDefaultExcludes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0775&lt;span class="nt"&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;includes&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;include&amp;gt;&lt;/span&gt;aws-lambda-java-25-with-dynamodb-as-graalvm-native-image&lt;span class="nt"&gt;&amp;lt;/include&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/includes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fileSets&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/assembly&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This descriptor defines what files from which directories with what permissions will be added to the zip as an assembly format. &lt;em&gt;fileMode&lt;/em&gt; equal to &lt;strong&gt;0775&lt;/strong&gt; means it has permission to be executable on the Linux operating system.  We include previously built GraalVM Native Image with the name &lt;strong&gt;aws-lambda-java-25-with-dynamodb-as-graalvm-native-image&lt;/strong&gt; there. We also include the already defined &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/src/shell/native/bootstrap" rel="noopener noreferrer"&gt;bootstrap&lt;/a&gt; file, which basically invokes the GraalVM Native Image :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LAMBDA_TASK_ROOT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

./aws-lambda-java-25-with-dynamodb-as-graalvm-native-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, we have to build GraalVM Native Image packaged as a zip file, which can be built as a Lambda Custom Runtime with &lt;code&gt;mvn clean package&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying GraalVM Native Image as a Lambda Custom Runtime
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;, we set the Lambda runtime as &lt;strong&gt;provided.al2023&lt;/strong&gt;, which is the newest version of the &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/lambda.html" rel="noopener noreferrer"&gt;custom runtime&lt;/a&gt;, and provide the path to the previously built GraalVM Native Image as target/function.zip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;target/function.zip&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;provided.al2023&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to deploy our application with &lt;code&gt;sam deploy -g&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application using GraalVM Native Image
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/template.yaml" rel="noopener noreferrer"&gt;GetProductById&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-graalvm-native-image/src/main/java/software/amazonaws/example/product/handler/GetProductByIdHandler.java" rel="noopener noreferrer"&gt;GetProductByIdHandler&lt;/a&gt;. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEVDDBGVNI25" https://{$API_GATEWAY_URL}/prod/products/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with provided:al2023.v124 version, and the deployed artifact size of this application was 25.186 KB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda Custom Runtime with GraalVM Native Image&lt;/td&gt;
&lt;td&gt;559&lt;/td&gt;
&lt;td&gt;568&lt;/td&gt;
&lt;td&gt;593&lt;/td&gt;
&lt;td&gt;692&lt;/td&gt;
&lt;td&gt;739&lt;/td&gt;
&lt;td&gt;739&lt;/td&gt;
&lt;td&gt;3.84&lt;/td&gt;
&lt;td&gt;4.23&lt;/td&gt;
&lt;td&gt;4.88&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;55.92&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of the series, we first introduced GraalVM Native Image. Then we explained step-by-step how to convert the sample application to one where the native image can be built. When we deployed the native image on AWS Lambda using the Lambda Custom Runtime. Finally, we measured the performance of the Lambda function. We observed that the cold and warm start times were lower compared to using the Lambda SnapStart, even with priming techniques, see the measurements table in &lt;a href="https://dev.tourl"&gt;part 5&lt;/a&gt;. On the other hand, creating a Native Image is not for free, as we need to scale CI/CD pipeline to build a native image. Building it requires many GBs of memory, and the process takes many minutes depending on the hardware. Creating a full set of the Native Image metadata, even using GraalVM Tracing Agent, means introducing additional complexity. Lambda SnapStart, on the other hand, is fully managed.&lt;/p&gt;

&lt;p&gt;We'll compare both approaches in one of the next articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36919"&gt;series&lt;/a&gt; where I use a relational serverless &lt;a href="https://aws.amazon.com/rds/aurora/dsql/" rel="noopener noreferrer"&gt;Amazon Aurora DSQL&lt;/a&gt; database and additionally the &lt;a href="https://hibernate.org/" rel="noopener noreferrer"&gt;Hibernate ORM framework&lt;/a&gt; instead of DynamoDB to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>graalvm</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and Aurora DSQL - Part 5 SnapStart and full priming</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:47:26 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-5-3dlj</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-5-3dlj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt;, we introduced our sample application. In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;, we measured the performance (cold and warm start times) of the Lambda function without any optimizations. We observed quite a large cold start time, especially if we use the Hibernate ORM framework. Using this framework also significantly increases the artifact size.  &lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;, we introduced AWS Lambda SnapStart as one of the approaches to reduce the cold start times of the Lambda function. We observed that by enabling the SnapStart on the Lambda function, the cold start time goes down significantly for both sample applications. &lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-4-1kg5"&gt;part 4&lt;/a&gt;, we introduced how to apply Lambda SnapStart priming technique, such as Aurora DSQL request priming. The goal was to even further improve the performance of our Lambda functions. We saw that by doing this kind of priming and writing some additional code, we could additionally reduce the Lambda cold start times compared to simply activating the SnapStart. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could also reduce the maximal value for the Lambda warm start times by preloading classes (as Java lazily loads classes when they are required for the first time) and doing some preinitialization work (by invoking the method to retrieve the product from the Aurora DSQL products table by its ID). Previously, all this happened once during the first warm execution of the Lambda function.&lt;/p&gt;

&lt;p&gt;In this article, we'll introduce another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). We'll then measure the Lambda performance by applying it and comparing the results with other already introduced approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with JDBC and Hikari connection pool and the enabled AWS Lambda SnapStart using full priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, please make sure that we have enabled Lambda SnapStart in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;template.yaml&lt;/a&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;SnapStart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApplyOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishedVersions&lt;/span&gt; 
    &lt;span class="s"&gt;....&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;JAVA_TOOL_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-XX:+TieredCompilation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-XX:TieredStopAtLevel=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about the concepts behind the Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; and about SnapStart runtime hooks (which we'll use again) in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce you to the API Gateway Request Event priming (or full priming for short). We implemented it in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetProductByIdWithFullPrimingHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; 
                 &lt;span class="nc"&gt;RequestHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdWithFullPrimingHandler&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getGlobalContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;beforeCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nc"&gt;LambdaEventSerializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serializerFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSystemClassLoader&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;                 
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockLambdaContext&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHttpMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPathParameters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterRestore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPathParameters&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionalProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                                 
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optionalProduct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I refer to &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-4-1kg5"&gt;part 4&lt;/a&gt; for the explanation about how the Lambda SnapStart runtime hooks work. Please read my article &lt;a href="https://dev.to/aws-heroes/aws-snapstart-part-27-using-insights-from-aws-lambda-profiler-extension-for-java-to-reduce-lambda-2a1i"&gt;Using insights from AWS Lambda Profiler Extension for Java to reduce Lambda cold starts&lt;/a&gt;, on how I came up with this idea. I also described in this article in detail why it is supposed to speed things up. Shortly speaking, we primed another expensive &lt;em&gt;LambdaEventSerializers.serializerFor&lt;/em&gt; invocation. It consists of class loading and expensive initialization logic, which I identified. By invoking &lt;em&gt;handleRequest&lt;/em&gt;, we fully prime this method invocation, which consists mainly of Aurora DSQL request priming introduced in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-4-1kg5"&gt;part 4&lt;/a&gt;. At the end, we also prime the &lt;em&gt;APIGatewayProxyResponseEvent&lt;/em&gt; object construction. &lt;/p&gt;

&lt;p&gt;In our example, we primed the APIGatewayProxyRequestEvent "get product by id equal to zero" request. This is enough to instantiate and initialize all we need, even if we'd like to invoke the "create product" request. This priming implementation is also a read request without any side effects. But if you'd like, for example, to prime a "create product" request, you can do it as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHttpMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{ 'id': 0, 'name': 'Print 10x13', 'price': 15 }"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use some artificial product ID like 0 or a negative one that isn't used in production. If you use API Gateway HTTP API instead of REST API (like in our example), you can use APIGatewayV2HTTPEvent instead of APIGatewayProxyRequestEvent to prime such a request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with JDBC and Hikari connection pool
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithDSQLAndFullPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEDQ25" https://{$API_GATEWAY_URL}/prod/productsWithFullPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will present the Lambda performance measurements with SnapStart being activated for all approx. 100 cold start times (labelled as &lt;em&gt;all&lt;/em&gt; in the table), but also for the last approx. 70 (labelled as &lt;em&gt;last 70&lt;/em&gt; in the table). With that, the effect of the snapshot tiered cache, which we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;, becomes visible to you.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart full priming, we'll also present the Lambda performance measurements from all previous parts.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 17.150 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;2336&lt;/td&gt;
&lt;td&gt;2453&lt;/td&gt;
&lt;td&gt;2827&lt;/td&gt;
&lt;td&gt;3026&lt;/td&gt;
&lt;td&gt;3131&lt;/td&gt;
&lt;td&gt;3132&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.29&lt;/td&gt;
&lt;td&gt;5.73&lt;/td&gt;
&lt;td&gt;8.88&lt;/td&gt;
&lt;td&gt;195.38&lt;/td&gt;
&lt;td&gt;531&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;970&lt;/td&gt;
&lt;td&gt;1058&lt;/td&gt;
&lt;td&gt;1705&lt;/td&gt;
&lt;td&gt;1726&lt;/td&gt;
&lt;td&gt;1734&lt;/td&gt;
&lt;td&gt;1735&lt;/td&gt;
&lt;td&gt;4.92&lt;/td&gt;
&lt;td&gt;5.33&lt;/td&gt;
&lt;td&gt;5.86&lt;/td&gt;
&lt;td&gt;9.84&lt;/td&gt;
&lt;td&gt;198.52&lt;/td&gt;
&lt;td&gt;1134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;901&lt;/td&gt;
&lt;td&gt;960&lt;/td&gt;
&lt;td&gt;1061&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.29&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;196.94&lt;/td&gt;
&lt;td&gt;719&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, all&lt;/td&gt;
&lt;td&gt;879&lt;/td&gt;
&lt;td&gt;980&lt;/td&gt;
&lt;td&gt;1499&lt;/td&gt;
&lt;td&gt;1515&lt;/td&gt;
&lt;td&gt;1518&lt;/td&gt;
&lt;td&gt;1518&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;163.96&lt;/td&gt;
&lt;td&gt;914&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, last 70&lt;/td&gt;
&lt;td&gt;803&lt;/td&gt;
&lt;td&gt;912&lt;/td&gt;
&lt;td&gt;996&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;152.61&lt;/td&gt;
&lt;td&gt;597&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, all&lt;/td&gt;
&lt;td&gt;543&lt;/td&gt;
&lt;td&gt;625&lt;/td&gt;
&lt;td&gt;1306&lt;/td&gt;
&lt;td&gt;1411&lt;/td&gt;
&lt;td&gt;1433&lt;/td&gt;
&lt;td&gt;1434&lt;/td&gt;
&lt;td&gt;4.77&lt;/td&gt;
&lt;td&gt;5.20&lt;/td&gt;
&lt;td&gt;5.68&lt;/td&gt;
&lt;td&gt;9.16&lt;/td&gt;
&lt;td&gt;159.36&lt;/td&gt;
&lt;td&gt;864&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, last 70&lt;/td&gt;
&lt;td&gt;526&lt;/td&gt;
&lt;td&gt;578&lt;/td&gt;
&lt;td&gt;885&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;4.77&lt;/td&gt;
&lt;td&gt;5.16&lt;/td&gt;
&lt;td&gt;5.64&lt;/td&gt;
&lt;td&gt;9.24&lt;/td&gt;
&lt;td&gt;146.93&lt;/td&gt;
&lt;td&gt;560&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sample application with Hibernate and Hikari connection pool and the enabled AWS Lambda SnapStart using full priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-hibernate-aurora-dsql/" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We implemented the full priming in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; class. &lt;/p&gt;

&lt;p&gt;The explanation of what we would like to achieve and how is exactly the same as in the first sample application above. And the code itself looks the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with Hibernate and Hikari connection pool
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithHibernateAndDSQLAndFullPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEHADQ25" https://{$API_GATEWAY_URL}/prod/productsWithFullPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Please read &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; for the description of how we designed the experiment.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart full priming, we'll also present the Lambda performance measurements from all previous parts.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 42.333 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;6243&lt;/td&gt;
&lt;td&gt;6625&lt;/td&gt;
&lt;td&gt;7056&lt;/td&gt;
&lt;td&gt;8480&lt;/td&gt;
&lt;td&gt;8651&lt;/td&gt;
&lt;td&gt;8658&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;5.96&lt;/td&gt;
&lt;td&gt;6.50&lt;/td&gt;
&lt;td&gt;9.77&lt;/td&gt;
&lt;td&gt;200.10&lt;/td&gt;
&lt;td&gt;707&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;1277&lt;/td&gt;
&lt;td&gt;1360&lt;/td&gt;
&lt;td&gt;3050&lt;/td&gt;
&lt;td&gt;3103&lt;/td&gt;
&lt;td&gt;3200&lt;/td&gt;
&lt;td&gt;3201&lt;/td&gt;
&lt;td&gt;5.50&lt;/td&gt;
&lt;td&gt;6.01&lt;/td&gt;
&lt;td&gt;6.45&lt;/td&gt;
&lt;td&gt;10.16&lt;/td&gt;
&lt;td&gt;196.94&lt;/td&gt;
&lt;td&gt;2349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;1258&lt;/td&gt;
&lt;td&gt;1320&lt;/td&gt;
&lt;td&gt;1437&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.40&lt;/td&gt;
&lt;td&gt;10.08&lt;/td&gt;
&lt;td&gt;195.94&lt;/td&gt;
&lt;td&gt;1093&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, all&lt;/td&gt;
&lt;td&gt;1030&lt;/td&gt;
&lt;td&gt;1185&lt;/td&gt;
&lt;td&gt;2310&lt;/td&gt;
&lt;td&gt;2341&lt;/td&gt;
&lt;td&gt;2345&lt;/td&gt;
&lt;td&gt;2347&lt;/td&gt;
&lt;td&gt;5.33&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.50&lt;/td&gt;
&lt;td&gt;11.64&lt;/td&gt;
&lt;td&gt;201.70&lt;/td&gt;
&lt;td&gt;1607&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, last 70&lt;/td&gt;
&lt;td&gt;970&lt;/td&gt;
&lt;td&gt;1076&lt;/td&gt;
&lt;td&gt;1226&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;5.37&lt;/td&gt;
&lt;td&gt;5.96&lt;/td&gt;
&lt;td&gt;6.61&lt;/td&gt;
&lt;td&gt;12.01&lt;/td&gt;
&lt;td&gt;203.32&lt;/td&gt;
&lt;td&gt;670&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, all&lt;/td&gt;
&lt;td&gt;811&lt;/td&gt;
&lt;td&gt;933&lt;/td&gt;
&lt;td&gt;2101&lt;/td&gt;
&lt;td&gt;2148&lt;/td&gt;
&lt;td&gt;2154&lt;/td&gt;
&lt;td&gt;2155&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.45&lt;/td&gt;
&lt;td&gt;9.84&lt;/td&gt;
&lt;td&gt;196.94&lt;/td&gt;
&lt;td&gt;1420&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, last 70&lt;/td&gt;
&lt;td&gt;748&lt;/td&gt;
&lt;td&gt;831&lt;/td&gt;
&lt;td&gt;918&lt;/td&gt;
&lt;td&gt;1033&lt;/td&gt;
&lt;td&gt;1033&lt;/td&gt;
&lt;td&gt;1033&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.40&lt;/td&gt;
&lt;td&gt;9.84&lt;/td&gt;
&lt;td&gt;195.38&lt;/td&gt;
&lt;td&gt;546&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of the series, we introduced how to apply another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). The goal was to even further improve the performance of our Lambda functions. We saw that by doing this kind of priming and writing even more additional (but simple) code, we could further reduce the Lambda cold start times compared to simply activating the SnapStart and doing Aurora DSQL request priming. It's once again especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could again reduce the maximal value for the Lambda warm start times by preloading classes and doing some preinitialization work. &lt;/p&gt;

&lt;p&gt;It's up to you to decide whether this additional complexity is worth the Lambda function performance improvement. You could also be happy with its performance using the Lambda SnapStart with the Aurora DSQL request priming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36298"&gt;series&lt;/a&gt; where I use a NoSQL serverless &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; database instead of Aurora DSQL to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 5 SnapStart and full priming</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 13 Apr 2026 15:50:42 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-5-5gpe</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-5-5gpe</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt;, we introduced our sample application. In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;, we measured the performance (cold and warm start times) of the Lambda function without any optimizations. What we observed was quite a large cold start time. We introduced AWS Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt; as one of the approaches to reduce the cold start times of the Lambda function. We saw that by enabling the SnapStart on the Lambda function, the cold start time goes down. &lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-4-45i8"&gt;part 4&lt;/a&gt;, we introduced how to apply Lambda SnapStart priming techniques and started with DynamoDB request priming. We saw that by doing this kind of priming and writing some additional code, we could significantly further reduce the Lambda cold start times compared to simply activating the SnapStart. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could significantly reduce the maximal value for the Lambda warm start times by preloading classes (as Java lazily loads classes when they are required for the first time) and doing some preinitialization work (by invoking the method to retrieve the product from the DynamoDB table by its ID). Previously, all this happened once during the first warm execution of the Lambda function.&lt;/p&gt;

&lt;p&gt;In this article, we'll introduce another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). We'll then measure the Lambda performance by applying it and comparing the results with other already introduced approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with the enabled AWS Lambda SnapStart using full  priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-dynamodb" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, please make sure that we have enabled Lambda SnapStart in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/template.yaml" rel="noopener noreferrer"&gt;template.yaml&lt;/a&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;SnapStart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApplyOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishedVersions&lt;/span&gt; 
    &lt;span class="s"&gt;....&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;JAVA_TOOL_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-XX:+TieredCompilation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-XX:TieredStopAtLevel=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about the concepts behind the Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt; and about SnapStart runtime hooks (which we'll use again) in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce you to the API Gateway Request Event priming (or full priming for short). We implemented it in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetProductByIdWithFullPrimingHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; 
                 &lt;span class="nc"&gt;RequestHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdWithFullPrimingHandler&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getGlobalContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;beforeCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nc"&gt;LambdaEventSerializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serializerFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSystemClassLoader&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;                 
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockLambdaContext&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHttpMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPathParameters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      
 &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterRestore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPathParameters&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionalProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                                 
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optionalProduct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I refer to &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-4-45i8"&gt;part 4&lt;/a&gt; for the explanation about how the Lambda SnapStart runtime hooks work. Please read my article &lt;a href="https://dev.to/aws-heroes/aws-snapstart-part-27-using-insights-from-aws-lambda-profiler-extension-for-java-to-reduce-lambda-2a1i"&gt;Using insights from AWS Lambda Profiler Extension for Java to reduce Lambda cold starts&lt;/a&gt;, on how I came up with this idea. I also described in this article in detail why it is supposed to speed things up. Shortly speaking, we primed another expensive &lt;em&gt;LambdaEventSerializers.serializerFor&lt;/em&gt; invocation. It consists of class loading and expensive initialization logic, which I identified. By invoking &lt;em&gt;handleRequest&lt;/em&gt;, we fully prime this method invocation, which consists mainly of DynamoDB request priming introduced in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-4-45i8"&gt;part 4&lt;/a&gt;. At the end, we also prime the &lt;em&gt;APIGatewayProxyResponseEvent&lt;/em&gt; object construction. &lt;/p&gt;

&lt;p&gt;In our example, we primed the APIGatewayProxyRequestEvent "get product by id equal to zero" request. This is enough to instantiate and initialize all we need, even if we'd like to invoke the "create product" request. This priming implementation is also a read request without any side effects. But if you'd like, for example, to prime a "create product" request, you can do it as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getAPIGatewayProxyRequestEventAsJson&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHttpMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{ 'id': 0, 'name': 'Print 10x13', 'price': 15 }"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use some artificial product ID like 0 or a negative one that isn't used in production. If you use API Gateway HTTP API instead of REST API (like in our example), you can use APIGatewayV2HTTPEvent instead of APIGatewayProxyRequestEvent to prime such a request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application with Lambda SnapStart and full priming
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithDynamoDBAndFullPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithFullPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithFullPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEVDDB25" https://{$API_GATEWAY_URL}/prod/productsWithFullPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will present the Lambda performance measurements with SnapStart being activated for all approx. 100 cold start times (labelled as &lt;em&gt;all&lt;/em&gt; in the table), but also for the last approx. 70 (labelled as &lt;em&gt;last 70&lt;/em&gt; in the table). With that, the effect of the snapshot tiered cache, which we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt;, becomes visible to you.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart full priming, we'll also present the Lambda performance measurements from all previous parts.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 13.796 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;3800&lt;/td&gt;
&lt;td&gt;3967&lt;/td&gt;
&lt;td&gt;4183&lt;/td&gt;
&lt;td&gt;4411&lt;/td&gt;
&lt;td&gt;4495&lt;/td&gt;
&lt;td&gt;4499&lt;/td&gt;
&lt;td&gt;5.55&lt;/td&gt;
&lt;td&gt;6.15&lt;/td&gt;
&lt;td&gt;7.00&lt;/td&gt;
&lt;td&gt;12.18&lt;/td&gt;
&lt;td&gt;56.37&lt;/td&gt;
&lt;td&gt;4000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;2294&lt;/td&gt;
&lt;td&gt;2366&lt;/td&gt;
&lt;td&gt;3530&lt;/td&gt;
&lt;td&gt;3547&lt;/td&gt;
&lt;td&gt;3548&lt;/td&gt;
&lt;td&gt;3551&lt;/td&gt;
&lt;td&gt;5.68&lt;/td&gt;
&lt;td&gt;6.30&lt;/td&gt;
&lt;td&gt;7.33&lt;/td&gt;
&lt;td&gt;13.43&lt;/td&gt;
&lt;td&gt;44.74&lt;/td&gt;
&lt;td&gt;2923&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;2247&lt;/td&gt;
&lt;td&gt;2324&lt;/td&gt;
&lt;td&gt;2389&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;5.68&lt;/td&gt;
&lt;td&gt;6.35&lt;/td&gt;
&lt;td&gt;7.39&lt;/td&gt;
&lt;td&gt;13.65&lt;/td&gt;
&lt;td&gt;44.03&lt;/td&gt;
&lt;td&gt;2051&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DynamoDB request priming applied, all&lt;/td&gt;
&lt;td&gt;778&lt;/td&gt;
&lt;td&gt;817&lt;/td&gt;
&lt;td&gt;1544&lt;/td&gt;
&lt;td&gt;1572&lt;/td&gt;
&lt;td&gt;1601&lt;/td&gt;
&lt;td&gt;1602&lt;/td&gt;
&lt;td&gt;5.50&lt;/td&gt;
&lt;td&gt;6.10&lt;/td&gt;
&lt;td&gt;6.99&lt;/td&gt;
&lt;td&gt;12.01&lt;/td&gt;
&lt;td&gt;34.74&lt;/td&gt;
&lt;td&gt;933&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DynamoDB request priming applied, last 70&lt;/td&gt;
&lt;td&gt;752&lt;/td&gt;
&lt;td&gt;790&lt;/td&gt;
&lt;td&gt;837&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;6.05&lt;/td&gt;
&lt;td&gt;6.99&lt;/td&gt;
&lt;td&gt;11.92&lt;/td&gt;
&lt;td&gt;42.65&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, all&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;660&lt;/td&gt;
&lt;td&gt;1172&lt;/td&gt;
&lt;td&gt;1310&lt;/td&gt;
&lt;td&gt;1325&lt;/td&gt;
&lt;td&gt;1325&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;6.05&lt;/td&gt;
&lt;td&gt;6.93&lt;/td&gt;
&lt;td&gt;11.82&lt;/td&gt;
&lt;td&gt;37.25&lt;/td&gt;
&lt;td&gt;630&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and full priming applied, last 70&lt;/td&gt;
&lt;td&gt;598&lt;/td&gt;
&lt;td&gt;640&lt;/td&gt;
&lt;td&gt;711&lt;/td&gt;
&lt;td&gt;895&lt;/td&gt;
&lt;td&gt;895&lt;/td&gt;
&lt;td&gt;895&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;6.01&lt;/td&gt;
&lt;td&gt;6.93&lt;/td&gt;
&lt;td&gt;12.01&lt;/td&gt;
&lt;td&gt;34.95&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of the series, we introduced how to apply another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). The goal was to even further improve the performance of our Lambda functions. We saw that by doing this kind of priming and writing even more additional (but simple) code, we could further reduce the Lambda cold start times compared to simply activating the SnapStart and doing DynamoDB request priming. It's once again especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could again significantly reduce the maximal value for the Lambda warm start times by preloading classes and doing some preinitialization work. &lt;/p&gt;

&lt;p&gt;It's up to you to decide whether this additional complexity is worth the Lambda function performance improvement. You could also be happy with its performance using the Lambda SnapStart with the DynamoDB request priming.&lt;/p&gt;

&lt;p&gt;In the next part, we'll introduce another approach to reduce the cold start time of the Lambda function - GraalVM Native Image. We'll create the native image of our application and deploy it as a Lambda Custom Runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36919"&gt;series&lt;/a&gt; where I use a relational serverless &lt;a href="https://aws.amazon.com/rds/aurora/dsql/" rel="noopener noreferrer"&gt;Amazon Aurora DSQL&lt;/a&gt; database and additionally the &lt;a href="https://hibernate.org/" rel="noopener noreferrer"&gt;Hibernate ORM framework&lt;/a&gt; instead of DynamoDB to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslmabda</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and Aurora DSQL - Part 4 SnapStart and DSQL request priming</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Thu, 09 Apr 2026 15:14:36 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-4-1kg5</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-4-1kg5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt;, we introduced our sample application. In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;, we measured the performance (cold and warm start times) of the Lambda function without any optimizations. We observed quite a large cold start time, especially if we use the Hibernate ORM framework. Using this framework also significantly increases the artifact size.  &lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;, we introduced AWS Lambda SnapStart as one of the approaches to reduce the cold start times of the Lambda function. We observed that by enabling the SnapStart on the Lambda function, the cold start time goes down significantly for both sample applications. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. The biggest impact of just enabling the SnapStart is on the application using the Hibernate.&lt;/p&gt;

&lt;p&gt;In this part of our article series, we'll introduce how to apply Lambda SnapStart priming techniques. We'll start with the database (in our case, Aurora DSQL) request priming. The goal is to further improve the performance of our Lambda functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with JDBC and Hikari connection pool and the enabled AWS Lambda SnapStart using Aurora DSQL request priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, please make sure that we have enabled Lambda SnapStart in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;template.yaml&lt;/a&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;SnapStart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApplyOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishedVersions&lt;/span&gt; 
    &lt;span class="s"&gt;....&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;JAVA_TOOL_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-XX:+TieredCompilation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-XX:TieredStopAtLevel=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about the concepts behind the Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/compute/reducing-java-cold-starts-on-aws-lambda-functions-with-snapstart/" rel="noopener noreferrer"&gt;SnapStart and runtime hooks&lt;/a&gt; offer you new possibilities to create your Lambda functions for low startup latency. With the pre-snapshot hook, we can prepare our Java application as much as possible for the first call. We load and initialize as much as possible that our Lambda function needs before the Lambda SnapStart creates the snapshot. The name for this technique is priming.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce you to the priming of database (in our case, Aurora DSQL) requests, which we implemented in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDSQLPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/a&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/span&gt;
        &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RequestHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getGlobalContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
 &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;beforeCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterRestore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;       
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPathParameters&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionalProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                    
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optionalProduct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/snapstart-runtime-hooks-java.html" rel="noopener noreferrer"&gt;Lambda SnapStart CRaC runtime hooks&lt;/a&gt; here. To do this, we need to declare the following dependency in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.crac&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;org-crac&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GetProductByIdWithDSQLPrimingHandler class additionally implements &lt;em&gt;org.crac.Resource&lt;/em&gt; interface. The class registers itself as a CRaC resource in the constructor of this class. The priming itself happens in the method where we search for the product with the ID equal to 0 in the products table of the Aurora DSQL database. &lt;em&gt;beforeCheckpoint&lt;/em&gt; method is a CRaC runtime hook that is invoked before creating the microVM snapshot. We are not even processing the result of the call to &lt;em&gt;productDao.getProductById(0)&lt;/em&gt;. The product with the ID equal to zero might not even exist in the database. But with this invocation, Java lazily loads and instantiates all the classes that it requires for this invocation.  PreparedStatement, ResultSet, and many others are among such classes. We also instantiate everything required to establish the connection to the database via JDBC and process the request and response.&lt;/p&gt;

&lt;p&gt;We can leave the afterRestore method empty. This is because we don't need to perform any action after the SnapStart snapshot has been restored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with JDBC and Hikari connection pool
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithDSQLAndDSQLPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDSQLPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEDQ25" https://{$API_GATEWAY_URL}/prod/productsWithAuroraPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Please read &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; for the description of how we designed the experiment.&lt;/p&gt;

&lt;p&gt;I will present the Lambda performance measurements with SnapStart being activated for all approx. 100 cold start times (labelled as &lt;em&gt;all&lt;/em&gt; in the table), but also for the last approx. 70 (labelled as &lt;em&gt;last 70&lt;/em&gt; in the table). With that, the effect of the snapshot tiered cache, which we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;, becomes visible to you.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart with the Aurora DSQL database request priming, we'll also present the Lambda performance measurements from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 17.150 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;2336&lt;/td&gt;
&lt;td&gt;2453&lt;/td&gt;
&lt;td&gt;2827&lt;/td&gt;
&lt;td&gt;3026&lt;/td&gt;
&lt;td&gt;3131&lt;/td&gt;
&lt;td&gt;3132&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.29&lt;/td&gt;
&lt;td&gt;5.73&lt;/td&gt;
&lt;td&gt;8.88&lt;/td&gt;
&lt;td&gt;195.38&lt;/td&gt;
&lt;td&gt;531&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;970&lt;/td&gt;
&lt;td&gt;1058&lt;/td&gt;
&lt;td&gt;1705&lt;/td&gt;
&lt;td&gt;1726&lt;/td&gt;
&lt;td&gt;1734&lt;/td&gt;
&lt;td&gt;1735&lt;/td&gt;
&lt;td&gt;4.92&lt;/td&gt;
&lt;td&gt;5.33&lt;/td&gt;
&lt;td&gt;5.86&lt;/td&gt;
&lt;td&gt;9.84&lt;/td&gt;
&lt;td&gt;198.52&lt;/td&gt;
&lt;td&gt;1134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;901&lt;/td&gt;
&lt;td&gt;960&lt;/td&gt;
&lt;td&gt;1061&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;1212&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.29&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;196.94&lt;/td&gt;
&lt;td&gt;719&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, all&lt;/td&gt;
&lt;td&gt;879&lt;/td&gt;
&lt;td&gt;980&lt;/td&gt;
&lt;td&gt;1499&lt;/td&gt;
&lt;td&gt;1515&lt;/td&gt;
&lt;td&gt;1518&lt;/td&gt;
&lt;td&gt;1518&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;163.96&lt;/td&gt;
&lt;td&gt;914&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, last 70&lt;/td&gt;
&lt;td&gt;803&lt;/td&gt;
&lt;td&gt;912&lt;/td&gt;
&lt;td&gt;996&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;1056&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;5.77&lt;/td&gt;
&lt;td&gt;9.54&lt;/td&gt;
&lt;td&gt;152.61&lt;/td&gt;
&lt;td&gt;597&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sample application with Hibernate and Hikari connection pool and the enabled AWS Lambda SnapStart using Aurora DSQL request priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-hibernate-aurora-dsql/" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-1-2g27"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We implemented the priming of the database (in our case, Aurora DSQL) request in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDSQLPrimingHandler" rel="noopener noreferrer"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/a&gt; class. &lt;/p&gt;

&lt;p&gt;The explanation of what we would like to achieve and how is exactly the same as in the first sample application above. The main difference is that we use the Hibernate framework on top and also preload and preinitialize its classes and abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of the Lambda function of the sample application with Hibernate and Hikari connection pool
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithHibernateAndDSQLAndDSQLPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-hibernate-aurora-dsql/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDSQLPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithDSQLPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEHADQ25" https://{$API_GATEWAY_URL}/prod/productsWithAuroraPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Please read &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; for the description of how we designed the experiment.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart with the Aurora DSQL database request priming, we'll also present the Lambda performance measurements from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-2-4bbb"&gt;part 2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-3-4ep2"&gt;part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 42.333 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;6243&lt;/td&gt;
&lt;td&gt;6625&lt;/td&gt;
&lt;td&gt;7056&lt;/td&gt;
&lt;td&gt;8480&lt;/td&gt;
&lt;td&gt;8651&lt;/td&gt;
&lt;td&gt;8658&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;5.96&lt;/td&gt;
&lt;td&gt;6.50&lt;/td&gt;
&lt;td&gt;9.77&lt;/td&gt;
&lt;td&gt;200.10&lt;/td&gt;
&lt;td&gt;707&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;1277&lt;/td&gt;
&lt;td&gt;1360&lt;/td&gt;
&lt;td&gt;3050&lt;/td&gt;
&lt;td&gt;3103&lt;/td&gt;
&lt;td&gt;3200&lt;/td&gt;
&lt;td&gt;3201&lt;/td&gt;
&lt;td&gt;5.50&lt;/td&gt;
&lt;td&gt;6.01&lt;/td&gt;
&lt;td&gt;6.45&lt;/td&gt;
&lt;td&gt;10.16&lt;/td&gt;
&lt;td&gt;196.94&lt;/td&gt;
&lt;td&gt;2349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;1258&lt;/td&gt;
&lt;td&gt;1320&lt;/td&gt;
&lt;td&gt;1437&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;1634&lt;/td&gt;
&lt;td&gt;5.42&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.40&lt;/td&gt;
&lt;td&gt;10.08&lt;/td&gt;
&lt;td&gt;195.94&lt;/td&gt;
&lt;td&gt;1093&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, all&lt;/td&gt;
&lt;td&gt;1030&lt;/td&gt;
&lt;td&gt;1185&lt;/td&gt;
&lt;td&gt;2310&lt;/td&gt;
&lt;td&gt;2341&lt;/td&gt;
&lt;td&gt;2345&lt;/td&gt;
&lt;td&gt;2347&lt;/td&gt;
&lt;td&gt;5.33&lt;/td&gt;
&lt;td&gt;5.91&lt;/td&gt;
&lt;td&gt;6.50&lt;/td&gt;
&lt;td&gt;11.64&lt;/td&gt;
&lt;td&gt;201.70&lt;/td&gt;
&lt;td&gt;1607&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DSQL database request priming applied, last 70&lt;/td&gt;
&lt;td&gt;970&lt;/td&gt;
&lt;td&gt;1076&lt;/td&gt;
&lt;td&gt;1226&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;1511&lt;/td&gt;
&lt;td&gt;5.37&lt;/td&gt;
&lt;td&gt;5.96&lt;/td&gt;
&lt;td&gt;6.61&lt;/td&gt;
&lt;td&gt;12.01&lt;/td&gt;
&lt;td&gt;203.32&lt;/td&gt;
&lt;td&gt;670&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of our article series, we introduced how to apply the Lambda SnapStart priming technique, such as Aurora DSQL request priming. The goal was to even further improve the performance of our Lambda functions. We saw that by doing this kind of priming and writing some additional code, we could additionally reduce the Lambda cold start times compared to simply activating the SnapStart. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could also reduce the maximal value for the Lambda warm start times by preloading classes (as Java lazily loads classes when they are required for the first time) and doing some preinitialization work (by invoking the method to retrieve the product from the Aurora DSQL products table by its ID). Previously, all this happened &lt;em&gt;once&lt;/em&gt; during the &lt;em&gt;first warm execution&lt;/em&gt; of the Lambda function.&lt;/p&gt;

&lt;p&gt;In the next part of our article series, we'll introduce another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). We'll then measure the Lambda performance by applying it and comparing the results with other already introduced approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36298"&gt;series&lt;/a&gt; where I use a NoSQL serverless &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; database instead of Aurora DSQL to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 4 SnapStart and DynamoDB request priming</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:06:04 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-4-45i8</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-4-45i8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt;, we introduced our sample application. In &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;, we measured the performance (cold and warm start times) of the Lambda function without any optimizations. What we observed was quite a large cold start time. We introduced AWS Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt; as one of the approaches to reduce the cold start times of the Lambda function. We saw that by enabling the SnapStart on the Lambda function, the cold start time goes down. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect.&lt;/p&gt;

&lt;p&gt;In this part of our article series, we'll introduce how to apply Lambda SnapStart priming techniques, starting with DynamoDB request priming. The goal is to further improve the performance of our Lambda functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with the enabled AWS Lambda SnapStart using DynamoDB request priming
&lt;/h2&gt;

&lt;p&gt;We'll reuse the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-dynamodb" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-1-sample-4hdg"&gt;part 1&lt;/a&gt; and do exactly the same performance measurement as we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, please make sure that we have enabled Lambda SnapStart in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/template.yaml" rel="noopener noreferrer"&gt;template.yaml&lt;/a&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;SnapStart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApplyOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishedVersions&lt;/span&gt; 
    &lt;span class="s"&gt;....&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;JAVA_TOOL_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-XX:+TieredCompilation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-XX:TieredStopAtLevel=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about the concepts behind the Lambda SnapStart in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/compute/reducing-java-cold-starts-on-aws-lambda-functions-with-snapstart/" rel="noopener noreferrer"&gt;SnapStart and runtime hooks&lt;/a&gt; offer you new possibilities to create your Lambda functions for low startup latency. With the pre-snapshot hook, we can prepare our Java application as much as possible for the first call. We load and initialize as much as possible that our Lambda function needs before the Lambda SnapStart creates the snapshot. The name for this technique is priming.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce you to the priming of DynamoDB requests, which we implemented in the extra &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDynamoDBPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithDynamoDBPrimingHandler&lt;/a&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetProductByIdWithDynamoDBPrimingHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; 
                 &lt;span class="nc"&gt;RequestHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDao&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdWithDynamoDBPrimingHandler&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getGlobalContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;beforeCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterRestore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   

&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;APIGatewayProxyResponseEvent&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APIGatewayProxyRequestEvent&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPathParameters&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionalProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;APIGatewayProxyResponseEvent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                                    
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optionalProduct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/snapstart-runtime-hooks-java.html" rel="noopener noreferrer"&gt;Lambda SnapStart CRaC runtime hooks&lt;/a&gt; here. To do this, we need to declare the following dependency in &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.crac&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;org-crac&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GetProductByIdWithDynamoDBPrimingHandler class additionally implements &lt;em&gt;org.crac.Resource&lt;/em&gt; interface. The class registers itself as a CRaC resource in the constructor of this class. The priming itself happens in the method where we search for the product with the ID equal to 0 in the DynamoDB table. &lt;em&gt;beforeCheckpoint&lt;/em&gt; method is a CRaC runtime hook that is invoked before creating the microVM snapshot. We are not even processing the result of the call to &lt;em&gt;productDao.getProduct("0")&lt;/em&gt;. The product with the ID equal to zero might not even exist in the database. But with this invocation, Java lazily loads and instantiates all the classes that it requires for this invocation. GetItemRequest, GetItemResponse, and many others are among such classes. Also, the expensive one-time initialization of the HTTP Client (default is Apache HTTP Client) happens. The same is true for the initialization of the Jackson Marshaller for converting Java objects to JSON and vice versa. As the priming happens during the deployment phase of the Lambda function when SnapStart is activated and before the SnapStart snapshot is created, the snapshot will already contain all of this. After the fast snapshot restore phase during the Lambda invoke, we'll gain a lot in performance in case the cold start happens. We can leave the afterRestore method empty. We don't need to perform any action after the SnapStart snapshot has been restored. &lt;/p&gt;

&lt;p&gt;By the way, we could also achieve the same result by sending nearly any other request to DynamoDB, for example, &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html" rel="noopener noreferrer"&gt;DescribeTable&lt;/a&gt;. However, I found it easier to reuse some already existing database operations. And we already implemented the getProduct by ID method in our DynamoDB DAO.  This is a read request without any side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application with Lambda SnapStart and DynamoDB request priming
&lt;/h2&gt;

&lt;p&gt;We'll measure the performance of the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/template.yaml" rel="noopener noreferrer"&gt;GetProductByIdJava25WithDynamoDBAndDDBPriming&lt;/a&gt; Lambda function mapped to the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb/src/main/java/software/amazonaws/example/product/handler/GetProductByIdWithDynamoDBPrimingHandler.java" rel="noopener noreferrer"&gt;GetProductByIdWithDynamoDBPrimingHandler&lt;/a&gt; shown above. We will trigger it by invoking &lt;em&gt;curl -H "X-API-Key: a6ZbcDefQW12BN56WEVDDB25" https://{$API_GATEWAY_URL}/prod/productsWithDynamoDBPriming/1&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We designed the experiment exactly as described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will present the Lambda performance measurements with SnapStart being activated for all approx. 100 cold start times (labelled as &lt;em&gt;all&lt;/em&gt; in the table), but also for the last approx. 70 (labelled as &lt;em&gt;last 70&lt;/em&gt; in the table). With that, the effect of the snapshot tiered cache, which we described in &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt;, becomes visible to you.&lt;/p&gt;

&lt;p&gt;To show the impact of the SnapStart with the DynamoDB request priming, we'll also present the Lambda performance measurements from &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-using-lambda-with-java-25-api-gateway-and-dynamodb-part-2-initial-3fdj"&gt;part 2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-3-2h31"&gt;part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 13.796 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (c) and warm (w) start time with &lt;em&gt;-XX:+TieredCompilation -XX:TieredStopAtLevel=1&lt;/em&gt; compilation in ms:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;c p50&lt;/th&gt;
&lt;th&gt;c p75&lt;/th&gt;
&lt;th&gt;c p90&lt;/th&gt;
&lt;th&gt;c p99&lt;/th&gt;
&lt;th&gt;c p99.9&lt;/th&gt;
&lt;th&gt;c max&lt;/th&gt;
&lt;th&gt;w p50&lt;/th&gt;
&lt;th&gt;w p75&lt;/th&gt;
&lt;th&gt;w p90&lt;/th&gt;
&lt;th&gt;w p99&lt;/th&gt;
&lt;th&gt;w p99.9&lt;/th&gt;
&lt;th&gt;w max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No SnapStart enabled&lt;/td&gt;
&lt;td&gt;3800&lt;/td&gt;
&lt;td&gt;3967&lt;/td&gt;
&lt;td&gt;4183&lt;/td&gt;
&lt;td&gt;4411&lt;/td&gt;
&lt;td&gt;4495&lt;/td&gt;
&lt;td&gt;4499&lt;/td&gt;
&lt;td&gt;5.55&lt;/td&gt;
&lt;td&gt;6.15&lt;/td&gt;
&lt;td&gt;7.00&lt;/td&gt;
&lt;td&gt;12.18&lt;/td&gt;
&lt;td&gt;56.37&lt;/td&gt;
&lt;td&gt;4000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, all&lt;/td&gt;
&lt;td&gt;2294&lt;/td&gt;
&lt;td&gt;2366&lt;/td&gt;
&lt;td&gt;3530&lt;/td&gt;
&lt;td&gt;3547&lt;/td&gt;
&lt;td&gt;3548&lt;/td&gt;
&lt;td&gt;3551&lt;/td&gt;
&lt;td&gt;5.68&lt;/td&gt;
&lt;td&gt;6.30&lt;/td&gt;
&lt;td&gt;7.33&lt;/td&gt;
&lt;td&gt;13.43&lt;/td&gt;
&lt;td&gt;44.74&lt;/td&gt;
&lt;td&gt;2923&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled but no priming applied, last 70&lt;/td&gt;
&lt;td&gt;2247&lt;/td&gt;
&lt;td&gt;2324&lt;/td&gt;
&lt;td&gt;2389&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;2637&lt;/td&gt;
&lt;td&gt;5.68&lt;/td&gt;
&lt;td&gt;6.35&lt;/td&gt;
&lt;td&gt;7.39&lt;/td&gt;
&lt;td&gt;13.65&lt;/td&gt;
&lt;td&gt;44.03&lt;/td&gt;
&lt;td&gt;2051&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DynamoDB request priming applied, all&lt;/td&gt;
&lt;td&gt;778&lt;/td&gt;
&lt;td&gt;817&lt;/td&gt;
&lt;td&gt;1544&lt;/td&gt;
&lt;td&gt;1572&lt;/td&gt;
&lt;td&gt;1601&lt;/td&gt;
&lt;td&gt;1602&lt;/td&gt;
&lt;td&gt;5.50&lt;/td&gt;
&lt;td&gt;6.10&lt;/td&gt;
&lt;td&gt;6.99&lt;/td&gt;
&lt;td&gt;12.01&lt;/td&gt;
&lt;td&gt;34.74&lt;/td&gt;
&lt;td&gt;933&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapStart enabled and DynamoDB request priming applied, last 70&lt;/td&gt;
&lt;td&gt;752&lt;/td&gt;
&lt;td&gt;790&lt;/td&gt;
&lt;td&gt;837&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;988&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;6.05&lt;/td&gt;
&lt;td&gt;6.99&lt;/td&gt;
&lt;td&gt;11.92&lt;/td&gt;
&lt;td&gt;42.65&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this part of the series, we introduced how to apply Lambda SnapStart priming techniques and started with DynamoDB request priming. The goal was to even further improve the performance of our Lambda functions. We saw that by doing this kind of priming and writing some additional code, we could significantly further reduce the Lambda cold start times compared to simply activating the SnapStart. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. Moreover, we could significantly reduce the maximal value for the Lambda warm start times by preloading classes (as Java lazily loads classes when they are required for the first time) and doing some preinitialization work (by invoking the method to retrieve the product from the DynamoDB table by its ID). Previously, all this happened &lt;em&gt;once&lt;/em&gt; during the &lt;em&gt;first warm execution&lt;/em&gt; of the Lambda function.&lt;/p&gt;

&lt;p&gt;In the next part of our article series, we'll introduce another Lambda SnapStart priming technique. I call it API Gateway Request Event priming (or full priming). We'll then measure the Lambda performance by applying it and comparing the results with other already introduced approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also watch out for another &lt;a href="https://dev.to/vkazulkin/series/36919"&gt;series&lt;/a&gt; where I use a relational serverless &lt;a href="https://aws.amazon.com/rds/aurora/dsql/" rel="noopener noreferrer"&gt;Amazon Aurora DSQL&lt;/a&gt; database and additionally the &lt;a href="https://hibernate.org/" rel="noopener noreferrer"&gt;Hibernate ORM framework&lt;/a&gt; instead of DynamoDB to do the same Lambda performance measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you like my content, please follow me on &lt;a href="https://github.com/Vadym79" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and give my repositories a star!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please also check out my &lt;a href="https://vkazulkin.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; for more technical content and upcoming public speaking activities.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>java</category>
      <category>serverless</category>
      <category>awslambda</category>
    </item>
  </channel>
</rss>
