<?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.us-east-2.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>Serverless applications on AWS with Lambda using Java 25, API Gateway and Aurora DSQL - Part 8 Using Container Image</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-8-2hkb</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-aurora-dsql-part-8-2hkb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Several years ago, I wrote an article series &lt;a href="https://dev.to/vkazulkin/series/34789"&gt;Lambda function using Docker Container Image&lt;/a&gt;, where I explained how to convert an application deployed on AWS Lambda as a managed Java runtime to one deployed on AWS Lambda as a (Docker) container image. As we saw, the cold start time of a Lambda function using a container image was quite high. Back then, no Lambda cold-start performance optimization approaches were available, such as Lambda SnapStart. This has changed recently, as &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/" rel="noopener noreferrer"&gt;AWS Lambda now supports SnapStart for container image functions&lt;/a&gt;. Now it's time to revisit this topic and evaluate this option. In this part of the series, we'll refresh our knowledge about how to convert the sample application to one using a container image as a deployment artifact on AWS Lambda and measure the performance of the Lambda functions. &lt;/p&gt;

&lt;p&gt;In the next part, we'll enable SnapStart on the Lambda functions as we did in part &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;3&lt;/a&gt;. We'll also apply the same priming techniques that we applied in parts 4-5 of this series. Of course, we'll measure the performance of the Lambda functions for all scenarios.&lt;/p&gt;

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

&lt;p&gt;You can find the final version of our sample application in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-aurora-dsql-as-container-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-aurora-dsql-as-container-image&lt;/a&gt; GitHub repository, which we can deploy on AWS Lambda as a container image. Let's go through the changes that we had to make to our application 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;. First of all, we need to define a &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-container-image/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&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; public.ecr.aws/lambda/java:25&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/aws-lambda-java-25-with-aurora-dsql-as-container-image-*.jar ${LAMBDA_TASK_ROOT}/lib/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the AWS Lambda base Docker image &lt;em&gt;public.ecr.aws/lambda/java:25&lt;/em&gt; for Java 25 from the public Amazon ECR repository. &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/java-image.html#java-image-instructions" rel="noopener noreferrer"&gt;Here&lt;/a&gt; you can find all Lambda base Docker images for Java provided by AWS. In the Docker image, we also copy the application's code packaged as a JAR file to the path of our Lambda function code. This path is defined as the runtime environment variable &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html" rel="noopener noreferrer"&gt;LAMBDA_TASK_ROOT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, let's answer the question of how to create and push a Docker container image to the ECR repository.  You can do it automatically when you execute &lt;strong&gt;sam build, and sam deploy&lt;/strong&gt; command chain, or if you build and package the application outside of SAM and only deploy with SAM, you need to do it manually, like this:&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 and package your application&lt;/span&gt;

mvn clean package

&lt;span class="c"&gt;# Build Docker image with name aws-lambda-java-25-aurora-dsql:&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; aws-lambda-java-25-aurora-dsql:v1 &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Log in to Amazon ECR in case the credentials have expired:&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;aws_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;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com  

&lt;span class="c"&gt;# Create Amazon ECR repository if it does not exist with the name aws-lambda-java-25-aurora-dsql:&lt;/span&gt;

aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; aws-lambda-java-25-aurora-dsql &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;aws_region&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Tag our local Docker image and link it with the Amazon ECR repository. &lt;/span&gt;
&lt;span class="c"&gt;# Exactly this image is referenced as ImageUri in the Lambda function:&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag aws-lambda-java-25-aurora-dsql:v1 &lt;span class="o"&gt;{&lt;/span&gt;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/aws-lambda-java-25-aurora-dsql:v1

&lt;span class="c"&gt;# Push the Docker image to the Amazon ECR repository:&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push &lt;span class="o"&gt;{&lt;/span&gt;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/aws-lambda-java-25-aurora-dsql:v1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace the values of the variables {aws_account_id} and {aws_region} with your own values. If you build a version (image tag) other than &lt;em&gt;v1&lt;/em&gt;, please replace the version reference as well. &lt;/p&gt;

&lt;p&gt;You can find all other changes in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-aurora-dsql-as-container-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;. Instead of defining Runtime (as Java 25), CodeUri, and Handler parameters, we have to define the following for all Lambda functions:&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;GetProductByIdFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GetProductByIdJava25WithDSQLAsCI&lt;/span&gt;
    &lt;span class="na"&gt;PackageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Image&lt;/span&gt;
    &lt;span class="na"&gt;ImageUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/aws-lambda-java-25-aurora-dsql:v1&lt;/span&gt;
    &lt;span class="na"&gt;ImageConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;software.amazonaws.example.product.handler.GetProductByIdHandler::handleRequest"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we set the &lt;em&gt;PackageType&lt;/em&gt; to Image and point the &lt;em&gt;ImageURI&lt;/em&gt; to the ECR function's code in the ECR repository. We created the repository with the name &lt;em&gt;aws-lambda-java-25-aurora-dsql&lt;/em&gt; above. Please pay attention to the version (image tag). We use &lt;em&gt;v1&lt;/em&gt; here, but you can use &lt;em&gt;latest&lt;/em&gt; instead. Then, in the &lt;em&gt;ImageConfig&lt;/em&gt;, we define the &lt;em&gt;Command&lt;/em&gt; property, which is specific to each Lambda function and should point to the function's handler. For a function defined as a container image, Lambda resolves the image tag to an image digest. In Amazon ECR, if you update the image tag to a new image, Lambda does not automatically update the function. You need to use &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html#update-function-code" rel="noopener noreferrer"&gt;update-function-code&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;Finally, to deploy our application, we need to invoke &lt;code&gt;sam deploy --image-repository {aws_account_id}.dkr.ecr.{aws_region}.amazonaws.com/aws-lambda-java-25-aurora-dsql&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Please note that &lt;a href="https://aws.amazon.com/lambda/pricing/#SnapStart_Pricing" rel="noopener noreferrer"&gt;SnapStart charges&lt;/a&gt; apply to the container images, while SnapStart for supported Java managed versions remains free on AWS Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application using a Container 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-container-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-aurora-dsql-as-container-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: a6ZbcDefQW12BN56WEDQ25CI" 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;The deployed artifact size of the container image of this application was 141.63 MB.&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;Lambda Container Image&lt;/td&gt;
&lt;td&gt;1886&lt;/td&gt;
&lt;td&gt;2047&lt;/td&gt;
&lt;td&gt;2218&lt;/td&gt;
&lt;td&gt;2568&lt;/td&gt;
&lt;td&gt;2628&lt;/td&gt;
&lt;td&gt;2629&lt;/td&gt;
&lt;td&gt;4.90&lt;/td&gt;
&lt;td&gt;5.24&lt;/td&gt;
&lt;td&gt;5.63&lt;/td&gt;
&lt;td&gt;8.00&lt;/td&gt;
&lt;td&gt;131.40&lt;/td&gt;
&lt;td&gt;380&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 using a Container Image
&lt;/h2&gt;

&lt;p&gt;Overall, we need to apply the same steps as described above. You can find the final version of our sample application in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-hibernate-aurora-dsql-as-container-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-hibernate-aurora-dsql-as-container-image&lt;/a&gt; GitHub repository, which we can deploy on AWS Lambda as a container image. Lastly, the biggest difference is the ECR repository name, which is &lt;em&gt;aws-lambda-java-25-hibernate-and-aurora-dsql&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application using a Container 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-container-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-container-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: a6ZbcDefQW12BN56WEHADQ25CI" 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;The deployed artifact size of the container image of this application was 164.32 MB.&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;Lambda Container Image&lt;/td&gt;
&lt;td&gt;3971&lt;/td&gt;
&lt;td&gt;4150&lt;/td&gt;
&lt;td&gt;4229&lt;/td&gt;
&lt;td&gt;4660&lt;/td&gt;
&lt;td&gt;4914&lt;/td&gt;
&lt;td&gt;4914&lt;/td&gt;
&lt;td&gt;5.46&lt;/td&gt;
&lt;td&gt;5.86&lt;/td&gt;
&lt;td&gt;6.30&lt;/td&gt;
&lt;td&gt;8.20&lt;/td&gt;
&lt;td&gt;203&lt;/td&gt;
&lt;td&gt;398&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 learned how to convert the sample application to one using a container image as a deployment artifact on AWS Lambda and measured the performance of the Lambda functions. We observed that the Lambda cold start times are quite high. &lt;/p&gt;

&lt;p&gt;In the next part, we'll enable &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/" rel="noopener noreferrer"&gt;SnapStart on the Lambda functions using a container image&lt;/a&gt;. We'll also apply priming techniques and measure the performance of the Lambda functions for all scenarios.&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 8 Using Container Image</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-8-using-20oo</link>
      <guid>https://dev.to/aws-heroes/serverless-applications-on-aws-with-lambda-using-java-25-api-gateway-and-dynamodb-part-8-using-20oo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Several years ago, I wrote an article series &lt;a href="https://dev.to/vkazulkin/series/34789"&gt;Lambda function using Docker Container Image&lt;/a&gt;, where I explained how to convert an application deployed on AWS Lambda as a managed Java runtime to one deployed on AWS Lambda as a (Docker) container image. As we saw, the cold start time of a Lambda function using a container image was quite high. Back then, no Lambda cold-start performance optimization approaches were available, such as Lambda SnapStart. This has changed recently, as &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/" rel="noopener noreferrer"&gt;AWS Lambda now supports SnapStart for container image functions&lt;/a&gt;. Now it's time to revisit this topic and evaluate this option. In this part of the series, we'll refresh our knowledge about how to convert the sample application to one using a container image as a deployment artifact on AWS Lambda and measure the performance of the Lambda functions. &lt;/p&gt;

&lt;p&gt;In the next part, we'll enable SnapStart on the Lambda functions as we did in part &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;3&lt;/a&gt;. We'll also apply the same priming techniques that we applied in parts 4-5 of this series. Of course, we'll measure the performance of the Lambda functions for all scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application using a Container Image
&lt;/h2&gt;

&lt;p&gt;You can find the final version of our sample application in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/tree/main/aws-lambda-java-25-dynamodb-as-container-image" rel="noopener noreferrer"&gt;aws-lambda-java-25-dynamodb-as-container-image&lt;/a&gt; GitHub repository, which we can deploy on AWS Lambda as a container image. Let's go through the changes that we had to make to our application 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;. First of all, we need to define a &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-container-image/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&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; public.ecr.aws/lambda/java:25&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/aws-lambda-java-25-with-dynamodb-as-container-image-*.jar ${LAMBDA_TASK_ROOT}/lib/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the AWS Lambda base Docker image &lt;em&gt;public.ecr.aws/lambda/java:25&lt;/em&gt; for Java 25 from the public Amazon ECR repository. &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/java-image.html#java-image-instructions" rel="noopener noreferrer"&gt;Here&lt;/a&gt; you can find all Lambda base Docker images for Java provided by AWS. In the Docker image, we also copy the application's code packaged as a JAR file to the path of our Lambda function code. This path is defined as the runtime environment variable &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html" rel="noopener noreferrer"&gt;LAMBDA_TASK_ROOT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, let's answer the question of how to create and push a Docker container image to the ECR repository.  You can do it automatically when you execute &lt;strong&gt;sam build, and sam deploy&lt;/strong&gt; command chain, or if you build and package the application outside of SAM and only deploy with SAM, you need to do it manually, like this:&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 and package your application&lt;/span&gt;

mvn clean package

&lt;span class="c"&gt;# Build Docker image with name aws-lambda-java-25-with-dynamodb:&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; aws-lambda-java-25-with-dynamodb:v1 &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Log in to Amazon ECR in case the credentials have expired:&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;aws_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;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com  

&lt;span class="c"&gt;# Create Amazon ECR repository if it does not exist with the name aws-lambda-java-25-with-dynamodb:&lt;/span&gt;

aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; aws-lambda-java-25-with-dynamodb &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;aws_region&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Tag our local Docker image and link it with the Amazon ECR repository. &lt;/span&gt;
&lt;span class="c"&gt;# Exactly this image is referenced as ImageUri in the Lambda function:&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag aws-lambda-java-25-with-dynamodb:v1 &lt;span class="o"&gt;{&lt;/span&gt;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/aws-lambda-java-25-with-dynamodb:v1

&lt;span class="c"&gt;# Push the Docker image to the Amazon ECR repository:&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push &lt;span class="o"&gt;{&lt;/span&gt;aws_account_id&lt;span class="o"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="o"&gt;{&lt;/span&gt;aws_region&lt;span class="o"&gt;}&lt;/span&gt;.amazonaws.com/aws-lambda-java-25-with-dynamodb:v1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please replace the values of the variables {aws_account_id} and {aws_region} with your own values. If you build a version (image tag) other than &lt;em&gt;v1&lt;/em&gt;, please replace the version reference as well. &lt;/p&gt;

&lt;p&gt;You can find all other changes in the &lt;a href="https://github.com/Vadym79/aws-lambda-java-25/blob/main/aws-lambda-java-25-dynamodb-as-container-image/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;. Instead of defining Runtime (as Java 25), CodeUri, and Handler parameters, we have to define the following for all Lambda functions:&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;GetProductByIdFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GetProductByIdJava25WithDynamoDBAsCI&lt;/span&gt;
    &lt;span class="na"&gt;PackageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Image&lt;/span&gt;
    &lt;span class="na"&gt;ImageUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/aws-lambda-java-25-with-dynamodb:v1&lt;/span&gt;
    &lt;span class="na"&gt;ImageConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;software.amazonaws.example.product.handler.GetProductByIdHandler::handleRequest"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we set the &lt;em&gt;PackageType&lt;/em&gt; to Image and point the &lt;em&gt;ImageURI&lt;/em&gt; to the ECR function's code in the ECR repository. We created the repository with the name &lt;em&gt;aws-lambda-java-25-with-dynamodb&lt;/em&gt; above. Please pay attention to the version (image tag). We use &lt;em&gt;v1&lt;/em&gt; here, but you can use &lt;em&gt;latest&lt;/em&gt; instead. Then, in the &lt;em&gt;ImageConfig&lt;/em&gt;, we define the &lt;em&gt;Command&lt;/em&gt; property, which is specific to each Lambda function and should point to the function's handler. For a function defined as a container image, Lambda resolves the image tag to an image digest. In Amazon ECR, if you update the image tag to a new image, Lambda does not automatically update the function. You need to use &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html#update-function-code" rel="noopener noreferrer"&gt;update-function-code&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;To deploy our application, we need to invoke &lt;code&gt;sam deploy --image-repository {aws_account_id}.dkr.ecr.{aws_region}.amazonaws.com/aws-lambda-java-25-with-dynamodb&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Please note that &lt;a href="https://aws.amazon.com/lambda/pricing/#SnapStart_Pricing" rel="noopener noreferrer"&gt;SnapStart charges&lt;/a&gt; apply to the container images, while SnapStart for supported Java managed versions remains free on AWS Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements of cold and warm start times of our application using a Container 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-container-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-container-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: a6ZbcDefQW12BN56WEVDDB25CI" 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;The deployed artifact size of the container image of this application was 138.53 MB.&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;Lambda Container Image&lt;/td&gt;
&lt;td&gt;2788&lt;/td&gt;
&lt;td&gt;2884&lt;/td&gt;
&lt;td&gt;3063&lt;/td&gt;
&lt;td&gt;3374&lt;/td&gt;
&lt;td&gt;3456&lt;/td&gt;
&lt;td&gt;3460&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;5.73&lt;/td&gt;
&lt;td&gt;6.30&lt;/td&gt;
&lt;td&gt;9.46&lt;/td&gt;
&lt;td&gt;56.82&lt;/td&gt;
&lt;td&gt;1481&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 learned how to convert the sample application to one using a container image as a deployment artifact on AWS Lambda and measured the performance of the Lambda functions. We observed that the Lambda cold start times are quite high. But there is a noticeable improvement compared to my &lt;a href="https://dev.to/aws-builders/lambda-function-using-docker-container-image-part-2-measuring-cold-and-warm-starts-of-lambda-function-with-java-21-runtime-mof"&gt;last measurements&lt;/a&gt;, which I did with Java 21. I refer to the article that describes AWS efforts to &lt;a href="https://aws.amazon.com/de/blogs/machine-learning/reducing-container-cold-start-times-using-soci-index-on-dlami-and-dlc/" rel="noopener noreferrer"&gt;reduce container cold start times&lt;/a&gt; in general.&lt;/p&gt;

&lt;p&gt;In the next part, we'll enable &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/" rel="noopener noreferrer"&gt;SnapStart on the Lambda functions using a container image&lt;/a&gt;. We'll also apply priming techniques and measure the performance of the Lambda functions for all scenarios.&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>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 8 AI Agents with Spring AI on AgentCore Runtime</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 05 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-24pa</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-24pa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-376f"&gt;part 7&lt;/a&gt;, we transformed our application into an agentic one. We replaced the search for YouTube and upcoming talks with the AgentCore Gateway Web Search Tool. Also, we used the Spring AI framework to implement the AI agent and AWS Lambda to host it. This might be sufficient for very simple agents, as AWS Lambda has its limitations of a 15-minute execution duration and isn't very efficient for agents spending a lot of time waiting on I/O. &lt;/p&gt;

&lt;p&gt;In this part of the series, we'll show how to host those AI agents on the Amazon Bedrock AgentCore Runtime instead. &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;AgentCore Runtime&lt;/a&gt; provides a secure, serverless, and purpose-built hosting environment for deploying and running AI agents or tools. I wrote an &lt;a href="https://dev.to/vkazulkin/series/34351"&gt;article series about AgentCore Runtime&lt;/a&gt;, which I refer to.&lt;/p&gt;

&lt;p&gt;There are multiple good articles that highlight the difference between hosting the agents on AWS Lambda vs Amazon Bedrock AgentCore Runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.virtuability.com/blog/2026-03-27-when-to-use-agentcore/" rel="noopener noreferrer"&gt;When to Use Bedrock AgentCore Runtime (vs Lambda)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gerardo.dev/en/lambda-microvms-vs-agentcore-runtime.html" rel="noopener noreferrer"&gt;Lambda MicroVMs vs AgentCore Runtime: When to Use Each for Production 
Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@liams_o/agentcore-vs-lambda-based-orchestration-choosing-your-next-architecture-e0a6ceccd072" rel="noopener noreferrer"&gt;AgentCore vs. Lambda-Based Orchestration. Choosing Your Next Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample application using AgentCore Gateway Web Search Tool and Spring AI hosted on Amazon Bedrock AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;The target architecture of our sample application looks like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm8lvbdn4dzarhg4quwqn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm8lvbdn4dzarhg4quwqn.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of hosting the AI Agents for searching for upcoming talks and YouTube videos in the Lambda function, we bundle them and deploy them together on AgentCore Runtime. We expose this agent via the HTTP protocol. In this case, bundling agents works fine. It's because we can parameterize our prompt to instruct the agent to search either for upcoming talks or for YouTube videos. There are cases where it's beneficial to have different agents and host them separately (on AgentCore Runtime). We still use a Lambda function per agent to invoke our agent hosted on AgentCore Runtime, using the AWS Java SDK.&lt;/p&gt;

&lt;p&gt;You can find the final version of the application in my &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;Here we find the following subdirectories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; The &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/agent" rel="noopener noreferrer"&gt;agent implementation&lt;/a&gt;. It contains the same business logic that we described in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-376f"&gt;part 7&lt;/a&gt;, but now it's a pure Spring Boot application. It should be &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/agent/Dockerfile" rel="noopener noreferrer"&gt;dockerized&lt;/a&gt; to be deployed on AgentCore Runtime. I gave a detailed explanation of how to do it in my article &lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-conference-application-on-agentcore-runtime/" rel="noopener noreferrer"&gt;Building AI Agents with Spring AI and Amazon Bedrock AgentCore – Part 5 Deploy MCP client for Conference application on AgentCore Runtime&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk" rel="noopener noreferrer"&gt;Infrastructure as Code&lt;/a&gt; based on AWS CDK for Java.  We covered it partially in part 7. In addition to AgentCore Gateway and Cognito stack, we now have to create &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk/src/main/java/dev/vkazulkin/agentcore/runtime/RuntimeStack.java" rel="noopener noreferrer"&gt;AgentCore Runtime stack&lt;/a&gt;. I also refer to my article &lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-conference-application-on-agentcore-runtime/" rel="noopener noreferrer"&gt;Deploy MCP client for Conference application on AgentCore Runtime&lt;/a&gt; for the detailed explanation of this step. After AgentCore Runtime is created, we have to grab its Runtime ARN. We'll see it in the console output, or we can find it on the AWS AgentCore Runtime service page.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/durable-functions-as-ai-agents-orchestrator" rel="noopener noreferrer"&gt;Agent orchestrator implementation&lt;/a&gt; with AWS Lambda durable function.  The biggest difference to the business logic from part 7 is the implementation of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/durable-functions-as-ai-agents-orchestrator/src/main/java/dev/vkazulkin/handler/UpcomingTalksExtractor.java" rel="noopener noreferrer"&gt;UpcomingTalksExtractor&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/durable-functions-as-ai-agents-orchestrator/src/main/java/dev/vkazulkin/handler/YouTubeVideosExtractor.java" rel="noopener noreferrer"&gt;YouTubeVideosExtractor&lt;/a&gt; Lambda functions.  They both inherit from the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/durable-functions-as-ai-agents-orchestrator/src/main/java/dev/vkazulkin/handler/BaseContentExtractor.java" rel="noopener noreferrer"&gt;BaseContentExtractor&lt;/a&gt;, which provides the &lt;em&gt;search&lt;/em&gt; method both Lambda functions use:
&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&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;searchTopic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
     &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxNumberOfResults&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clazz&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
           {
    "prompt":"Do a web search for the %s authored by %s %s. Provide maximum %d results. Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.",
    "resultType": "%s"
           }
       """&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTopic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
       &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxNumberOfResults&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&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;of&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;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"REGION"&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="no"&gt;AGENT_RUNTIME_ARN&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;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AGENTCORE_RUNTIME_ARN"&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="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="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;response&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;OBJECT_MAPPER&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;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clazz&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;First, we parameterize the prompt by providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the search topic (YouTube videos or upcoming talks)&lt;/li&gt;
&lt;li&gt;content author first and last name&lt;/li&gt;
&lt;li&gt;maximum number of results&lt;/li&gt;
&lt;li&gt;the result type (YouTubeVideos.class or UpcomingTalks.class depending on the search topic).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we use the AWS Java SDK to create an &lt;em&gt;InvokeAgentRuntimeRequest&lt;/em&gt; with the constructed prompt and AgentCore Runtime ARN, which we retrieve from an environment variable &lt;em&gt;AGENTCORE_RUNTIME_ARN&lt;/em&gt;. We set the value of this variable in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/durable-functions-as-ai-agents-orchestrator/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;. This value should correspond to the Runtime ARN we created in the IaC part described earlier. Here is an example of how to do it for the &lt;em&gt;YouTubeVideosExtractorFunction&lt;/em&gt; Lambda function:&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;YouTubeVideosExtractorFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YouTubeVideosAIAgentExtractor&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.vkazulkin.handler.YouTubeVideosExtractor::handleRequest&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;AGENTCORE_RUNTIME_ARN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:runtime/RuntimeForAuthorContentAgenticSearch-LBaMJZGabnk&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
            &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeRuntime&lt;/span&gt;
                &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Runtime*&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*WorkloadIdentity&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*CredentialProvider&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Token*&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Access*&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:*:*:*runtime*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to give this Lambda function permissions to invoke the AgentCore Runtime. We have to do the same for the &lt;em&gt;UpcomingTalksExtractorFunction&lt;/em&gt; Lambda function as well. After that, create a &lt;em&gt;BedrockAgentCoreClient&lt;/em&gt; client to execute this request. Lastly, we convert an LLM response in JSON format to the specified result type and return it.&lt;/p&gt;

&lt;p&gt;Once again, I gave a detailed explanation of this step in my article &lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-conference-application-on-agentcore-runtime/" rel="noopener noreferrer"&gt;Deploy MCP client for Conference application on AgentCore Runtime&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building, packaging, deploying, and testing our sample application
&lt;/h2&gt;

&lt;p&gt;Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;. The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files. Please follow the steps from &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-376f"&gt;part 7&lt;/a&gt; to test the application, as they are exactly the same. Our Lambda durable function executes exactly the same steps, so the output will be the same. The only difference is that I renamed the Lambda Durable function in this project to &lt;em&gt;AuthorContentExtractorAIAgentsOrchestrator&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;In this part of the series, we showed how to host AI agents, which our Lambda durable function orchestrates, on the 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>java</category>
      <category>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 7 AI Agents with Spring AI and AgentCore on AWS Lambda</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-376f</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-376f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b"&gt;part 6&lt;/a&gt; of the series, we moved the logic associated with each step into a separate Lambda function. We explored how to invoke another Lambda function within the durable step. Also, we showed how to invoke multiple Lambda functions in parallel.&lt;/p&gt;

&lt;p&gt;In this article, we'll transform our application into an agentic one. Currently, the search for YouTube and upcoming talks returns static content. We'll replace it with the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway Web Search Tool&lt;/a&gt;. As it's exposed via MCP, we need an MCP client to connect to it. For that, we'll use the &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI framework&lt;/a&gt;. First, we still use AWS Lambda to host the AI agents. In later parts, we'll show how to host those AI agents on &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;The question is whether it'd be appropriate to host our AI agents on AWS Lambda. Our agents are really simple and return a constant amount of data. They also have a nearly constant context size. We don't need the flexibility of Firecracker, including the ability to grow and shrink the CPU and memory usage of VMs in place. Also, our agents are stateless, so we don't need the AgentCore support of stateful streamable-HTTP MCP servers. For such agents, it's feasible to host them on AWS Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Spring AI framework
&lt;/h2&gt;

&lt;p&gt;I've written an entire article series on the &lt;a href="https://dev.to/vkazulkin/series/34771"&gt;Spring AI framework&lt;/a&gt; and have also heavily used this framework in my article series &lt;a href="https://vkazulkin.com/series/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore/" rel="noopener noreferrer"&gt;Building AI Agents with Spring AI and Amazon Bedrock AgentCore&lt;/a&gt;. I refer to them to understand the basics. Spring AI is based on &lt;a href="https://spring.io/projects/spring-boot" rel="noopener noreferrer"&gt;Spring Boot&lt;/a&gt;, but we have pure Lambda functions in our application instead. So, we need to have a Spring context to use Spring AI. How can we achieve it?&lt;/p&gt;

&lt;h2&gt;
  
  
  How to convert AWS Lambda to a Spring (Boot) Application
&lt;/h2&gt;

&lt;p&gt;I've written another article series about &lt;a href="https://dev.to/vkazulkin/series/34774"&gt;Spring Boot 3.4 applications on AWS Lambda&lt;/a&gt; where I introduced 3 approaches to convert AWS Lambda to a Spring (Boot) Application :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/serverless-java-container/wiki/Quick-start---Spring-Boot4" rel="noopener noreferrer"&gt;AWS Serverless Java Container for Spring Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-lambda-web-adapter" rel="noopener noreferrer"&gt;AWS Lambda Web Adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-cloud-function/reference/adapters/aws-intro.html" rel="noopener noreferrer"&gt;Spring Cloud Function for AWS Lambda&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I'll go for AWS Serverless Java Container for Spring Boot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application using AgentCore Gateway Web Search Tool and Spring AI hosted on AWS Lambda
&lt;/h2&gt;

&lt;p&gt;The target architecture of our sample application looks like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjlpqnavrgye3dqnrnw7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjlpqnavrgye3dqnrnw7.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I first copied the application that we created in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b"&gt;part 6&lt;/a&gt; into the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool&lt;/a&gt; repository. We explain step by step how to adjust our application. Please go through the content of part 6 to understand the sample application and the basic concepts of Lambda durable functions. There will be some changes in the Infrastructure as Code (IaC) part as well.&lt;/p&gt;

&lt;p&gt;First, let's import some important dependencies in the &lt;a&gt;pom.xml&lt;/a&gt;. Those include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aws-serverless-java-container-springboot4 (to use AWS Serverless Java Spring Boot 4 Container)&lt;/li&gt;
&lt;li&gt;spring-ai-starter-mcp-client (to use Spring AI MCP Client)&lt;/li&gt;
&lt;li&gt;spring-ai-starter-model-bedrock-converse (to use Spring AI Bedrock Converse API)&lt;/li&gt;
&lt;li&gt;others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, let's create the Spring Boot &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/controller/AuthorContentWebSearchExtractorController.java" rel="noopener noreferrer"&gt;AuthorContentWebSearchExtractorController&lt;/a&gt; controller:&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;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorContentWebSearchExtractorController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
&lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/author/content/youtubeVideos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestMethod&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="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&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;YouTubeVideos&lt;/span&gt; &lt;span class="nf"&gt;searchForYouTubeVideos&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;webSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"YouTube videos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nc"&gt;YouTubeVideos&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="o"&gt;}&lt;/span&gt;


   &lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/author/content/upcomingTalks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestMethod&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="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&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;UpcomingTalks&lt;/span&gt; &lt;span class="nf"&gt;searchForUpcomingTalks&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;webSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"upcoming talks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&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="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 define 2 (web search) GET methods, &lt;em&gt;searchForYouTubeVideos&lt;/em&gt; and &lt;em&gt;searchForUpcomingTalks&lt;/em&gt;, with the specified path and incoming and outgoing media types. They both receive the JSON representation of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/entity/Author.java" rel="noopener noreferrer"&gt;Author&lt;/a&gt; class in their HTTP body. We can also modify our application to receive first and last name as request parameters. For this, we also need to adjust both paths, for example, to /author/content/upcomingTalks/{firstname}/{lastname}. We'll take care of the implementation of the &lt;em&gt;websearch&lt;/em&gt; method later.&lt;/p&gt;

&lt;p&gt;The controller is a standard Spring Boot controller where we have access to the whole Spring Boot (web) functionality. &lt;/p&gt;

&lt;p&gt;Our Lambda durable function &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/AuthorContentExtractor.java" rel="noopener noreferrer"&gt;AuthorContentExtractor&lt;/a&gt; looks very similar to the function we defined in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b"&gt;part 6&lt;/a&gt;.  Here we invoke in parallel 2 Lambda functions: &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/UpcomingTalksWebSearchExtractor.java" rel="noopener noreferrer"&gt;UpcomingTalksWebSearchExtractor&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/YouTubeVideosWebSearchExtractor.java" rel="noopener noreferrer"&gt;YouTubeVideosWebSearchExtractor&lt;/a&gt;. We'll transform exactly those 2 functions, so they perform the agentic search for the content instead of returning the static one. When we invoke them, we pass an object of type &lt;em&gt;Author&lt;/em&gt; as a payload. Our Lambda durable function &lt;em&gt;AuthorContentExtractor&lt;/em&gt; receives this payload as its input. &lt;/p&gt;

&lt;p&gt;The question is how to transform this payload into the HTTP requests which 2 methods of the &lt;em&gt;AuthorContentWebSearchExtractorController&lt;/em&gt; receive. This is exactly the functionality that AWS Serverless Java Spring Boot 4 Container provides to us. This framework does this transformation automatically in case the input payload comes from Amazon API Gateway. The payload should be of either type &lt;em&gt;APIGatewayProxyRequestEvent&lt;/em&gt; (REST API) or type &lt;em&gt;APIGatewayV2HTTPEvent&lt;/em&gt; (HTTP API). For more information, please read the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html" rel="noopener noreferrer"&gt;Invoking a Lambda function using an Amazon API Gateway endpoint&lt;/a&gt; article. In such a case, we can use the generic Lambda handler &lt;em&gt;com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler&lt;/em&gt; to do the job. Nevertheless, AWS Serverless Java Spring Boot 4 Container offers the functionality to convert and proxy any request to one described above. In our sample application, we'll use the &lt;em&gt;APIGatewayProxyRequestEvent&lt;/em&gt; request event. Let's explain how to do it.&lt;/p&gt;

&lt;p&gt;Both Lambda functions, &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/UpcomingTalksWebSearchExtractor.java" rel="noopener noreferrer"&gt;UpcomingTalksWebSearchExtractor&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/YouTubeVideosWebSearchExtractor.java" rel="noopener noreferrer"&gt;YouTubeVideosWebSearchExtractor.java&lt;/a&gt;, recieve the input request of type &lt;em&gt;Author&lt;/em&gt;. The only difference is that they should convert this request into an &lt;em&gt;APIGatewayProxyRequestEvent&lt;/em&gt; request event and proxy it to different paths. We define those paths in the Lambda function itself. &lt;/p&gt;

&lt;p&gt;For the UpcomingTalksWebSearchExtractor Lambda function, it 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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalksWebSearchExtractor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AuthorContentWebSearchExtractor&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;protected&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getPath&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;"/author/content/upcomingTalks"&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;The path &lt;em&gt;/author/content/upcomingTalks&lt;/em&gt; exactly matches the path of the request mapping in the controller:&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;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/author/content/upcomingTalks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestMethod&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="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&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;UpcomingTalks&lt;/span&gt; &lt;span class="nf"&gt;searchForUpcomingTalks&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;webSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"upcoming talks"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this Lambda function will proxy the incoming request to the controller's &lt;em&gt;searchForUpcomingTalks&lt;/em&gt; method.&lt;/p&gt;

&lt;p&gt;For the YouTubeVideosWebSearchExtractor Lambda function, it 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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YouTubeVideosWebSearchExtractor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AuthorContentWebSearchExtractor&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;protected&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getPath&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;"/author/content/youtubeVideos"&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;The path &lt;em&gt;/author/content/youtubeVideos&lt;/em&gt; exactly matches the path of the request mapping in the controller:&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;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/author/content/youtubeVideos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestMethod&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="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&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;YouTubeVideos&lt;/span&gt; &lt;span class="nf"&gt;searchForYouTubeVideos&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;webSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"YouTube videos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nc"&gt;YouTubeVideos&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this Lambda function will proxy the incoming request to the controller's &lt;em&gt;searchForYouTubeVideos&lt;/em&gt; method.&lt;/p&gt;

&lt;p&gt;The generic business logic that performs the conversion is in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/handler/AuthorContentWebSearchExtractor.java" rel="noopener noreferrer"&gt;AuthorContentWebSearchExtractor&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;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorContentWebSearchExtractor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RequestStreamHandler&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;SpringBootLambdaContainerHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AwsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AwsProxyResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
&lt;span class="nc"&gt;SpringBootLambdaContainerHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAwsProxyHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&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="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;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
  &lt;span class="nc"&gt;OutputStream&lt;/span&gt; &lt;span class="n"&gt;outputStream&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="kd"&gt;throws&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;author&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;proxyRequestEvent&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;getAwsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proxy&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="n"&gt;context&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;body&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;getBody&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;printStream&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;PrintStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
        &lt;span class="kc"&gt;true&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="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;printStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;AwsProxyRequest&lt;/span&gt; &lt;span class="nf"&gt;getAwsProxyRequest&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;author&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;awsProxyRequest&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;AwsProxyRequest&lt;/span&gt; &lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;awsProxyRequest&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;awsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPath&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;getPath&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;awsProxyRequest&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="n"&gt;author&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;header&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;SingleValueHeaders&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&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="n"&gt;awsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHeaders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&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;headers&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;Headers&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&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="n"&gt;awsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMultiValueHeaders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&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;awsProxyRequestContext&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;AwsProxyRequestContext&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;apiGatewayRequestIdentity&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;ApiGatewayRequestIdentity&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;apiGatewayRequestIdentity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setApiKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blabla"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;awsProxyRequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIdentity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiGatewayRequestIdentity&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;awsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;awsProxyRequestContext&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;awsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;        
&lt;span class="o"&gt;}&lt;/span&gt;

 &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getPath&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 the code. As we see, our class implements the &lt;em&gt;RequestStreamHandler&lt;/em&gt; class. First, we instantiate the  &lt;em&gt;SpringBootLambdaContainerHandler&lt;/em&gt; handler with the proxy request and response type and the main entry point of the Spring Boot &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/Application.java" rel="noopener noreferrer"&gt;Application&lt;/a&gt; class. In the &lt;em&gt;handleRequest&lt;/em&gt; method, we convert the input stream into a JSON representation of the &lt;em&gt;Author&lt;/em&gt; object. We use it in the &lt;em&gt;getAwsProxyRequest&lt;/em&gt; method to create the &lt;em&gt;AwsProxyRequest&lt;/em&gt; object, which represents the &lt;em&gt;APIGatewayProxyRequestEvent&lt;/em&gt; request event. To do so, we set several properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP Method as GET (should be the same as the HTTP method of both controllers' methods).&lt;/li&gt;
&lt;li&gt;path (each of the 2 Lambda functions sets its own path as described above).&lt;/li&gt;
&lt;li&gt;HTTP body (we set the &lt;em&gt;Author&lt;/em&gt; object) as required by the 2 Lambda functions.&lt;/li&gt;
&lt;li&gt;header and multi-value headers with the content type of &lt;em&gt;application/json&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;some dummy settings for request content and API Gateway request identity. It leads to an error if we don't set them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After having created the &lt;em&gt;AwsProxyRequest&lt;/em&gt; object, we use the handler to proxy it. By doing so, the appropriate method of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/controller/AuthorContentWebSearchExtractorController.java" rel="noopener noreferrer"&gt;AuthorContentWebSearchExtractorController&lt;/a&gt; will be invoked. As a result, we get an object of the type &lt;em&gt;AwsProxyResponse&lt;/em&gt;, which contains the response of the controller's method. This response is either of type &lt;em&gt;UpcomingTalks&lt;/em&gt; or of type &lt;em&gt;YouTubeVideos&lt;/em&gt; depending on the method invoked. We get this response by invoking the &lt;em&gt;body&lt;/em&gt; method, and then we inject it into the output stream. With that, our Lambda durable function receives the response of the Lambda invocation.&lt;/p&gt;

&lt;p&gt;Now let's implement the &lt;em&gt;webSearch&lt;/em&gt; method of the controller. This method implements the web search for the given author content (either upcoming talks or YouTube videos) using the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway Web Search Tool&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to the Amazon Bedrock AgentCore Gateway Web Search Tool
&lt;/h2&gt;

&lt;p&gt;AI agents are transforming how organizations interact with information, but they face a fundamental limitation: their knowledge is frozen at training time. When a user asks about today’s stock prices or a software release that shipped an hour ago, an agent relying solely on its training data cannot provide accurate answers.&lt;/p&gt;

&lt;p&gt;Building custom web search integrations to solve this is costly — procuring third-party search APIs, managing keys and quotas and rate limits, parsing inconsistent result formats across providers, building snippet extraction logic so models get relevant passages instead of raw HTML, reasoning about where customer queries travel and how data might be retained, and maintaining freshness and coverage over time. Each of these is a project in itself.&lt;/p&gt;

&lt;p&gt;Web Search Tool on Amazon Bedrock AgentCore eliminates that complexity. It is a fully managed, MCP-compliant web search capability that lets your agents retrieve information from the web without any infrastructure overhead. It is available as a managed connector that you connect to your AgentCore Gateway. Agents discover it with a standard tools/list call and invoke it like any other Model Context Protocol tool. There are no search APIs to provision, no outbound credentials to manage, and no result-parsing glue to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up the Amazon Bedrock AgentCore Gateway Web Search Tool
&lt;/h2&gt;

&lt;p&gt;I refer to the article &lt;a href="https://aws.amazon.com/de/blogs/machine-learning/introducing-web-search-on-amazon-bedrock-agentcore/" rel="noopener noreferrer"&gt;Introducing Web Search on Amazon Bedrock AgentCore&lt;/a&gt;, which describes everything you need to set it up. Currently, there is no IaC CDK support for the Web Search Tool, but setting it up manually takes minutes.&lt;/p&gt;

&lt;p&gt;As a small help, I provided the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk" rel="noopener noreferrer"&gt;spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk&lt;/a&gt; repository that uses AWS CDK in Java. Please execute the Cognio and Gateway stacks. Then, manually add a Gateway MCP Target with the Web Search tool connector as shown in the pictures above.&lt;/p&gt;

&lt;p&gt;The created &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway.html" rel="noopener noreferrer"&gt;Bedrock AgentCore Gateway&lt;/a&gt; should look similar to this one:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdvoq8yptgc2kfioh96m.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdvoq8yptgc2kfioh96m.png" alt=" " width="795" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll need the Gateway Resource ARN and Gateway Resource URL later. Then we need to create the AgentCore Target by selecting the MCP Target and &lt;em&gt;Web Search tool&lt;/em&gt; in the &lt;em&gt;Connectors&lt;/em&gt; section:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4tbf1gomxha9p087xf9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4tbf1gomxha9p087xf9.png" alt=" " width="800" height="470"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wsyi3zi4pr7w87mqbqe.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wsyi3zi4pr7w87mqbqe.png" alt=" " width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll need to define an IAM role with the following policy, as described in the article above:&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;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InvokeWebSearchTool"&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="s2"&gt;"bedrock-agentcore:InvokeWebSearch"&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:bedrock-agentcore:us-east-1:aws:tool/web-search.v1"&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;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InvokeGateway"&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="s2"&gt;"bedrock-agentcore:InvokeGateway"&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;"{YOUR_AGENTCORE_GATEWAY_ARN}"&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 &lt;em&gt;{YOUR_AGENTCORE_GATEWAY_ARN}&lt;/em&gt; with the ARN of your created AgentCore Gateway.&lt;/p&gt;

&lt;p&gt;Another question is how to set up the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html" rel="noopener noreferrer"&gt;inbound authorization&lt;/a&gt; of our AgentCore Gateway. We can put it completely public, which is not recommended. Another alternative is to use &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html" rel="noopener noreferrer"&gt;IAM-based inbound authorization&lt;/a&gt;. In this article, we'll use &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html#gateway-inbound-auth-jwt" rel="noopener noreferrer"&gt;JSON Web Token (JWT)-based inbound authorization&lt;/a&gt; with Amazon Cognito as an identity provider as we see in the picture above. I won't go into the details of how to create and configure the Cognito User (Client) Pool, but refer to my following articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-2-deploy-conference-search-application-on-the-agentcore-runtime/" rel="noopener noreferrer"&gt;Deploy Conference Search application on AgentCore Runtime&lt;/a&gt;. Here I provided IaC and an explanation of how to create the Cognito User (Client) Pool.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-conference-application-via-agentcore-gateway/" rel="noopener noreferrer"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt;. Here I provided IaC and an explanation of how to configure the inbound authorization for the AgentCore Gateway with Amazon Cognito as an identity provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, please look into the Cognito and Gateway stack of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-ai-agents-orchestrator-java-25/spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk" rel="noopener noreferrer"&gt;spring-ai-2.0-ai-agents-on-agentcore-runtime/cdk&lt;/a&gt;&amp;nbsp;repository for an example of how to set it up.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to implement the Amazon Bedrock AgentCore Gateway Web Search Tool using Spring AI
&lt;/h2&gt;

&lt;p&gt;I've already covered how to talk to an LLM and use the MCP Client to connect to tools with Spring AI in my following articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-local-mcp-client-for-conference-application/" rel="noopener noreferrer"&gt;Develop local MCP client for Conference application&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vkazulkin.com/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-4-provide-mcp-tools-for-conference-application-via-agentcore-gateway/" rel="noopener noreferrer"&gt;Provide MCP tools for Conference application via AgentCore Gateway&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please go through those articles first to understand the basics, as we'll reuse much of the business logic. Let's first configure the AgentCore Gateway resource URL and Cognito settings in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; file:&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;amazon.bedrock.agentcore.gateway.base.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://web-search-gateway-XXX.gateway.bedrock-agentcore.us-east-1.amazonaws.com&lt;/span&gt;
&lt;span class="py"&gt;amazon.bedrock.agentcore.gateway.endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/mcp&lt;/span&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;UserPoolForAuthorContentAgenticSearch&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;UserClientPoolForAuthorContentAgenticSearch&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;ResourceServerIdAuthorContentAgenticSearch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please split this URL into 2 parts: the base URL and the endpoint, which is always &lt;em&gt;/mcp&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The implementation of the &lt;em&gt;webSearch&lt;/em&gt; method in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/src/main/java/dev/vkazulkin/controller/AuthorContentWebSearchExtractorController.java" rel="noopener noreferrer"&gt;AuthorContentWebSearchExtractorController&lt;/a&gt; controller 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;private&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;webSearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&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;searchTopic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxNumberOfResults&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clazz&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="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;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;sync&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="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="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="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&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;-&amp;gt;&lt;/span&gt; &lt;span class="no"&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mcpToolCallbackProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SyncMcpToolCallbackProvider&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            Search for the %s given by %s %s. Provide maximum %d results.
            Your response should be in JSON format.
            Do not include any explanations, only provide a RFC8259 compliant JSON response 
            following this format without deviation.
            """&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTopic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;maxNumberOfResults&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="n"&gt;mcpToolCallbackProvider&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;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clazz&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;This is a generic method, which takes the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;object of type &lt;em&gt;Author&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;search topic ("search for upcoming talks" or "search for YouTube videos" in our case)&lt;/li&gt;
&lt;li&gt;maximum number of results returned by the Web Search tool. Valid range: 1-25. Defaults to 10.&lt;/li&gt;
&lt;li&gt;result type (&lt;em&gt;UpcomingTalks&lt;/em&gt; or &lt;em&gt;YouTubeVideos&lt;/em&gt; in our case)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, get the authentication token from Amazon Cognito. We can remove this part, including setting this token in the header of the HTTP Streamable Client, in case our AgentCore isn't secured by the JWT (either public or we use IAM authorization). Next, we create the MCP client and initialize it. After that, we list the MCP tools available; in our case, only the &lt;em&gt;WebSearch&lt;/em&gt; tool. Then, we create &lt;em&gt;MCPToolCallbackProvider&lt;/em&gt; with the MCP client. Next, we construct the parametrized prompt by passing the author, the search topic, and the number of results. We also advise an LLM to return the response in JSON format, which our Lambda functions require.&lt;/p&gt;

&lt;p&gt;Finally, we use the &lt;em&gt;ChatClient&lt;/em&gt; from Spring AI to pass the prompt, the tools, and the result type. We implement the last one by using the &lt;em&gt;entity&lt;/em&gt; method, which tries to provide &lt;a href="https://spring.io/blog/2024/05/09/spring-ai-structured-output" rel="noopener noreferrer"&gt;the structured LLM output&lt;/a&gt;. Once again, for more details about this code, I refer to my articles mentioned above. We invoke the &lt;em&gt;websearch&lt;/em&gt; method from the controller's methods &lt;em&gt;searchForUpcomingTalks&lt;/em&gt; and &lt;em&gt;searchForYouTubeVideos&lt;/em&gt; with different parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  IaC for the sample application using AgentCore Gateway Web Search Tool and Spring AI hosted on AWS Lambda
&lt;/h2&gt;

&lt;p&gt;The IaC part in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-spring-ai-agentcore-gateway-web-search-tool/template.yaml" rel="noopener noreferrer"&gt;AWS SAM Template&lt;/a&gt; matches what we described in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b"&gt;part 6&lt;/a&gt;. There are, of course, some differences, as we need to give our 2 Lambda functions, which implement the AI agents, more permissions. We need these permissions to grant those Lambda functions access to the Cognito, AgentCore, and Bedrock services. Here is an example of the &lt;em&gt;UpcomingTalksWebSearchExtractorFunction&lt;/em&gt; Lambda function declaration:&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;UpcomingTalksWebSearchExtractorFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UpcomingTalksWebSearchExtractor&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.vkazulkin.handler.UpcomingTalksWebSearchExtractor::handleRequest&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
          &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AllowedCognitoActions&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cognito-idp:ListUserPools&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cognito-idp:ListUserPoolClients&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cognito-idp:DescribeUserPoolClient&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeGateway&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:InvokeGateway&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*WorkloadIdentity&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*CredentialProvider&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Token*&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Access*&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:*:*:*gateway*'&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeRuntime&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Runtime*&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*WorkloadIdentity&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*CredentialProvider&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Token*&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock-agentcore:*Access*&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:*:*:*runtime*'&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeBedrock&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock:InvokeModel&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock:InvokeModelWithResponseStream&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock:Converse&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bedrock:ConverseStream&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;GetRequest&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;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;RestApiId&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;MyApi&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/author/content/upcomingTalks&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The declaration of the &lt;em&gt;YouTubeVideosWebSearchExtractorFunction&lt;/em&gt; Lambda function looks very similar. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building, packaging, deploying, and testing our sample application
&lt;/h2&gt;

&lt;p&gt;Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AuthorContentWebSearchExtractor&lt;/em&gt; function, and go to the "Test" tab.&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkxvs5zu1ksgmkgllhdz.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkxvs5zu1ksgmkgllhdz.png" alt=" " width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's also look at event history:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbiwlq3owjpink5og6thi.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbiwlq3owjpink5og6thi.png" alt=" " width="800" height="479"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv55gkizmklket5koqgk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv55gkizmklket5koqgk.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see that this matches what we implemented. Our Lambda durable function invokes 2 Lambda functions in parallel to search for YouTube videos and upcoming talks (each in an individual branch). Then, it awaits the results of both invocations because we defined in the parallel configuration that we wait until all invocations are completed. Then the last Lambda function to write the author content into the file gets invoked. This process is the same as we described in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b"&gt;part 6&lt;/a&gt;, as the workflows remained the same. We only changed the implementation of 2 Lambda functions responsible for the content search. Now we use the AgentCore Gateway Web Search Tool for it.&lt;/p&gt;

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

&lt;p&gt;In this article, we transformed our application into an agentic one. We replaced the search for YouTube and upcoming talks with the AgentCore Gateway Web Search Tool. Also, we used the Spring AI framework. We used AWS Lambda to host the AI agents. &lt;/p&gt;

&lt;p&gt;As we saw, Lambda durable functions provide a lot of building blocks like synchronous and asynchronous steps, invocation of another Lambda function, invoke async, waits, and callbacks (and combinations of both, like wait for the callback), and parallel execution to orchestrate the AI agents and multi-step workflows. We only showed one simple example of how to orchestrate the AI agent by using another Lambda function invocation and parallel execution.&lt;/p&gt;

&lt;p&gt;In the next part, we'll show how to host those AI agents on the AgentCore Runtime instead.&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>java</category>
      <category>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 6 Invoke a Lambda function and parallel execution</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 21 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-483b</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously. After that, in parts &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-oo4"&gt;3&lt;/a&gt;, &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1f1n"&gt;4&lt;/a&gt;, and &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-3kmg"&gt;5&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement wait for callback, wait for condition, and map operations.&lt;/p&gt;

&lt;p&gt;Still, our Lambda durable function itself contains too much business logic. Ideally, it should simply be the orchestrator and contain as little business logic as possible. That's why, in this part, we'll move the logic associated with each step into a separate Lambda function. An additional advantage of this approach is that it allows us to apply individual settings to those functions, like setting the right memory and granting the exact permissions they need. We'll explore how to invoke another Lambda function within the durable step. Also, we'll show how to invoke multiple Lambda functions in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with invoking another Lambda function and performing parallel execution
&lt;/h2&gt;

&lt;p&gt;Let's explore invoking another Lambda function and parallel execution. We get access to them through &lt;em&gt;DurableContext&lt;/em&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invoke - the invoke operation calls another Lambda function and waits for its result. It supports both durable functions and standard on-demand Lambda functions. When you call &lt;em&gt;ctx.invoke()&lt;/em&gt; or &lt;em&gt;ctx.invokeAsync()&lt;/em&gt;, the SDK checkpoints the start of the operation, and the durable functions backend invokes the target Lambda function. On replay, the SDK returns the checkpointed result without invoking the target again.&lt;/li&gt;
&lt;li&gt;parallel - parallel runs multiple independent branches concurrently, each in its own child context. Branches are registered via &lt;em&gt;branch()&lt;/em&gt; and execute immediately (respecting maxConcurrency). The operation completes when all branches finish or completion criteria are met.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I first copied the application that we created in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; into the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-as-orchestrator-java-25&lt;/a&gt; repository. We explain step by step how to adjust our application. Please go through the content of part 2 to understand the sample application and the basic concepts of Lambda durable functions. There will be some changes in the Infrastructure as Code (IaC) part as well. I'll leave out the wait for callbacks that we implemented in part 2. You can copy this logic into the Lambda durable function orchestrator, if you will.&lt;/p&gt;

&lt;p&gt;We'll move the business logic for 3 individual steps out of our Lambda durable function &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/AuthorContentExtractor.java" rel="noopener noreferrer"&gt;AuthorContentExtractor&lt;/a&gt; to 3 different Lambda functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/UpcomingTalksExtractor.java" rel="noopener noreferrer"&gt;Search for upcoming talks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/YouTubeVideosExtractor.java" rel="noopener noreferrer"&gt;Search for YouTube videos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/WriteAuthorContentToFile.java" rel="noopener noreferrer"&gt;Write author content to file&lt;/a&gt;&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4p2ieihwx5rlmxwnco6j.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4p2ieihwx5rlmxwnco6j.png" alt=" " width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The implementation of all 3 Lambda functions is trivial. We'll focus on the change in the Lambda durable function &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/AuthorContentExtractor.java" rel="noopener noreferrer"&gt;AuthorContentExtractor&lt;/a&gt; and in the IaC located in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We already showed in part 2 how to execute the first 2 Lambda functions asynchronously. This time, we'll show how to execute them in parallel. Here is how the complete code of the &lt;em&gt;AuthorContentExtractor&lt;/em&gt; Lambda durable function looks:&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;AuthorContentExtractor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DurableHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuthorContent&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;UPCOMING_TALKS_EXTRACTOR_FUNCTION_ARN&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;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UpcomingTalksExtractorFunctionArn"&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;String&lt;/span&gt; &lt;span class="no"&gt;YOUTUBE_VIDEOS_EXTRACTOR_FUNCTION_ARN&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;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"YouTubeVideosExtractorFunctionArn"&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;String&lt;/span&gt; &lt;span class="no"&gt;WRITE_AUTHOR_CONTENT_TO_FILE_FUNCTION_ARN&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;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WriteAuthorContentToFileFunctionArn"&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;AuthorContent&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DurableContext&lt;/span&gt; &lt;span class="n"&gt;ctx&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ParallelConfig&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;maxConcurrency&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;nestingType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NestingType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NESTED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completionConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CompletionConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allCompleted&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;invokeConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;InvokeConfig&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;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;parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"parallel-search"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&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;upcomingTalksFuture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-parallel-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;branchCtx&lt;/span&gt; &lt;span class="o"&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="n"&gt;branchCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;           
             &lt;span class="no"&gt;UPCOMING_TALKS_EXTRACTOR_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
             &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;invokeConfig&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;youtubeVideosFuture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-parallel-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;branchCtx&lt;/span&gt; &lt;span class="o"&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="n"&gt;branchCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                
             &lt;span class="no"&gt;YOUTUBE_VIDEOS_EXTRACTOR_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
             &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;invokeConfig&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upcomingTalksFuture&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;youtubeVideosFuture&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authorContent&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;AuthorContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"writeAuthorContentToFile-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WRITE_AUTHOR_CONTENT_TO_FILE_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;authorContent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Void&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="n"&gt;invokeConfig&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;authorContent&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;Let's go step by step through the code. First, we pass the ARNs of all 3 Lambda functions via environment variables. We set those for the Lambda durable function &lt;em&gt;AuthorContentExtractorFunction&lt;/em&gt; in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/template.yaml" rel="noopener noreferrer"&gt;AWS SAM template&lt;/a&gt;. Also, we give our Lambda durable function the required permissions to invoke the other 3 Lambda functions:&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;AuthorContentExtractorFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AuthorContentExtractorOrchestrator&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.vkazulkin.handler.AuthorContentExtractor::handleRequest&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;UpcomingTalksExtractorFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;UpcomingTalksExtractorFunction.Arn&lt;/span&gt;
          &lt;span class="na"&gt;YouTubeVideosExtractorFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;YouTubeVideosExtractorFunction.Arn&lt;/span&gt;
          &lt;span class="na"&gt;WriteAuthorContentToFileFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;WriteAuthorContentToFileFunction.Arn&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
          &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InvokeLambda&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lambda:InvokeFunction&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;YouTubeVideosExtractorFunction.Arn&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${YouTubeVideosExtractorFunction.Arn}:*'&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;UpcomingTalksExtractorFunction.Arn&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${UpcomingTalksExtractorFunction.Arn}:*'&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;WriteAuthorContentToFileFunction.Arn&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${WriteAuthorContentToFileFunction.Arn}:*'&lt;/span&gt;
&lt;span class="s"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we invoke &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/UpcomingTalksExtractor.java" rel="noopener noreferrer"&gt;UpcomingTalksExtractor&lt;/a&gt; and &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/src/main/java/dev/vkazulkin/handler/YouTubeVideosExtractor.java" rel="noopener noreferrer"&gt;YouTubeVideosExtractor&lt;/a&gt; Lambda functions in parallel, we create &lt;em&gt;ParallelConfig&lt;/em&gt;. Here we set the maximum concurrency value and nesting type. There are 2  nesting types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nested&lt;/strong&gt; (the default one). Create CONTEXT operations for each branch/iteration with full checkpointing. Operations within each branch/iteration are wrapped in their own context. &lt;strong&gt;Observability&lt;/strong&gt;: High - each branch/iteration appears as a separate operation in execution history. &lt;strong&gt;Cost&lt;/strong&gt;: Higher - consumes more operations due to CONTEXT creation overhead. &lt;strong&gt;Scale&lt;/strong&gt;: Lower maximum iterations due to operation limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat&lt;/strong&gt;.  Skip CONTEXT operations for branches/iterations using virtual contexts. Operations execute directly without individual context wrapping. &lt;strong&gt;Observability&lt;/strong&gt;: Lower - branches/iterations don't appear as separate operations. &lt;strong&gt;Cost&lt;/strong&gt;: ~30% lower - reduces operation consumption by skipping CONTEXT overhead. &lt;strong&gt;Scale&lt;/strong&gt;: Higher maximum iterations possible within operation limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also set the &lt;em&gt;CompletionConfig&lt;/em&gt;. Here, there are many possibilities from which we can choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;allCompleted&lt;/strong&gt; (we use it). All items run regardless of failures. Failures are captured per-item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;allSuccessful&lt;/strong&gt;. All items must succeed. Zero failures tolerated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;firstSuccessful&lt;/strong&gt;. Complete as soon as the first item succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;minSuccessful&lt;/strong&gt;. Complete when the specified number of items have succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;toleratedFailureCount&lt;/strong&gt;. Complete when more than the specified number of failures have occurred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;toleratedFailurePercentage&lt;/strong&gt;. Complete when the failure percentage exceeds the specified threshold (0.0 to 1.0).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we create an &lt;em&gt;InvocationConfig&lt;/em&gt; for each individual Lambda function invocation that we can optionally set. Here we go with the default configuration. But we additionally set the tenant ID, which is used to isolate execution state for different tenants. It's required when invoking &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation.html" rel="noopener noreferrer"&gt;multi-tenant Lambda functions&lt;/a&gt;. So far, we covered the following parts:&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ParallelConfig&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;maxConcurrency&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;nestingType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NestingType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NESTED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completionConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CompletionConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allCompleted&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;invokeConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;InvokeConfig&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;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, let's implement the Lambda function invocation in parallel:&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;parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"parallel-search"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&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;upcomingTalksFuture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-parallel-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;branchCtx&lt;/span&gt; &lt;span class="o"&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="n"&gt;branchCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;           
             &lt;span class="no"&gt;UPCOMING_TALKS_EXTRACTOR_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
             &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;invokeConfig&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;youtubeVideosFuture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-parallel-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;branchCtx&lt;/span&gt; &lt;span class="o"&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="n"&gt;branchCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                
             &lt;span class="no"&gt;YOUTUBE_VIDEOS_EXTRACTOR_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
             &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;invokeConfig&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallel&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upcomingTalksFuture&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;youtubeVideosFuture&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authorContent&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;AuthorContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&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 invoke the &lt;em&gt;parallel&lt;/em&gt; method on the &lt;em&gt;DurableContext&lt;/em&gt; and pass the &lt;em&gt;ParallelConfig&lt;/em&gt;. We get back the &lt;em&gt;ParallelDurableFuture&lt;/em&gt;, with which we can start a branch. To do this, we invoke the &lt;em&gt;branch&lt;/em&gt; method for each individual Lambda invocation and specify the result type. In each branch, we call the &lt;em&gt;invoke&lt;/em&gt; method on the &lt;em&gt;BranchContext&lt;/em&gt;. Here we pass the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt; - the unique operation name within this context. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;functionName&lt;/strong&gt; - the ARN or name of the Lambda function to invoke.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;payload&lt;/strong&gt; - the input payload to send to the target function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;resultType&lt;/strong&gt; - the result class for deserialization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We get back the &lt;em&gt;DurableFuture&lt;/em&gt; of the &lt;em&gt;resultType&lt;/em&gt; (in our case &lt;em&gt;UpcomingTalks&lt;/em&gt; or &lt;em&gt;YouTubeVideos&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;When we invoke the blocking &lt;em&gt;get&lt;/em&gt; method on the &lt;em&gt;ParallelDurableFuture&lt;/em&gt; to get the results with respect to the &lt;em&gt;ParallelConfig&lt;/em&gt;. Then, we invoke the &lt;em&gt;get&lt;/em&gt; method on &lt;em&gt;DurableFuture&lt;/em&gt; to get the individual results of both Lambda invocations. Finally, we create the _ AuthorContent _ with these results.&lt;/p&gt;

&lt;p&gt;The last step is to invoke the Lambda function, which is responsible for writing the author content into the file:&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;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"writeAuthorContentToFile-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WRITE_AUTHOR_CONTENT_TO_FILE_FUNCTION_ARN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;authorContent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Void&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="n"&gt;invokeConfig&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we explained in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt;, we use S3 Files here. The whole part on how to configure S3 Files and give the Lambda function access to it has now moved from the &lt;em&gt;AuthorContentExtractorOrchestrator&lt;/em&gt; durable Lambda function to the &lt;em&gt;WriteAuthorContentToFileFunction&lt;/em&gt; Lambda function. See the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-as-orchestrator-java-25/template.yaml" rel="noopener noreferrer"&gt;SAM template&lt;/a&gt; for more detail.&lt;/p&gt;

&lt;p&gt;Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AuthorContentExtractorOrchestrator&lt;/em&gt; function, and go to the "Test" tab.&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fftjhvn72z4q0dpgstpfi.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fftjhvn72z4q0dpgstpfi.png" alt=" " width="798" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's also look at event history:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrlfzv8c4kg5devzuw5m.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrlfzv8c4kg5devzuw5m.png" alt=" " width="800" height="562"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv55gkizmklket5koqgk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv55gkizmklket5koqgk.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see that this matches what we implemented. Our Lambda durable function invokes in parallel 2 Lambda functions to search for YouTube videos and upcoming talks (each in an individual branch). Then, it awaits the results of both invocations because we defined in the parallel configuration that we wait until all invocations are completed. Then the last Lambda function to write the author content into the file gets invoked.&lt;/p&gt;

&lt;p&gt;One general question at the end: is the Lambda function invocation from another Lambda function considered an anti-pattern? Or should we put an API Gateway in between instead? My answer is that with built-in fault-tolerance for AWS Lambda durable functions, it's now safe to invoke a Lambda function directly.&lt;/p&gt;

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

&lt;p&gt;In this part of the series, we moved the logic associated with each step into a separate Lambda function. We explored how to invoke another Lambda function within the durable step. Also, we showed how to invoke multiple Lambda functions in parallel.&lt;/p&gt;

&lt;p&gt;In the next part of the series, we'll transform our application into an agentic one. Currently, the search for YouTube and upcoming talks returns static content. We'll replace it with the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway Web Search Tool&lt;/a&gt;. As it's exposed via MCP, we need an MCP client to connect to it. For that, we'll use the &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI framework&lt;/a&gt;. First, we still use AWS Lambda to host the AI agents. In later parts, we'll show how to host those AI agents on the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt; instead.&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>java</category>
      <category>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>s3files</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 5 Map and child context</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-3kmg</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-3kmg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously.  Later, in parts &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-oo4"&gt;3&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1f1n"&gt;4&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement wait for callback and wait for condition. &lt;/p&gt;

&lt;p&gt;In this part, we'll explore map and child context operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with map and child context
&lt;/h2&gt;

&lt;p&gt;Let's explore the &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java/blob/main/docs/core/child-contexts.md" rel="noopener noreferrer"&gt;child context&lt;/a&gt; operation first.  Child contexts run an isolated stream of work with their own operation counter and checkpoint log. They support the full range of durable operations — step, wait, invoke, createCallback, and nested child contexts.  In our sample application, we'll add a &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java/blob/main/docs/core/map.md" rel="noopener noreferrer"&gt;map&lt;/a&gt; operation, which uses a child context underneath. We get access to it through &lt;em&gt;DurableContext&lt;/em&gt;. A &lt;em&gt;Map&lt;/em&gt; operation applies a function to each item in a collection concurrently, with each item running in its own child context.&lt;/p&gt;

&lt;p&gt;Now let's add an artificial use case: for each YouTube video found, we add it to the author's YouTube playlist. I first copied the application that we created in &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-java-25" rel="noopener noreferrer"&gt;part 2&lt;/a&gt; into the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;The main business logic we have to add is in the &lt;em&gt;saveYouTubeVideosToPlayList&lt;/em&gt; method of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25/src/main/java/dev/vkazulkin/handler/AbstractAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AbstractAuthorContentExtractor&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="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;saveYouTubeVideosToPlayList&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DurableContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;YouTubeVideos&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&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;youtubePlayList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;findOrCreateYouTubePlayListForTheAuthor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&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;mapConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MapConfig&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;nestingType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NestingType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NESTED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxConcurrency&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;completionConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CompletionConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allCompleted&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;mapResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"saveYouTubeVideoToPlayList-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
      &lt;span class="nc"&gt;Boolean&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="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;youtubeVideo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childCtx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&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="n"&gt;childCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"saveYouTubeVideoToPlayList-step-"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Boolean&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;saveYouTubeVideoToPlayList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;youtubeVideo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtubePlayList&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;getStepConfig&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="n"&gt;mapConfig&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="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;findOrCreateYouTubePlayListForTheAuthor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// Use YouTube API to log in to the YouTube Account and search for the author's playlist&lt;/span&gt;
     &lt;span class="c1"&gt;// and return it if it already exists or create a new one&lt;/span&gt;
     &lt;span class="c1"&gt;// move this logic into a separate Lambda function and invoke it as a durable step&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&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;Boolean&lt;/span&gt; &lt;span class="nf"&gt;saveYouTubeVideoToPlayList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;YouTubeVideo&lt;/span&gt; &lt;span class="n"&gt;youtubeVideo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;playListId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//Use YouTube API to save the video URL to the playlist. &lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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 invoke this &lt;em&gt;saveYouTubeVideosToPlayList&lt;/em&gt; method in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25/src/main/java/dev/vkazulkin/handler/AuthorContentExtractor.java" rel="noopener noreferrer"&gt;AuthorContentExtractor&lt;/a&gt; or &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25/src/main/java/dev/vkazulkin/handler/AsyncAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AsyncAuthorContentExtractor&lt;/a&gt; classes.&lt;/p&gt;

&lt;p&gt;First of all, I won't use the YouTube API to create or retrieve the YouTube playlist for the given author and store each found video there. I use some static output, as the goal of this article is to demonstrate the functionality of the &lt;em&gt;map&lt;/em&gt; operation. &lt;/p&gt;

&lt;p&gt;Let's go step by step through this code. &lt;em&gt;Map&lt;/em&gt; applies a function to each item in a collection concurrently, with each item running in its own child context. Results are collected into a &lt;em&gt;MapResult&lt;/em&gt; that maintains input order. Each item's function receives its own DurableContext, so you can use any durable operation (step(), wait(), invoke(), etc.) inside the map function.  The function passed to map() is a &lt;em&gt;MapFunction&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="nd"&gt;@FunctionalInterface&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MapFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;O&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DurableContext&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index parameter is the zero-based position of the item in the input collection, useful for naming operations or correlating results. For each found YouTube video, we invoke the &lt;em&gt;saveYouTubeVideoToPlayList&lt;/em&gt; method, which returns the result of type &lt;em&gt;Boolean&lt;/em&gt;. In our case, it is always true, indicating that the video was stored in the playlist. &lt;/p&gt;

&lt;p&gt;The input collection must have deterministic iteration order. List, LinkedList, and TreeSet are accepted. HashSet and unordered map views are not, leading to the IllegalArgumentException. That's why I changed the implementation of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-map-and-child-context-java-25/src/main/java/dev/vkazulkin/entity/YouTubeVideos.java" rel="noopener noreferrer"&gt;YouTubeVideos&lt;/a&gt; class to use List instead of Set there.&lt;/p&gt;

&lt;p&gt;Next, let's explore &lt;em&gt;MapConfig&lt;/em&gt; in more detail. There are several main settings there:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;maxConcurrency&lt;/strong&gt; controls how many items execute concurrently. When set, items beyond the limit are queued and started as earlier items complete. The default is null (unlimited).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;nestingType&lt;/strong&gt;, with &lt;strong&gt;Nested&lt;/strong&gt; as the default. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nested&lt;/strong&gt;. Create CONTEXT operations for each branch/iteration with full checkpointing. Operations within each branch/iteration are wrapped in their own context. &lt;strong&gt;Observability&lt;/strong&gt;: High - each branch/iteration appears as a separate operation in execution history. &lt;strong&gt;Cost&lt;/strong&gt;: Higher - consumes more operations due to CONTEXT creation overhead. &lt;strong&gt;Scale&lt;/strong&gt;: Lower maximum iterations due to operation limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat&lt;/strong&gt;.  Skip CONTEXT operations for branches/iterations using virtual contexts. Operations execute directly without individual context wrapping. &lt;strong&gt;Observability&lt;/strong&gt;: Lower - branches/iterations don't appear as separate operations. &lt;strong&gt;Cost&lt;/strong&gt;: ~30% lower - reduces operation consumption by skipping CONTEXT overhead. &lt;strong&gt;Scale&lt;/strong&gt;: Higher maximum iterations possible within operation limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CompletionConfig&lt;/strong&gt; controls when the map operation stops starting new items. Here, there are many possibilities from which we can choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;allCompleted&lt;/strong&gt; (we use it). All items run regardless of failures. Failures are captured per-item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;allSuccessful&lt;/strong&gt;. All items must succeed. Zero failures tolerated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;firstSuccessful&lt;/strong&gt;. Complete as soon as the first item succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;minSuccessful&lt;/strong&gt;. Complete when the specified number of items have succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;toleratedFailureCount&lt;/strong&gt;. Complete when more than the specified number of failures have occurred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;toleratedFailurePercentage&lt;/strong&gt;. Complete when the failure percentage exceeds the specified threshold (0.0 to 1.0).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When early termination is triggered, items that were never started have SKIPPED status with null for both result and error in the MapResult.&lt;/p&gt;

&lt;p&gt;Each &lt;em&gt;MapResultItem&lt;/em&gt; contains the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;status&lt;/strong&gt;: SUCCEEDED, FAILED, or SKIPPED.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;result&lt;/strong&gt;:  the result value, or null if failed/skipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;error&lt;/strong&gt;: the error details as MapError, or null if succeeded/skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Failed items store error details as &lt;em&gt;MapError&lt;/em&gt;, a serializable record that survives checkpoint-and-replay cycles with the following methods that can be invoked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;errorType&lt;/strong&gt;:  Fully qualified exception class name (e.g., java.lang.RuntimeException).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;errorMessage&lt;/strong&gt;: The exception message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stackTrace&lt;/strong&gt;:  Stack trace frames as a list of strings, or null.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One item's failure does not prevent other items from completing. &lt;em&gt;MapResult&lt;/em&gt; captures the failed items at their corresponding index.&lt;/p&gt;

&lt;p&gt;Map operations are fully durable. On replay after interruption:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Completed items return cached results without re-execution.&lt;/li&gt;
&lt;li&gt;Incomplete items resume from their last checkpoint.&lt;/li&gt;
&lt;li&gt;Items that never started execute fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small results (&amp;lt; 256KB) are checkpointed directly, whereas large results are reconstructed from individual child context checkpoints on replay.&lt;/p&gt;

&lt;p&gt;That's it. There are no changes in the Infrastructure as Code part that we need to make. Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AuthorContentExtractor&lt;/em&gt; or &lt;em&gt;AsyncAuthorContentExtractor&lt;/em&gt; function, and go to the "Test" tab.&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsj1489ohgljy9lud4gsq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsj1489ohgljy9lud4gsq.png" alt=" " width="799" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we see that the operation with the name &lt;em&gt;saveYouTubeVideoToPlayList-step&lt;/em&gt; and the subtype &lt;em&gt;Map&lt;/em&gt;. We also see 2 iterations (for each of the 2 YouTube videos found) with the subtype &lt;em&gt;MapIteration&lt;/em&gt;. Each iteration is executed as an individual durable step.&lt;/p&gt;

&lt;p&gt;Let's also look at event history:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtqhquzb7wlhtd03mazx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtqhquzb7wlhtd03mazx.png" alt=" " width="800" height="579"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnpzsq3fnnwjw04xhj8jk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnpzsq3fnnwjw04xhj8jk.png" alt=" " width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we see all events (durable operations) in the right order required for the execution of our Lambda durable function.&lt;/p&gt;

&lt;p&gt;There is also the &lt;em&gt;mapAsync&lt;/em&gt; operation. It starts the map operation without blocking, returning a &lt;em&gt;DurableFuture&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;In this part of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement map and child context operations.  &lt;/p&gt;

&lt;p&gt;Still, our Lambda durable function itself contains too much business logic. Ideally, it should simply be the orchestrator and contain as little business logic as possible. That's why, in the next part, we'll move the logic for each step into a separate Lambda function. With that, we'll explore how to invoke another Lambda function within the durable step. We'll also show how to invoke multiple Lambda functions in parallel.&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>serverless</category>
      <category>s3files</category>
      <category>lambdadurablefunctions</category>
      <category>java</category>
    </item>
    <item>
      <title>Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 4 Upgrade sample application to Embabel 1.5</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 07 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-4-upgrade-sample-e4o</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-4-upgrade-sample-e4o</guid>
      <description>&lt;h2&gt;
  
  
  Upgrade sample application to Embabel 1.5 to support Spring Boot 4.x and Spring AI 2.x
&lt;/h2&gt;

&lt;p&gt;In parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94"&gt;3&lt;/a&gt;, we developed our &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.x-conference-app-agent-local" rel="noopener noreferrer"&gt;sample application&lt;/a&gt; and interacted with it via the shell and HTTP. We used Embabel version 1.0 there, which targets Spring Boot 3.x / Spring AI 1.x.&lt;/p&gt;

&lt;p&gt;Recently, the Embabel team released &lt;a href="https://github.com/embabel/embabel-agent/releases/tag/v1.5.0" rel="noopener noreferrer"&gt;version 1.5&lt;/a&gt;, which supports Spring Boot 4.x / Spring AI 2.x.  In this article, we'll explain how to migrate our sample application to the Embabel 1.5.x version. You can find the final version of the application in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.5.x-conference-app-agent-local" rel="noopener noreferrer"&gt;my GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The migration for my use case is really simple. First, in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.5.x-conference-app-agent-local/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt; we need to declare this Embabel version:&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;properties&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;java.version&amp;gt;&lt;/span&gt;25&lt;span class="nt"&gt;&amp;lt;/java.version&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;embabel-agent.version&amp;gt;&lt;/span&gt;1.5.1&lt;span class="nt"&gt;&amp;lt;/embabel-agent.version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that we use version 1.5.1 here. With version 1.5.0, I ran into a &lt;a href="https://github.com/embabel/embabel-agent/issues/1923" rel="noopener noreferrer"&gt;specific problem&lt;/a&gt; while using the Amazon Bedrock Converse API. This was due to the fact that BedrockOptionsConverter created generic ToolCallingChatOptions. Spring AI 2.0’s BedrockProxyChatModel expects BedrockChatOptions and casts the request options accordingly, causing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ClassCastException: DefaultToolCallingChatOptions cannot be cast to BedrockChatOptions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With version 1.5.1, the Embabel team fixed the problem. Next, as Spring Boot 4.x / Spring AI 2.x uses &lt;a href="https://spring.io/blog/2025/10/07/introducing-jackson-3-support-in-spring" rel="noopener noreferrer"&gt;Jackson version 3 by default&lt;/a&gt;, we need to declare a Jackson 3 mapper Spring bean. We do it in the Spring Boot main &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.5.x-conference-app-agent-local/src/main/java/dev/vkazulkin/EmbabelConferenceApplication.java" rel="noopener noreferrer"&gt;EmbabelConferenceApplication&lt;/a&gt; application class of our sample application:&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;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;JsonMapper&lt;/span&gt; &lt;span class="nf"&gt;objectMapper&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jackson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;databind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JsonMapper&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;That's it. Now you can compile, start, and interact with our sample application the same way we described in parts &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;2&lt;/a&gt; and &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94"&gt;3&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springai</category>
      <category>embabel</category>
      <category>bedrockagentcore</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 4 Wait for condition</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 31 Aug 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1f1n</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1f1n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously. &lt;br&gt;
Later, in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-oo4"&gt;part 3&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement wait and callback. &lt;/p&gt;

&lt;p&gt;In this part, we'll explore the &lt;em&gt;wait for condition&lt;/em&gt; operation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sample application with wait for condition
&lt;/h2&gt;

&lt;p&gt;Let's explore the "wait for condition" operation. We get access to it through &lt;em&gt;DurableContext&lt;/em&gt;.  It repeatedly calls a check function until it signals done. Between polls, the Lambda suspends without consuming compute. State is checkpointed after each check, so progress survives interruptions.&lt;/p&gt;

&lt;p&gt;I first copied the application that we created in &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25" rel="noopener noreferrer"&gt;part 3&lt;/a&gt; into the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-with-wait-for-condition-java-25" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-with-wait-for-condition-java-25&lt;/a&gt; repository. The only change we had to make was to reimplement the &lt;em&gt;waitForUpcomingTalksApproval&lt;/em&gt; method of the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-wait-for-condition-java-25/src/main/java/dev/vkazulkin/handler/AbstractAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AbstractAuthorContentExtractor&lt;/a&gt; class.  Here we'll now use the "wait for condition" instead of the "wait for callback" operation.&lt;/p&gt;

&lt;p&gt;This is how the code now looks:&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="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt; &lt;span class="nf"&gt;waitForUpcomingTalksApproval&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DurableContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WaitForConditionConfig&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&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;waitStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WaitStrategies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultStrategy&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialState&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;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
         &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PENDING&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="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitForCondition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
     &lt;span class="s"&gt;"wait-for-approval"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
     &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&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="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentStatus&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stepCtx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;latest&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;getUpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;upcomingTalks&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;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPROVED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;                     
         &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;WaitForConditionResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stopPolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WaitForConditionResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;continuePolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;config&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="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt; &lt;span class="nf"&gt;getUpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// Generate random integers in range 0 to 3&lt;/span&gt;
     &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RANDOM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
          &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_APPROVED&lt;/span&gt;&lt;span class="o"&gt;)&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;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
          &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPROVED&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 this code. The &lt;em&gt;ctx.waitForCondition&lt;/em&gt; invocation receives the current state and a StepContext, and returns a &lt;em&gt;WaitForConditionResult&lt;/em&gt;. WaitForConditionResult contains the result type, in our case &lt;em&gt;UpcomingTalkApprovalStatus&lt;/em&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;WaitForConditionResult.stopPolling(value)&lt;/em&gt;  - condition met (in our case, status "APPROVED"), return value as the final result.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;WaitForConditionResult.continuePolling(value)&lt;/em&gt; - keep polling (bacause the status is "NOT_APPROVED"), pass value to the next check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the sake of simplicity, we haven't implemented the proper endpoint to poll the status. We provided a simple implementation in the &lt;em&gt;getUpcomingTalkApprovalStatus&lt;/em&gt; method, which generates a random integer between 0 and 3. If 3 is generated, the "APPROVED" status will be returned, indicating that the condition is met and the polling should stop. If 0, 1, or 2 are generated, the operation will keep polling.&lt;/p&gt;

&lt;p&gt;Next, let's look at how to configure &lt;em&gt;WaitForConditionConfig&lt;/em&gt;. First, we pass the &lt;em&gt;initialState&lt;/em&gt; with the status "PENDING," and this status is passed to the first check invocation. After that, we also configure the &lt;em&gt;waitStrategy&lt;/em&gt;. The wait strategy controls the delay between polls. By default, &lt;em&gt;waitForCondition&lt;/em&gt; uses an exponential backoff wait strategy (60 max attempts, 5s initial delay, 300s max delay, 1.5x backoff rate, FULL jitter). We can invoke the &lt;em&gt;WaitStrategies.exponentialBackoff&lt;/em&gt; method to configure exponential backoff with different settings than the default ones. Alternatively, we can use the &lt;em&gt;WaitStrategies.fixedDelay&lt;/em&gt; method. With this wait strategy, we need to configure: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;fixedDelay&lt;/em&gt; - a constant delay between polls and &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;maxAttempts&lt;/em&gt; - a maximum number of attempts before throwing &lt;em&gt;WaitForConditionFailedException&lt;/em&gt;. This exception is thrown when the maximum attempts are exceeded (thrown by the wait strategy).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also implement &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java/blob/main/docs/core/wait-for-condition.md#custom-wait-strategies" rel="noopener noreferrer"&gt;Custom Wait Strategies&lt;/a&gt;, which we won't cover in this article.&lt;/p&gt;

&lt;p&gt;That's it. There are no changes in the Infrastructure as Code part that we need to make. Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AuthorContentExtractor&lt;/em&gt; or &lt;em&gt;AsyncAuthorContentExtractor&lt;/em&gt; function, and go to the "Test" tab.&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuiupzpbq96rme4wd1bo8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuiupzpbq96rme4wd1bo8.png" alt=" " width="799" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we see that the operation with the name &lt;em&gt;wait-for-approval&lt;/em&gt; and the subtype &lt;em&gt;WaitForCondition&lt;/em&gt; succeeded after 3 attempts. The number of retries is 2. This means that it took 3 attempts to generate the integer 3. We defined the generation of this value as the condition to stop polling and return the result.&lt;/p&gt;

&lt;p&gt;Let's also look at event history:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8agkrjthysnm8iwskq3h.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8agkrjthysnm8iwskq3h.png" alt=" " width="800" height="571"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5wlzye6qcaximoo9e6k.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5wlzye6qcaximoo9e6k.png" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we see all events (durable operations) in the right order required for the execution of our Lambda durable function.&lt;/p&gt;

&lt;p&gt;There is also the &lt;em&gt;waitForConditionAsync&lt;/em&gt;  operation for non-blocking polling that you can explore on your own. It starts polling but returns a &lt;em&gt;DurableFuture&lt;/em&gt; immediately, allowing other operations to run concurrently.&lt;/p&gt;

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

&lt;p&gt;In this part of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement the &lt;em&gt;wait for condition&lt;/em&gt; operation.  &lt;/p&gt;

&lt;p&gt;In the next part, we'll explore map and child context operations. Still, our Lambda durable function itself contains too much business logic. Ideally, it should simply be the orchestrator and contain as little business logic as possible. That's why, in the subsequent parts, we'll move the logic for each step into a separate Lambda function. With that, we'll explore how to invoke another Lambda function within the durable step. We'll also show how to invoke multiple Lambda functions in parallel.&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>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>s3files</category>
      <category>java</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 3 Waits and callbacks</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-oo4</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-oo4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously. In this part, we'll extend our application by adding and implementing &lt;em&gt;wait&lt;/em&gt; and &lt;em&gt;callback&lt;/em&gt; operations to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application with wait for callback
&lt;/h2&gt;

&lt;p&gt;Let's explore waits and callbacks. We get access to them through &lt;em&gt;DurableContext&lt;/em&gt;. Waits are planned pauses where your function stops running and stops charging until it's time to continue. Use them for time periods, external callbacks, or polling for a condition. Callbacks suspend execution until an external system sends a result. Use this for human approvals, webhooks, or any event-driven workflow. In our sample application, we'll combine both and use &lt;em&gt;waitForCallback&lt;/em&gt;. It simplifies callback handling by combining callback creation and submission in one operation.&lt;/p&gt;

&lt;p&gt;I first copied the application that we created in &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e"&gt;part 2&lt;/a&gt; into the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/tree/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25&lt;/a&gt; repository. We'll work on it to extend our application. Please go through the content of part 2 to understand the sample application and the basic concepts of Lambda durable functions. We'll completely reuse the Infrastructure as Code part as well.&lt;/p&gt;

&lt;p&gt;Let's add another, a bit artificial use case to our sample application. After searching for the author's YouTube videos and upcoming talks, the author should confirm those talks. For this, let's extend our &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25/src/main/java/dev/vkazulkin/handler/AsyncAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AsyncAuthorContentExtractor&lt;/a&gt; Lambda function and add the following invocation:&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;upcomingTalkApprovalStatus&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;waitForUpcomingTalksApproval&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's look at the implementation of the &lt;em&gt;waitForUpcomingTalksApproval&lt;/em&gt; method in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25/src/main/java/dev/vkazulkin/handler/AbstractAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AbstractAuthorContentExtractor&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="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&lt;/span&gt; &lt;span class="nf"&gt;waitForUpcomingTalksApproval&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DurableContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&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;waitCallbackConfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WaitForCallbackConfig&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;callbackConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CallbackConfig&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;timeout&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;ofHours&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;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;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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitForCallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"wait-for-approval"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
             &lt;span class="nc"&gt;UpcomingTalkApprovalStatus&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="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callbackId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stepCtx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
              &lt;span class="o"&gt;-&amp;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;sendApprovalRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callbackId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
             &lt;span class="n"&gt;waitCallbackConfig&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendApprovalRequest&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;callbackId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="no"&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;"get approval for the talk of  "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" for the talks "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;upcomingTalks&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
       &lt;span class="s"&gt;" with the callback id "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;callbackId&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 explain step by step what is happening here. We use &lt;em&gt;DurableContext&lt;/em&gt; to create the &lt;em&gt;waitForCallback&lt;/em&gt; operation. We give it a name and the return type. This return type corresponds to the type of the object that will be passed in the callback. We also provide the submitter function &lt;em&gt;sendApprovalRequest&lt;/em&gt;, receiving the callback ID and a &lt;em&gt;StepContext&lt;/em&gt;. In this function, we can perform the desired operation, like sending the talk approval request to the author. We won't implement this functionality but only log the callback ID, which we'll require later to send the callback. We can optionally pass the &lt;em&gt;WaitForCallbackConfig&lt;/em&gt;. In our case, we only set the &lt;em&gt;CallbackConfig&lt;/em&gt; with a timeout of 1 hour. This means that if no callback arrives within this period of time, the Lambda durable function will fail. We can also set the &lt;em&gt;StepConfig&lt;/em&gt;. Please review part 2 for more information. &lt;/p&gt;

&lt;p&gt;When we invoke the &lt;em&gt;waitForCallback&lt;/em&gt; method, the invocation will block until the callback arrives. After its arrival, the payload of the callback is deserialized into the object of type &lt;em&gt;UpcomingTalkApprovalStatus&lt;/em&gt;, and this object will be returned.&lt;/p&gt;

&lt;p&gt;Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AsyncAuthorContentExtractor&lt;/em&gt; function, and go to the "Test" tab.&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bla@bla.de"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj1tfu7l7qxabcz7yw8z.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj1tfu7l7qxabcz7yw8z.png" alt=" " width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we see here is that the first 2 steps (searching for YouTube videos and upcoming talks) have already been executed asynchronously. Now, our Lambda function is in the "wait for the approval" state. The invocation of the Lambda durable function is now completed. Why? Because waits suspend our function without incurring charges. &lt;/p&gt;

&lt;p&gt;Now let's send the reply (callback) to our Lambda durable function. We first need to grab the callback ID from the log. There are several ways to send the callback (success or failure). We can use the Lambda CLI &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/lambda/send-durable-execution-callback-success.html" rel="noopener noreferrer"&gt;send-durable-execution-callback-success&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/lambda/send-durable-execution-callback-failure.html" rel="noopener noreferrer"&gt;execution-callback-failure&lt;/a&gt; commands. Alternatively, we can use the AWS SDK to do the same. Let's use the AWS Java SDK for it. First, we need to add the following dependency to &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25/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;software.amazon.awssdk&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&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;We implemented the functionality to send the callback in the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-with-wait-for-callback-java-25/src/main/java/dev/vkazulkin/callback/SendDurableExecutionCallback.java" rel="noopener noreferrer"&gt;SendDurableExecutionCallback&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;SendDurableExecutionCallback&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;final&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;CALLBACK_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="s"&gt;"Ab9hZXiWYXJuOmF3czpsYW1iZGE6dXMtZWFzdC0xOjI2NTYzNDI1NzYxMDpmdW5jdGlvbjpBdXRob3JDb250ZW50RXh0cmFjdG9yOiRMQVRFU1QvZHVyYWJsZS1leGVjdXRpb24vc2VhcmNoRm9yVmFkeW0xMzUvMmM2NzRiOWEtNTQ0OC0zZDVjLWI3MzUtNTQ1NWM5ODA5MWUwYWl4JDA2N2JkNmRkLTViN2MtNGZmYi05NmY4LTRkM2Y1Yjk3MWE0Zf8"&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;final&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;LambdaClient&lt;/span&gt; &lt;span class="no"&gt;LAMBDA_CLIENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="nc"&gt;LambdaClient&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;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="na"&gt;build&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;final&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="no"&gt;MAPPER&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="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;approvedStatus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MAPPER&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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UpcomingTalkApprovalStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UpcomingTalks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefaultUpcomingTalks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"approved"&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;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LAMBDA_CLIENT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendDurableExecutionCallbackSuccess&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;-&amp;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;callbackId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CALLBACK_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;result&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;approvedStatus&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we need to configure the callback ID (please replace it with your individual one). Then, we create the Lambda client. Next, we invoke its &lt;em&gt;sendDurableExecutionCallbackSuccess&lt;/em&gt; method to send the successful callback. We pass the callback ID and payload as JSON. This payload should be deserializable into the object of type &lt;em&gt;UpcomingTalkApprovalStatus&lt;/em&gt; (see above). Similarly, we can send the failure callback by using the &lt;em&gt;sendDurableExecutionCallbackFailure&lt;/em&gt; operation. Please move this logic with all required dependencies to the service that sends the callbacks. Now let's send the callback (approximately 23 minutes later) and see what happens:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet212hn5pb0xlw2bnfxj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet212hn5pb0xlw2bnfxj.png" alt=" " width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We observe that the callback was successfully received and processed. After it, the last &lt;em&gt;writeAuthorContentToFile-step&lt;/em&gt; step was executed as well. But we also observe that execution of our Lambda durable function started from the beginning. What happened here exactly? After our function resumes from a pause or interruption, the system performs replay. During replay, our code runs from the beginning but skips over completed checkpoints, using stored results instead of re-executing completed operations. This replay mechanism ensures consistency while enabling long-running executions.&lt;/p&gt;

&lt;p&gt;Here is the picture, which describes the concept that I took from this &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;source&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4lrx9jv7f51ujh8725g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4lrx9jv7f51ujh8725g.png" alt=" " width="800" height="777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also other individual operations that &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java" rel="noopener noreferrer"&gt;AWS Lambda Durable Execution SDK for Java&lt;/a&gt; supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;wait&lt;/em&gt; - wait suspends the function and resumes after the specified duration. There is no charge during suspension.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;waitAsync&lt;/em&gt; - starts the wait timer but returns a DurableFuture immediately, allowing other operations to run concurrently. The execution only suspends when you call .get() on the future (if the wait hasn't completed yet).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;createCallback&lt;/em&gt; - callbacks suspend execution until an external system sends a result. Use this for human approvals, webhooks, or any event-driven workflow.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In this part of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to implement wait and callback.  &lt;/p&gt;

&lt;p&gt;In the next part, we'll explore the "wait for condition" operation to poll a condition until it's met. Still, our Lambda durable function itself contains too much business logic. Ideally, it should simply be the orchestrator and contain as little business logic as possible. That's why, in the subsequent parts, we'll move the logic for each step into a separate Lambda function. With that, we'll explore how to invoke another Lambda function within the durable step. We'll also show how to invoke multiple Lambda functions in parallel.&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>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>s3files</category>
      <category>java</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 2 Durable synchronous and asynchronous steps</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 17 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1k6e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1gp3"&gt;part 1&lt;/a&gt;, we introduced what we'll cover in this series and our sample application. We also gave a short introduction to the services that we'll use throughout this series: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;AWS Lambda durable functions&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html#s3-files-what-is" rel="noopener noreferrer"&gt;Amazon S3 Files&lt;/a&gt;. In this part of the series, we'll explore how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously.&lt;/p&gt;

&lt;h2&gt;
  
  
  First implementation attempt of our sample application
&lt;/h2&gt;

&lt;p&gt;Throughout this article series, we'll develop a simple application to show how to build multi-step applications and orchestrate workflows using AWS Lambda durable functions. This simple application provides the functionality to extract the content for the provided user. To this content belongs searching for YouTube videos of the authors and their upcoming talks. We'll store the result on Amazon S3 Files in JSON format. As this operation may potentially take time, we'll additionally provide access to the author content through the Amazon API Gateway:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0l2gsqbvyfq2dh2gjpzn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0l2gsqbvyfq2dh2gjpzn.png" alt=" " width="510" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll start with the search for YouTube videos and upcoming talks of the author synchronously. For the sake of simplicity, we won't implement any persistent layer and store the static content in memory. We'll store the result on Amazon S3 Files in JSON format. &lt;/p&gt;

&lt;p&gt;You can find the code examples in my &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-java-sdk&lt;/a&gt; repository. We'll go through all the examples in more detail in the subsequent articles.&lt;/p&gt;

&lt;p&gt;First of all, let's declare some important dependencies in &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/pom.xml" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;. The most important one is the &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java" rel="noopener noreferrer"&gt;AWS Lambda Durable Execution SDK for Java&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;software.amazon.lambda.durable&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;aws-durable-execution-sdk-java&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.0.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;Why do we need such an SDK?  &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html#durable-functions-how-it-works" rel="noopener noreferrer"&gt;Under the hood&lt;/a&gt;, durable functions are regular Lambda functions using a checkpoint/replay mechanism to track progress and support long-running operations through user-defined suspension points, commonly referred to as durable execution. After your function resumes from a pause or interruption, the system performs replay. During replay, your code runs from the beginning but skips over completed checkpoints, using stored results instead of re-executing completed operations. This replay mechanism ensures consistency while enabling long-running executions.&lt;/p&gt;

&lt;p&gt;To harness this checkpoint-and-replay mechanism in your applications, Lambda provides a durable execution SDK. The SDK abstracts away the complexity of managing checkpoints and replay, exposing simple primitives called durable operations that you use in your code. The SDK integrates seamlessly with your existing Lambda development workflow.&lt;/p&gt;

&lt;p&gt;To provide the implementation of our Lambda durable function, we need the class to extend &lt;em&gt;DurableHandler&lt;/em&gt; and implement only the method &lt;em&gt;handleRequest&lt;/em&gt;.  Let's do it for our &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/src/main/java/dev/vkazulkin/handler/AuthorContentExtractor.java" rel="noopener noreferrer"&gt;AuthorContentExtractor&lt;/a&gt; Lambda durable function:&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;AuthorContentExtractor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DurableHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuthorContent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;AbstractAuthorContentExtractor&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;AuthorContent&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;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DurableContext&lt;/span&gt; &lt;span class="n"&gt;ctx&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;config&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;getStepConfig&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;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;searchForUpcomingTalks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&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;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;searchForYouTubeVideos&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&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;authorContent&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;AuthorContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"writeAuthorContentToFile-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Void&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;writeAuthorContentToFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorContent&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;config&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;authorContent&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;Let's go step by step through what's happening here. First of all, we also implement the &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/src/main/java/dev/vkazulkin/handler/AbstractAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AbstractAuthorContentExtractor&lt;/a&gt; interface. We place some common methods there that we'll use when improving our application.&lt;/p&gt;

&lt;p&gt;Now let's introduce the concept of durable steps. Steps run business logic with built-in retries and automatic checkpointing. Each step saves its result, so your function resumes from the last completed step after an interruption. With the SDK, you wrap your Lambda event handler, which then provides a &lt;em&gt;DurableContext&lt;/em&gt; alongside your event. This context gives you access to durable operations like steps. You write your function logic as normal sequential code, but instead of calling services directly, you wrap those calls in steps for automatic checkpointing and retries. When you need to pause execution, you add waits that suspend your function without incurring charges.  We'll show it in the next part of the series. The SDK handles all the complexity of state management and replay behind the scenes, so your code remains clean and readable.&lt;/p&gt;

&lt;p&gt;Here is the picture, which describes the concept that I took from this &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;source&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4lrx9jv7f51ujh8725g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4lrx9jv7f51ujh8725g.png" alt=" " width="800" height="777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We wrapped several operations into separate durable steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;searchForUpcomingTalks-step&lt;/li&gt;
&lt;li&gt;searchForYouTubeVideos-step&lt;/li&gt;
&lt;li&gt;writeAuthorContentToFile-step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look into the &lt;em&gt;searchForUpcomingTalks-step&lt;/em&gt; first:&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;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;searchForUpcomingTalks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a step, we need to give it a name and the result type. We also have to give it the function to execute, receiving a &lt;em&gt;StepContext&lt;/em&gt;. The return type of the function should be the same as the result type. In our case, we execute the &lt;em&gt;searchForUpcomingTalks&lt;/em&gt; function, which returns the static list of the upcoming talks. Optionally, we can also pass the &lt;em&gt;StepConfig&lt;/em&gt;. In our case, we create it 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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stepConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StepConfig&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;semanticsPerRetry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StepSemantics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AT_LEAST_ONCE_PER_RETRY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                 
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retryStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetryStrategies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exponentialBackoff&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;                                
      &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// max attempts&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;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// initial delay  &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;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// max delay&lt;/span&gt;
      &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;// backoff multiplier&lt;/span&gt;
      &lt;span class="nc"&gt;JitterStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FULL&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 create the &lt;em&gt;StepConfig&lt;/em&gt; by passing the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry strategy: fixed delay, linear backoff, or exponential backoff. We pass the latter here.&lt;/li&gt;
&lt;li&gt;semantics per retry: at-least-once delivery (default, which we also pass). The step may be re-executed if interrupted. START checkpoint is fire-and-forget. At-most-once delivery per retry attempt. START checkpoint is awaited before user code runs.&lt;/li&gt;
&lt;li&gt;custom serializer for the step with &lt;em&gt;serDes&lt;/em&gt; method. We don't provide it here, as we're happy with the default one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;searchForYouTubeVideos-step&lt;/em&gt; step works the same. After we have collected the result of the search for the upcoming talks and YouTube videos, we use the &lt;em&gt;writeAuthorContentToFile-step&lt;/em&gt; step to serialize the result to JSON and write it to the file:&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="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;Void&lt;/span&gt; &lt;span class="nf"&gt;writeAuthorContentToFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuthorContent&lt;/span&gt; &lt;span class="n"&gt;authorContent&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;authorContentAsJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OBJECT_MAPPER&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;authorContent&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;fileName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;firstName&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="n"&gt;authorContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;()+&lt;/span&gt;&lt;span class="s"&gt;".json"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Paths&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="no"&gt;WORKSPACE_MOUNT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;strToBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorContentAsJson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="nc"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strToBytes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the standard Java Path API to do it.  This is because we use Amazon S3 Files, and it supports POSIX. Strictly speaking, we could also use S3 here, but I wanted to show the functionality of S3 Files.&lt;/p&gt;

&lt;p&gt;The directory where we store the file is passed as an environment variable &lt;em&gt;WORKSPACE_MOUNT&lt;/em&gt;. In our case, it is &lt;em&gt;/mnt/workspace&lt;/em&gt;. See the Infrastructure as Code explanation below, especially the &lt;em&gt;LocalMountPath&lt;/em&gt; setting of the file system configuration for the Lambda function.&lt;/p&gt;

&lt;p&gt;For infrastructure as code (IaC), I use AWS SAM, and you can find it &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/template.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;. To indicate that our Lambda function is durable, we need to provide some additional &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-basic-concepts.html" rel="noopener noreferrer"&gt;durable config properties&lt;/a&gt;:&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;AuthorContentExtractorFunction&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::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AuthorContentExtractor&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.vkazulkin.handler.AuthorContentExtractor::handleRequest&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Execution timeout. The execution timeout controls how long a durable execution can run from start to completion. This is different from the Lambda function timeout, which controls how long a single function invocation can run. A durable execution can span multiple Lambda function invocations as it progresses through checkpoints, waits, and replays. The execution timeout applies to the total elapsed time of the durable execution, not to individual function invocations. Set the Execution timeout value in seconds (default: 86400 seconds / 24 hours, minimum: 60 seconds, maximum: 31536000 seconds / 1 year).&lt;/li&gt;
&lt;li&gt;Retention period. The retention period controls how long Lambda retains execution history and checkpoint data after a durable execution completes. This data includes step results, execution state, and the complete checkpoint log. After the retention period expires, Lambda deletes the execution history and checkpoint data. You can no longer retrieve execution details or replay the execution. Set the Retention period value in days (default: 14 days, minimum: 1 day, maximum: 90 days).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a very detailed explanation of how to create S3 Files and mount them to the Lambda function, please read the brilliant article &lt;a href="https://edjgeek.com/blog/s3-files-lambda-agents/" rel="noopener noreferrer"&gt;Lambda Just Got a File System. I Put AI Agents on It&lt;/a&gt; by Eric Johnson. I mostly copied the IaC part from it and adjusted it to my needs. I only describe the main steps here:&lt;/p&gt;

&lt;p&gt;First, we reference the networking stack, which we place in a separate file. Then we create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC networking for Lambda functions using S3 Files.&lt;/li&gt;
&lt;li&gt;VPC with private subnets (for Lambda + mount targets).&lt;/li&gt;
&lt;li&gt;Security groups for NFS traffic.&lt;/li&gt;
&lt;li&gt;Public subnet with a NAT gateway. We'll need them for internet access to Bedrock and Bedrock AgentCore Web Search Tool. In later parts, we'll use both services.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;NetworkingStack&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::Application&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;Location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/main/resources/stacks/network.yaml&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create an S3 Bucket:&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;WorkspaceBucket&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::S3::Bucket&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;BucketName &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vadym-s3-files-workspace&lt;/span&gt;
      &lt;span class="na"&gt;BucketEncryption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ServerSideEncryptionConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ServerSideEncryptionByDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;SSEAlgorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AES256&lt;/span&gt;
      &lt;span class="na"&gt;PublicAccessBlockConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;BlockPublicAcls&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;BlockPublicPolicy&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;IgnorePublicAcls&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;RestrictPublicBuckets&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;VersioningConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please don't forget to rename the S3 Bucket. After that, we need to create S3 Files:&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;S3FileSystem&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::S3Files::FileSystem&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;Bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;WorkspaceBucket.Arn&lt;/span&gt;
      &lt;span class="na"&gt;RoleArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FilesRole.Arn&lt;/span&gt;
      &lt;span class="na"&gt;AcceptBucketWarning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we reference an already created S3 Bucket. We also need to profile the appropriate IAM Role (see the SAM template for the IaC). Next, we need to create 2 different mount targets for the S3 File. They should use private subnets in different availability zones. Here is an example of one such mount 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;MountTargetA&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::S3Files::MountTarget&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;FileSystemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FileSystem.FileSystemId&lt;/span&gt;
      &lt;span class="na"&gt;SubnetId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetAId&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.MountTargetSGId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create an S3 Files access point:&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;S3FilesAccessPoint&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::S3Files::AccessPoint&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;FileSystemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FileSystem.FileSystemId&lt;/span&gt;
      &lt;span class="na"&gt;PosixUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
        &lt;span class="na"&gt;Gid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
      &lt;span class="na"&gt;RootDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/lambda&lt;/span&gt;
        &lt;span class="na"&gt;CreationPermissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;OwnerUid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
          &lt;span class="na"&gt;OwnerGid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
          &lt;span class="na"&gt;Permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;755'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;CreationPermissions&lt;/em&gt; property is crucial. It auto-creates the &lt;em&gt;/lambda&lt;/em&gt; directory within our S3 bucket with the right ownership when a client first connects. Without it, the root directory is owned by root (UID 0), and Lambda (running as UID 1000 through the access point) can’t create subdirectories.&lt;/p&gt;

&lt;p&gt;Lastly, we need to provide configuration and give permissions to our Lambda function to use S3 Files:&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;AuthorContentExtractorFunction&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::Function&lt;/span&gt;
    &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetA&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetB&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AuthorContentExtractor&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.vkazulkin.handler.AuthorContentExtractor::handleRequest&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
      &lt;span class="na"&gt;VpcConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;SecurityGroupIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.LambdaSGId&lt;/span&gt;
        &lt;span class="na"&gt;SubnetIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetAId&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetBId&lt;/span&gt;
      &lt;span class="na"&gt;FileSystemConfigs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FilesAccessPoint.AccessPointArn&lt;/span&gt;
          &lt;span class="na"&gt;LocalMountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
          &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MountS3Files&lt;/span&gt;
              &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3files:ClientMount&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3files:ClientWrite&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3files:ClientRootAccess&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FileSystem.FileSystemArn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we provide the VPC and file system configuration (including local mount path) and the policy for our Lambda function to use the S3 Files.&lt;/p&gt;

&lt;p&gt;Now we can build and package our application with &lt;em&gt;mvn clean package&lt;/em&gt; and deploy it with &lt;em&gt;sam deploy&lt;/em&gt;.  The deployment process can take up to 10 minutes because of the creation and mounting of S3 Files.&lt;/p&gt;

&lt;p&gt;To test our Lambda durable function, we can navigate to the Lambda service, search for the &lt;em&gt;AuthorContentExtractor&lt;/em&gt; function, and go to the "Test" tab:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbz7obubst4toraw8urlp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbz7obubst4toraw8urlp.png" alt=" " width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to pass the following sample JSON Event to it, which represents the author:&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;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vadym"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazulkin"&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;Then we can test it. After that, we go to the "Durable execution" tab and can see all the execution details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input and output as JSON&lt;/li&gt;
&lt;li&gt;Logger output&lt;/li&gt;
&lt;li&gt;Details like durable operations (with individual inputs and outputs of each step) and event history. If something goes wrong, the step status will be set to failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case, we invoked the durable function directly from the Lambda console. As an alternative, we can also use the Lambda SDK for it. We can also put an API Gateway in front of it if you wish and even invoke the Lambda function &lt;a href="https://dev.toSet%20up%20asynchronous%20invocation%20of%20the%20backend%20Lambda%20function"&gt;asynchronously&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffajhzt1bgyku05ikqjtl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffajhzt1bgyku05ikqjtl.png" alt=" " width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also see the JSON file with author content in the S3 Bucket that we created. Alternatively, I implemented a &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/src/main/java/dev/vkazulkin/handler/GetAuthorContentResult.java" rel="noopener noreferrer"&gt;GetAuthorContentResult&lt;/a&gt; Lambda function with the name &lt;em&gt;GetAuthorContentResult&lt;/em&gt;. This Lambda function takes the author's first and last name and streams the content of the JSON file from the S3 bucket. This JSON file is stored in the subdirectory &lt;em&gt;/lambda&lt;/em&gt; of our created S3 bucket. I also created and put an &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/template.yaml" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; in front of this Lambda function. Please use the &lt;em&gt;/result/{firstname}/{lastname}&lt;/em&gt; HTTP GET endpoint for it, for example /result/Vadym/Kazulkin. Be aware that it takes several minutes for the file to appear in the S3 Bucket after being written to S3 Files. If you need the result immediately, you can rewrite this Lambda to retrieve the result from S3 Files instead.&lt;/p&gt;

&lt;p&gt;This was a very simple application to explore the durable steps with the AWS Lambda Durable Execution SDK for Java. Let's improve our application a bit. Until now, we searched for the upcoming talks and YouTube videos sequentially. But we can do it in parallel. For that, we can use the &lt;em&gt;stepAsync&lt;/em&gt; operation instead of &lt;em&gt;step&lt;/em&gt;.  I provided a separate implementation, &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk/blob/main/aws-s3-files-lambda-durable-functions-java-25/src/main/java/dev/vkazulkin/handler/AsyncAuthorContentExtractor.java" rel="noopener noreferrer"&gt;AsyncAuthorContentExtractor&lt;/a&gt;, and the Lambda durable function with the name &lt;em&gt;AsyncAuthorContentExtractor&lt;/em&gt; for 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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;upcomingTalksFuture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stepAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForUpcomingTalks-async-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UpcomingTalks&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;searchForUpcomingTalks&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&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;youtubeVideosFuture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stepAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"searchForYouTubeVideos-async-step"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;YouTubeVideos&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="n"&gt;stepCtx&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;searchForYouTubeVideos&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&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;upcomingTalks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upcomingTalksFuture&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;youtubeVideos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;youtubeVideosFuture&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;em&gt;stepAsync&lt;/em&gt; invocation returns a &lt;em&gt;DurableFuture&lt;/em&gt; of the result type - a future representing the step result. This operation is non-blocking; we only block when invoking the &lt;em&gt;get&lt;/em&gt; method on the result. Everything else remains the same in this asynchronous implementation as it was in the synchronous one.&lt;/p&gt;

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

&lt;p&gt;In this part of the series, we explored how to use the AWS Lambda Durable Execution SDK for Java to create and execute durable steps synchronously and asynchronously. In the next part, we'll extend our application by adding and implementing &lt;em&gt;wait&lt;/em&gt; and &lt;em&gt;callback&lt;/em&gt; operations to it. Still, our Lambda durable function itself contains too much business logic. Ideally, it should simply be the orchestrator and contain as little business logic as possible. That's why in the later parts, we'll move the logic associated with each step into a separate Lambda function. We'll explore how to invoke another Lambda function within the durable step. We'll also show how to invoke multiple Lambda functions in parallel.&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>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>s3files</category>
      <category>java</category>
    </item>
    <item>
      <title>Orchestrating workflows with Amazon S3 Files and AWS Lambda durable functions using Java SDK - Part 1 Introduction to the series and services</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 10 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1gp3</link>
      <guid>https://dev.to/aws-heroes/orchestrating-workflows-with-amazon-s3-files-and-aws-lambda-durable-functions-using-java-sdk-part-1gp3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to the series
&lt;/h2&gt;

&lt;p&gt;Throughout this article series, we'll develop a simple application to show how to build multi-step applications and orchestrate workflows using AWS Lambda durable functions. For it, we'll explore the &lt;a href="https://github.com/aws/aws-durable-execution-sdk-java" rel="noopener noreferrer"&gt;AWS Lambda Durable Execution SDK for Java&lt;/a&gt;. This simple application provides the functionality to extract the content for the provided user. This content involves searching for YouTube videos of the authors and their upcoming talks. We'll store the result on Amazon S3 Files in JSON format. As this operation may potentially take time, we'll additionally provide access to the author content through the Amazon API Gateway:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0l2gsqbvyfq2dh2gjpzn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0l2gsqbvyfq2dh2gjpzn.png" alt=" " width="510" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'll start with the simple application to search for YouTube videos and upcoming talks of the author synchronously. First, we'll use durable steps and then later add waits and callbacks. We won't implement any persistent layer and store the static content in memory. &lt;/li&gt;
&lt;li&gt;We'll then improve our application by adding asynchronous communication where it makes sense. For example, searching for YouTube videos and upcoming talks can be done in parallel.&lt;/li&gt;
&lt;li&gt;We'll then further improve our application by converting the AWS durable function to a pure orchestrator. This means we'll move the business logic for searching for YouTube videos and upcoming talks into separate Lambda functions. With that, our Lambda durable function will invoke those separate Lambda functions in parallel and combine the results&lt;/li&gt;
&lt;li&gt;Finally, we'll further extend our application to use &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Web Search Tool&lt;/a&gt; to search for the author content on the web. With that, our Lambda durable function becomes an orchestrator of the agentic AI workflows. As we'll use Java in this series, we'll explore how to run this business logic responsible for the Amazon Bedrock AgentCore Web Search Tool. As this tool is exposed as an MCP tool by the &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway&lt;/a&gt;, we need a framework capable of providing AI functionality and supporting MCP. That's why we'll use Spring AI. As 2 operational environments for our agentic AI workflows, we'll explore an AWS Lambda function and &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the code examples in my &lt;a href="https://github.com/Vadym79/aws-s3-files-lambda-durable-functions-java-sdk" rel="noopener noreferrer"&gt;aws-s3-files-lambda-durable-functions-java-sdk&lt;/a&gt; repository. We'll go through all the examples in more detail in the subsequent articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to the services used throughout the series
&lt;/h2&gt;

&lt;p&gt;Throughout this series, we'll use the following AWS services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;AWS Lambda durable functions&lt;/a&gt;. With Lambda durable functions, you can build resilient multi-step applications and AI workflows. These can execute for up to one year while maintaining reliable progress despite interruptions. When a durable function runs, this complete lifecycle is called a durable execution, which uses checkpoints to track progress and automatically recover from failures through replay, re-executing from the beginning while skipping completed work. Within each function, you use durable operations as fundamental building blocks. Steps execute business logic with built-in retries and progress tracking. Waits suspend execution without incurring compute charges, making them ideal for long-running processes. These processes include human-in-the-loop workflows or polling external dependencies. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html#s3-files-what-is" rel="noopener noreferrer"&gt;Amazon S3 Files&lt;/a&gt;. S3 Files is a shared file system that connects any AWS compute resource directly with your data in Amazon S3. It provides fast, direct access to all of your S3 data as files with full file system semantics and low-latency performance, without your data ever leaving S3. Every file-based application, agent, and team can access and work with your S3 data as a file system using the tools they already depend on. Built using Amazon EFS, S3 Files gives you the performance and simplicity of a file system with the scalability, durability, and cost-effectiveness of S3. You can read, write, and organize data using file and directory operations. At the same time, S3 Files manages the synchronization of changes between your bucket and file system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We'll also introduce in the later parts of this series the following Amazon Bedrock AgentCore services or features: &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway.html" rel="noopener noreferrer"&gt;Gateway&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" rel="noopener noreferrer"&gt;Web Search Tool&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/de_de/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;Runtime&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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>serverless</category>
      <category>lambdadurablefunctions</category>
      <category>s3files</category>
      <category>java</category>
    </item>
    <item>
      <title>Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 3 Develop Agents as web application with Embabel</title>
      <dc:creator>Vadym Kazulkin</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94</link>
      <guid>https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-3-develop-agents-as-2d94</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-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;, we developed our first AI agents with the Embabel shell. We learned many concepts of this framework, such as agents, actions, and goals. In this article, we'll slightly adjust our application to convert it into a web application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update from September 2026&lt;/strong&gt; Please read my article &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-4-upgrade-sample-e4o"&gt;Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 4 Upgrade sample application to Embabel 1.5&lt;/a&gt; to find out how to upgrade this sample application to Embabel version 1.5, which supports Spring Boot 4.x and Spring AI 2.x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement AI agents as a web application with Embabel
&lt;/h2&gt;

&lt;p&gt;You can look at my GitHub &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/tree/main/embabel-1.x-conference-app-agent-local" rel="noopener noreferrer"&gt;embabel-1.x-conference-app-agent-local&lt;/a&gt; repository for the example. We'll completely reuse the implementation of both agents, the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/CreateTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;CreateTalksAndApplyForConferencesAgent&lt;/a&gt; and the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/agent/SearchForTalksAndApplyForConferencesAgent.java" rel="noopener noreferrer"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/a&gt; that we implemented in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those agents are capable of responding to the following prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&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;em&gt;SearchForTalksAndApplyForConferencesAgent&lt;/em&gt; is responsible for responding to this prompt.&lt;/li&gt;
&lt;li&gt;"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." &lt;em&gt;CreateTalksAndApplyForConferencesAgent&lt;/em&gt; is responsible for responding to this prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, let's check that we declared the following dependencies in &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/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;com.embabel.agent&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;embabel-agent-starter-bedrock&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;${embabel-agent.version}&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;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;org.springframework.boot&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;spring-boot-starter-web&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;Also, make sure not to declare the &lt;em&gt;embabel-agent-starter-shell&lt;/em&gt; dependency, as we don't use Embabel shell in this article.&lt;/p&gt;

&lt;p&gt;All we need to do now is to additionally implement the &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/controller/EmbabelAgentController.java" rel="noopener noreferrer"&gt;EmbabelAgentController&lt;/a&gt; controller.&lt;/p&gt;

&lt;p&gt;Let's first implement a generic endpoint that will accept both prompts. Let's first autowire and create some objects coming from Embabel that we'll use for 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="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmbabelAgentController&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;final&lt;/span&gt; &lt;span class="nc"&gt;AgentPlatform&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&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;final&lt;/span&gt; &lt;span class="nc"&gt;AgentInvocation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;invocation&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;final&lt;/span&gt; &lt;span class="nc"&gt;ProcessOptions&lt;/span&gt; &lt;span class="n"&gt;processOptions&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;ProcessOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withVerbosity&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;Verbosity&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowPrompts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowLlmResponses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withShowPlanning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDebug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;EmbabelAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentPlatform&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&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;agentPlatform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentPlatform&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;invocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentInvocation&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;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processOptions&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;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&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="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;First, we autowire the Embabel's &lt;em&gt;AgentPlatform&lt;/em&gt; object and use it to create the &lt;em&gt;AgentInvocation&lt;/em&gt; object. We additionally pass the &lt;em&gt;ProcessOption&lt;/em&gt; object that is configured to show planning, LLM responses, and prompts. We also pass the type of the response - the domain object returned after the agents achieve their goals; in our case, the  &lt;a href="https://github.com/Vadym79/amazon-bedrock-agentcore-spring-ai/blob/main/embabel-1.x-conference-app-agent-local/src/main/java/dev/vkazulkin/embabel/domain/Domain.java" rel="noopener noreferrer"&gt;Domain.ConferenceApplications&lt;/a&gt; class:&lt;/p&gt;

&lt;p&gt;Now, let's implement the endpoint 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="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;"/generic"&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;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;genericPrompt&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;inputs&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;"request"&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;UserInput&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="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;invocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we wrap the prompt into Embabel's &lt;em&gt;UserInput&lt;/em&gt; object that we put into the map with all inputs required to run the agent. Then we use the &lt;em&gt;AgentInvocation&lt;/em&gt; object to run the agent with the specified input synchronously. We can also invoke &lt;em&gt;runAsync&lt;/em&gt; to run the agent asynchronously. Finally, we retrieve the result of the agent invocation with the specified type.&lt;/p&gt;

&lt;p&gt;Let's build the application with &lt;code&gt;mvn clean package&lt;/code&gt; and run it locally with &lt;code&gt;mvn spring-boot:run&lt;/code&gt;. We'll send the prompt number 1 to this endpoint. What we'll see is that Embabel picked the wrong agent, &lt;em&gt;CreateTalksAndApplyForConferencesAgent&lt;/em&gt;, to run. Why? As we don't use the Embabel shell, Embabel doesn't analyze the prompt and compute the score to determine what exact agent to run. It currently only looks into the return type of all agents and picks the first one that matches the type defined in the &lt;em&gt;AgentInvocation&lt;/em&gt; object. But both our agents have the same return type &lt;em&gt;Domain.ConferenceApplications&lt;/em&gt;. Their goals, in the end, are the same: to apply for the conferences. With that, for our 2 agents, there is a 50% probability that Embabel will pick the wrong agent to run.&lt;/p&gt;

&lt;p&gt;How can we fix that? For example, we can define the endpoint per agent. Let's do 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="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;"/applyToConferencesWithExistingTalks"&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;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;applyToConferencesWithExistingTalks&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="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;invokeAgent&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="nc"&gt;SearchForTalksAndApplyForConferencesAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AGENT_NAME&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   
&lt;span class="o"&gt;}&lt;/span&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;"/applyToConferencesWithNewTalks"&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;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;applyToConferencesWithNewTalks&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="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;invokeAgent&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="nc"&gt;CreateTalksAndApplyForConferencesAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AGENT_NAME&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 additionally wrote 2 helper methods, &lt;em&gt;invokeAgent&lt;/em&gt; and &lt;em&gt;getAgentByName&lt;/em&gt;, to simplify the agent search by name and its subsequent invocation:&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;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&lt;/span&gt; &lt;span class="nf"&gt;invokeAgent&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;prompt&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;agentName&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;inputs&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;"request"&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;UserInput&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agent&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;getAgentByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentName&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;agentProcess&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;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAgentProcess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;processOptions&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&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;agentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ConferenceApplications&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="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="nf"&gt;getAgentByName&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;agentName&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;optionalAgent&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;agentPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;agents&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;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findFirst&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;optionalAgent&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"agent with the name "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" not found"&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;optionalAgent&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain what happens in the &lt;em&gt;invokeAgent&lt;/em&gt; method. We first invoke the &lt;em&gt;getAgentByName&lt;/em&gt; method. It searches in the collection of agents that the agent platform knows for one that has a specified name. All classes annotated with Embabel's &lt;em&gt;Agent&lt;/em&gt; annotation will be registered in the agent platform. Then we use the &lt;em&gt;AgentPlatform&lt;/em&gt; object to create the &lt;em&gt;AgentProcess&lt;/em&gt; and pass the found agent, the already described process options, and inputs to it. Then we run the &lt;em&gt;AgentProcess&lt;/em&gt; and return its last output of type &lt;em&gt;Domain.ConferenceApplications.class&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now we send prompt number 1 (see above) to the &lt;em&gt;applyToConferencesWithExistingTalks&lt;/em&gt; endpoint and prompt number 2 to the &lt;em&gt;applyToConferencesWithNewTalks&lt;/em&gt; endpoint. We'll see something similar as a result, as we saw in &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-2-develop-agents-57nb"&gt;part 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, here I use &lt;a href="https://httpie.io/" rel="noopener noreferrer"&gt;httpie&lt;/a&gt; to send prompt number 1: &lt;br&gt;
&lt;code&gt;http GET http://localhost:8080/applyToConferencesWithExistingTalks?prompt="Please provide me with the list of conferences including their IDs with Java topic happening in 2026 and 2027 with 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 response of the agent:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxq4w3wz6pde5nonc5twv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxq4w3wz6pde5nonc5twv.png" alt=" " width="799" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having one endpoint per Embabel agent has its pros and cons. The biggest disadvantage is that a service like &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-spring-ai-and-amazon-bedrock-agentcore-part-5-deploy-mcp-client-for-1n11"&gt;AgentCore Runtime&lt;/a&gt; exposes only one HTTP POST &lt;em&gt;/invocations&lt;/em&gt; endpoint to send the prompt to the web application. This means that we'll need to think about how to design, implement, and deploy each Embabel agent on a separate AgentCore Runtime. Each agent belonging to the same web application will have a lot of shared logic with other agents. This is not very convenient. The biggest advantage is that you can scale each Embabel agent individually. AgentCore Runtime will do it for you.&lt;/p&gt;

&lt;p&gt;It's worth checking in the upcoming versions of Embabel whether there are other options available &lt;a href="https://docs.embabel.com/embabel-agent/guide/1.0.0-SNAPSHOT/#reference.invoking" rel="noopener noreferrer"&gt;to invoke the agent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update from September 2026&lt;/strong&gt; Please read my article &lt;a href="https://dev.to/aws-heroes/building-ai-agents-with-embabel-spring-ai-and-amazon-bedrock-agentcore-part-4-upgrade-sample-e4o"&gt;Building AI Agents with Embabel, Spring AI and Amazon Bedrock AgentCore - Part 4 Upgrade sample application to Embabel 1.5&lt;/a&gt; to find out how to upgrade this sample application to Embabel version 1.5, which supports Spring Boot 4.x and Spring AI 2.x.&lt;/p&gt;

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

&lt;p&gt;In this article, we covered how to develop a web application with Embabel.&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>java</category>
      <category>springai</category>
      <category>embabel</category>
      <category>bedrockagentcore</category>
    </item>
  </channel>
</rss>
