<?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: Umesh Bhati</title>
    <description>The latest articles on DEV Community by Umesh Bhati (@umeshbhati).</description>
    <link>https://dev.to/umeshbhati</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1018896%2F62f2fbae-c904-4a99-b6a4-7657d65c8e4d.jpeg</url>
      <title>DEV Community: Umesh Bhati</title>
      <link>https://dev.to/umeshbhati</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/umeshbhati"/>
    <language>en</language>
    <item>
      <title>Debugging an AWS SAM Local 502 That Wasn't a Spring Boot Bug</title>
      <dc:creator>Umesh Bhati</dc:creator>
      <pubDate>Mon, 25 May 2026 14:10:36 +0000</pubDate>
      <link>https://dev.to/umeshbhati/debugging-an-aws-sam-local-502-that-wasnt-a-spring-boot-bug-172k</link>
      <guid>https://dev.to/umeshbhati/debugging-an-aws-sam-local-502-that-wasnt-a-spring-boot-bug-172k</guid>
      <description>&lt;p&gt;The local API started cleanly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running on http://127.0.0.1:3001
Press CTRL+C to quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the dashboard called &lt;code&gt;/jobs&lt;/code&gt;, and every request came back as &lt;code&gt;502&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, that looks like the application is broken. Maybe the controller failed. Maybe the database configuration is wrong. Maybe the Lambda bridge is not passing the request into Spring Boot correctly.&lt;/p&gt;

&lt;p&gt;But the stack trace told a different story. The request was not failing inside the &lt;code&gt;/jobs&lt;/code&gt; endpoint. It was failing inside AWS SAM CLI before the application had a clean chance to handle it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: unhashable type: 'collections.OrderedDict'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changed the debugging question. The problem was no longer “why is my API endpoint failing?” It became “why is local infrastructure handing SAM a value it cannot use as a function name?”&lt;/p&gt;

&lt;p&gt;The useful clue was buried in the middle of the Python stack trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File ".../samcli/commands/local/lib/local_lambda.py", line 393, in _make_env_vars
  or self.env_vars_values.get(function_name, None)
TypeError: unhashable type: 'collections.OrderedDict'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line matters because &lt;code&gt;dict.get()&lt;/code&gt; expects a hashable key. A normal Lambda function name would be a string, and a string is hashable. SAM was not holding a string there. It was holding a parsed CloudFormation object.&lt;/p&gt;

&lt;p&gt;That ruled out the first few obvious suspects. The database was not the immediate failure. The Spring controller was not the immediate failure. The Java handler was not throwing the exception shown in the terminal. The failure happened while SAM local was trying to assemble the Lambda invocation environment.&lt;/p&gt;

&lt;p&gt;The source of that object was the SAM template.&lt;/p&gt;

&lt;p&gt;The deployed template allowed the Lambda function name to be optional:&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::If&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HasLambdaFunctionName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LambdaFunctionName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::NoValue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a reasonable deployment pattern. In CloudFormation, &lt;code&gt;Fn::If&lt;/code&gt; can decide whether to use a fixed Lambda function name or let AWS generate one.&lt;/p&gt;

&lt;p&gt;But local execution is a different environment. In this case, SAM CLI did not resolve that conditional into a plain string before it built the local invocation config. So by the time &lt;code&gt;/jobs&lt;/code&gt; was called, &lt;code&gt;function_name&lt;/code&gt; was not something like &lt;code&gt;ApiFunction&lt;/code&gt;. It was still the parsed representation of the CloudFormation condition.&lt;/p&gt;

&lt;p&gt;That is how a valid deployment template became an invalid local runtime input.&lt;/p&gt;

&lt;p&gt;The first fix looked obvious: pass a concrete function name into SAM local.&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="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="s2"&gt;"LambdaFunctionName=rcyc-eil-sync-api-local ..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was not a bad guess. If the conditional &lt;code&gt;FunctionName&lt;/code&gt; was the problem, then giving the parameter a real value should have given SAM something concrete to resolve.&lt;/p&gt;

&lt;p&gt;But the error still appeared.&lt;/p&gt;

&lt;p&gt;That failure was useful. It showed that the issue was not only the missing value of &lt;code&gt;LambdaFunctionName&lt;/code&gt;. The built SAM template still carried the conditional &lt;code&gt;Fn::If&lt;/code&gt; shape, and SAM local was still walking through that shape during local invocation.&lt;/p&gt;

&lt;p&gt;In other words, the fix could not be only “supply a better parameter.” The local runner needed a simpler template.&lt;/p&gt;

&lt;p&gt;The fix was to separate the deployment template from the local runtime template.&lt;/p&gt;

&lt;p&gt;The production &lt;code&gt;template.yaml&lt;/code&gt; can keep the AWS-specific behavior: optional function names, aliases, and provisioned concurrency. Those belong in the deploy path.&lt;/p&gt;

&lt;p&gt;For local development, I added a smaller &lt;code&gt;template.local.yaml&lt;/code&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;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ApiFunction&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.aws-sam/build/ApiFunction&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;com.rcyc.eil.syncapi.lambda.StreamLambdaHandler::handleRequest&lt;/span&gt;
      &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LambdaMemorySize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is what is missing.&lt;/p&gt;

&lt;p&gt;There is no conditional &lt;code&gt;FunctionName&lt;/code&gt;. There is no &lt;code&gt;AutoPublishAlias&lt;/code&gt;. There is no &lt;code&gt;ProvisionedConcurrencyConfig&lt;/code&gt;. The local template points directly at the built Lambda artifact and gives SAM local a shape it can execute without interpreting deploy-only CloudFormation.&lt;/p&gt;

&lt;p&gt;Then the startup script was changed to use the local template by default:&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="nv"&gt;SAM_TEMPLATE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SAM_TEMPLATE_FILE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.local.yaml&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SAM_TEMPLATE_FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--warm-containers&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WARM_CONTAINERS_MODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;parameter_overrides&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That removed the &lt;code&gt;OrderedDict&lt;/code&gt; crash path because SAM local no longer had to treat a conditional CloudFormation object as the Lambda function name.&lt;/p&gt;

&lt;p&gt;I kept warm containers enabled.&lt;/p&gt;

&lt;p&gt;That detail matters. The easy escape would have been to remove &lt;code&gt;--warm-containers&lt;/code&gt; and accept slower local requests. But this API backs a job monitoring dashboard. During development, the dashboard repeatedly calls &lt;code&gt;/jobs&lt;/code&gt;, and waiting through a Java/Spring Lambda cold start on each request makes the UI feel broken even when the backend is correct.&lt;/p&gt;

&lt;p&gt;So the goal was not only “make the exception disappear.” The goal was “make local invocation stable while keeping repeated dashboard requests fast.”&lt;/p&gt;

&lt;p&gt;With the local template simplified, the startup script could keep eager warming:&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="nv"&gt;WARM_CONTAINERS_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WARM_CONTAINERS_MODE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;EAGER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That kept the developer experience aligned with the product requirement: a monitoring UI should feel responsive while engineers are testing it.&lt;/p&gt;

&lt;p&gt;I verified the fix in layers.&lt;/p&gt;

&lt;p&gt;First, the startup script still had to be valid shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-n&lt;/span&gt; scripts/start-local-api.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the local template had to be a valid SAM template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam validate &lt;span class="nt"&gt;-t&lt;/span&gt; template.local.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important check was &lt;code&gt;sam local invoke&lt;/code&gt; with debug logging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke ApiFunction &lt;span class="nt"&gt;-t&lt;/span&gt; template.local.yaml &lt;span class="nt"&gt;--event&lt;/span&gt; /dev/null &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my constrained validation environment, Docker access was blocked, so the Lambda could not fully start there. But the failure moved to the expected place: container runtime setup. SAM no longer crashed while building environment variables, and the debug output showed the function resolving as a plain &lt;code&gt;ApiFunction&lt;/code&gt; with &lt;code&gt;CodeUri='.aws-sam/build/ApiFunction'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That was the evidence I needed. The old failure happened before Docker. The new failure happened at Docker access. The &lt;code&gt;OrderedDict&lt;/code&gt; path was gone.&lt;/p&gt;

&lt;p&gt;If you hit this error, I would check these things first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look for &lt;code&gt;FunctionName&lt;/code&gt; using &lt;code&gt;Fn::If&lt;/code&gt;, &lt;code&gt;Ref&lt;/code&gt;, or another CloudFormation expression.&lt;/li&gt;
&lt;li&gt;Check whether &lt;code&gt;sam local&lt;/code&gt; is using &lt;code&gt;.aws-sam/build/template.yaml&lt;/code&gt; instead of the source template you think it is using.&lt;/li&gt;
&lt;li&gt;Run with &lt;code&gt;--debug&lt;/code&gt; and see whether the function resolves to a plain string or a parsed object.&lt;/li&gt;
&lt;li&gt;Try a local-only template that removes deploy-only properties like &lt;code&gt;AutoPublishAlias&lt;/code&gt;, &lt;code&gt;ProvisionedConcurrencyConfig&lt;/code&gt;, and conditional function names.&lt;/li&gt;
&lt;li&gt;Keep performance settings like &lt;code&gt;--warm-containers EAGER&lt;/code&gt; only after the local template shape is simple enough for SAM to execute reliably.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The checklist is short because the bug is not really about &lt;code&gt;/jobs&lt;/code&gt;, Java, or Spring Boot. It is about the value SAM local uses as the Lambda function name.&lt;/p&gt;

&lt;p&gt;The final local flow looked 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;sam build
./scripts/start-local-api.sh 3001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s1"&gt;'rcyc-api:your-password'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'http://127.0.0.1:3001/jobs?page=0&amp;amp;size=10&amp;amp;sortBy=jobId&amp;amp;sortDir=DESC'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If startup needs to be debugged without eager container initialization, the script still allows that:&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="nv"&gt;WARM_CONTAINERS_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LAZY ./scripts/start-local-api.sh 3001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main lesson for me was not specific to AWS SAM.&lt;/p&gt;

&lt;p&gt;When a request fails before your application code gets control, the endpoint is only where the symptom appears. The fix belongs at the boundary that created the bad runtime value.&lt;/p&gt;

&lt;p&gt;In this case, &lt;code&gt;/jobs&lt;/code&gt; was not the problem. Spring Boot was not the problem. The problem was that a deployment-friendly CloudFormation expression leaked into a local runtime path that expected a plain function name.&lt;/p&gt;

&lt;p&gt;Once that boundary was split into a deploy template and a local template, the error became easy to reason about and the local API could stay fast.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cli</category>
      <category>serverless</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
