<?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: Ankit Verma</title>
    <description>The latest articles on DEV Community by Ankit Verma (@ankit_verma_e2fa7fb2aa95d).</description>
    <link>https://dev.to/ankit_verma_e2fa7fb2aa95d</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%2F3973400%2F62bb2cd4-9517-49ee-9812-abecef838b1b.jpg</url>
      <title>DEV Community: Ankit Verma</title>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankit_verma_e2fa7fb2aa95d"/>
    <language>en</language>
    <item>
      <title>Filters vs Interceptors vs AOP — when each</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sat, 19 Sep 2026 08:54:38 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/filters-vs-interceptors-vs-aop-when-each-5ah5</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/filters-vs-interceptors-vs-aop-when-each-5ah5</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The big idea in one line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Filters, interceptors, and aspects are three places to run code&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;around&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;a request — at three different depths of the stack.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every web app has work that isn't the "real" job of any single endpoint: logging, timing, auth checks, adding headers, collecting metrics.&lt;/li&gt;
&lt;li&gt;Copying that work into every controller method is a mess. You repeat it everywhere, and you forget it somewhere.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;cross-cutting concern&lt;/strong&gt; is a job that applies to many requests or many methods — it cuts &lt;em&gt;across&lt;/em&gt; your normal code instead of belonging to one spot.&lt;/li&gt;
&lt;li&gt;Spring gives you three tools to handle these in one place. They differ by &lt;em&gt;where&lt;/em&gt; in the request's journey they sit, and &lt;em&gt;how much they can see&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;You meet this the first time you need "run this before every request" — and immediately hit the real question: which of the three?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛣️ First, the journey of one request
&lt;/h2&gt;

&lt;p&gt;Before comparing the tools, you need to see the path a request takes. It does not land on your controller directly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Servlet container&lt;/strong&gt; (Tomcat, Jetty): the web server that speaks HTTP and hands your app a raw &lt;code&gt;HttpServletRequest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DispatcherServlet&lt;/strong&gt;: Spring's single front-door servlet. It reads the URL, picks the right controller method, calls it, and turns the return value into a response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handler&lt;/strong&gt;: the specific controller method chosen to serve this request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is where each tool sits, from outside in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;HTTP request
  │
  ▼
▓ Servlet Filter            ← outermost · raw HTTP · no idea which controller
  │
  ▼
  ▓ Handler Interceptor     ← inside Spring MVC · knows the handler
    │
    ▼
    ▓ Controller method
      │  (calls a service bean)
      ▼
      ▓ AOP Aspect          ← wraps any bean method · fires for non-web calls too
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The order is the whole insight: &lt;strong&gt;filters sit outermost, interceptors sit inside the DispatcherServlet, aspects sit deepest — around your bean methods.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;As you move inward, each layer sees &lt;em&gt;more Spring detail&lt;/em&gt; and &lt;em&gt;less raw HTTP&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧱 Layer 1 — Servlet Filters (the outer wall)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Filter&lt;/strong&gt; is part of the Servlet spec — plain Jakarta/Java EE, not Spring. It wraps the request &lt;em&gt;before Spring even runs&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It sees the raw &lt;code&gt;HttpServletRequest&lt;/code&gt; and &lt;code&gt;HttpServletResponse&lt;/code&gt;, and nothing about which controller will run.
&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="nd"&gt;@Component&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;TimingFilter&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Filter&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;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ServletResponse&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;chain&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="nc"&gt;ServletException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&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;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// hand off to the next filter, then the app&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;took&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;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request took "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;took&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"ms"&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;ul&gt;
&lt;li&gt;
&lt;code&gt;chain.doFilter(...)&lt;/code&gt; is the pivot. Everything &lt;strong&gt;before&lt;/strong&gt; it runs on the way in; everything &lt;strong&gt;after&lt;/strong&gt; it runs on the way out.&lt;/li&gt;
&lt;li&gt;If you never call &lt;code&gt;chain.doFilter&lt;/code&gt;, the request stops right here. That is how a filter blocks a request entirely — for example, rejecting a missing API key before any Spring code runs.&lt;/li&gt;
&lt;li&gt;It holds both the request and the response, so it can even wrap or replace the response body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Filters are good at:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work that must happen for &lt;em&gt;every&lt;/em&gt; HTTP request — controller-bound or not (static files, error paths).&lt;/li&gt;
&lt;li&gt;Reading or modifying the raw request/response (compression, CORS headers, caching the request body).&lt;/li&gt;
&lt;li&gt;Security gates that should run before Spring. Spring Security itself is built as one big filter.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Layer 2 — HandlerInterceptors (inside Spring MVC)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;HandlerInterceptor&lt;/strong&gt; runs &lt;em&gt;inside&lt;/em&gt; the DispatcherServlet, &lt;strong&gt;after&lt;/strong&gt; Spring has already decided which handler will serve the request.&lt;/li&gt;
&lt;li&gt;So it knows the handler — a filter never does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It gives you three hooks around the controller call:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hook&lt;/th&gt;
&lt;th&gt;When it runs&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preHandle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;before the controller method&lt;/td&gt;
&lt;td&gt;auth check; start a timer; return &lt;code&gt;false&lt;/code&gt; to block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postHandle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;after the controller, before the view renders&lt;/td&gt;
&lt;td&gt;tweak the model or response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;afterCompletion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;after everything, even when the handler threw&lt;/td&gt;
&lt;td&gt;cleanup; stop a timer; log the exception&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&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;AuthInterceptor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HandlerInterceptor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;preHandle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-User"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// stop: the controller never runs&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;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// continue to the controller&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;ul&gt;
&lt;li&gt;Returning &lt;code&gt;false&lt;/code&gt; from &lt;code&gt;preHandle&lt;/code&gt; stops the request — the same idea as a filter that skips &lt;code&gt;chain.doFilter&lt;/code&gt;, except you are already inside Spring.&lt;/li&gt;
&lt;li&gt;You register it against URL patterns, and the &lt;code&gt;handler&lt;/code&gt; argument lets you read the target method's annotations:
&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="nd"&gt;@Configuration&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;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&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;addInterceptors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterceptorRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInterceptor&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;AuthInterceptor&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;addPathPatterns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/**"&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;&lt;strong&gt;Interceptors are good at:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web concerns that need to know the controller or endpoint (per-route auth, request logging that names the method).&lt;/li&gt;
&lt;li&gt;Anything tied to the MVC lifecycle, like touching the model before the view renders.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Layer 3 — AOP / Aspects (around any bean method)
&lt;/h2&gt;

&lt;p&gt;The first two tools only see &lt;em&gt;web&lt;/em&gt; requests. But cross-cutting work often lives deeper — inside services that are called from web endpoints &lt;em&gt;and&lt;/em&gt; from scheduled jobs &lt;em&gt;and&lt;/em&gt; from message listeners.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AOP (Aspect-Oriented Programming)&lt;/strong&gt; lets you run code around &lt;em&gt;any Spring bean method call&lt;/em&gt;, web or not.&lt;/li&gt;
&lt;li&gt;How does it reach inside a method call? &lt;strong&gt;Spring wraps your bean in a proxy&lt;/strong&gt; — a stand-in object that looks identical to your bean but runs extra code before and after the real method.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;caller ──▶ [ Proxy ] ──▶ your real bean method
               │
               └─ runs "advice" before &amp;amp; after the real call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two terms fall out of that picture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advice&lt;/strong&gt;: the extra code the aspect runs (before, after, or around the method).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointcut&lt;/strong&gt;: the rule that picks &lt;em&gt;which&lt;/em&gt; methods to wrap — e.g. "every method in the service package".
&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="nd"&gt;@Aspect&lt;/span&gt;
&lt;span class="nd"&gt;@Component&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;LoggingAspect&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Around&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execution(* com.example.service..*(..))"&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;Object&lt;/span&gt; &lt;span class="nf"&gt;logTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProceedingJoinPoint&lt;/span&gt; &lt;span class="n"&gt;pjp&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;Throwable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&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;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Object&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;pjp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// call the real method&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;took&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;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pjp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSignature&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" took "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;took&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"ms"&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;result&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;ul&gt;
&lt;li&gt;
&lt;code&gt;pjp.proceed()&lt;/code&gt; is the AOP version of &lt;code&gt;chain.doFilter&lt;/code&gt; — it runs the real method. Same in-and-out shape as a filter, but wrapped around a plain Java method instead of an HTTP request.&lt;/li&gt;
&lt;li&gt;The pointcut &lt;code&gt;execution(* com.example.service..*(..))&lt;/code&gt; targets every method under the service package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Aspects are good at:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business-level cross-cutting: &lt;code&gt;@Transactional&lt;/code&gt;, &lt;code&gt;@Cacheable&lt;/code&gt;, retries, method-level security, a custom &lt;code&gt;@Audited&lt;/code&gt; annotation.&lt;/li&gt;
&lt;li&gt;Work that is not about HTTP at all — it fires no matter who called the method.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ⚠️ Easy to confuse — the proxy gotcha (self-invocation)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Because AOP works through a proxy, calling one method of a bean &lt;strong&gt;from another method of the same bean&lt;/strong&gt; skips the proxy — the advice does &lt;strong&gt;not&lt;/strong&gt; run.&lt;/li&gt;
&lt;li&gt;The reason: &lt;code&gt;this.otherMethod()&lt;/code&gt; goes straight to the real object and never touches the wrapper.&lt;/li&gt;
&lt;li&gt;This is the classic "&lt;code&gt;@Transactional&lt;/code&gt; did nothing" bug: an internal call bypassed the proxy that would have opened the transaction.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 Bad vs good: picking the wrong layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;❌ Putting per-endpoint auth in a Filter&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A filter cannot see which controller method will run, so you end up re-parsing the URL by hand to decide the rule. Fragile, and it duplicates Spring's own routing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ Use an interceptor (or method security) instead&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It already knows the handler and its annotations, so the rule lives next to where the endpoint is defined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Using AOP to set an HTTP response header&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your service method has no &lt;code&gt;HttpServletResponse&lt;/code&gt; — you would have to smuggle one in. Wrong layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ Use a filter or interceptor&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They hold the raw response and can set headers directly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Easy to confuse — quick separations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filter vs Interceptor:&lt;/strong&gt; both wrap web requests. A &lt;strong&gt;filter&lt;/strong&gt; is at the servlet level, has no idea which controller runs, and fires even for non-Spring requests. An &lt;strong&gt;interceptor&lt;/strong&gt; is inside Spring MVC, knows the handler, and only fires for requests the DispatcherServlet routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interceptor vs Aspect:&lt;/strong&gt; an &lt;strong&gt;interceptor&lt;/strong&gt; is web-only and lifecycle-based (pre/post/after). An &lt;strong&gt;aspect&lt;/strong&gt; targets any method by pointcut and fires for non-web calls too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "continue" call in each:&lt;/strong&gt; &lt;code&gt;chain.doFilter()&lt;/code&gt;, &lt;code&gt;return true&lt;/code&gt;, and &lt;code&gt;pjp.proceed()&lt;/code&gt; all mean "go on to the next thing." Skip any of them and the flow stops there.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;Interceptor&lt;/th&gt;
&lt;th&gt;Aspect (AOP)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level&lt;/td&gt;
&lt;td&gt;Servlet container&lt;/td&gt;
&lt;td&gt;Spring MVC (DispatcherServlet)&lt;/td&gt;
&lt;td&gt;Any Spring bean method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;td&gt;Servlet (Jakarta)&lt;/td&gt;
&lt;td&gt;Spring MVC&lt;/td&gt;
&lt;td&gt;Spring AOP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sees&lt;/td&gt;
&lt;td&gt;raw request/response&lt;/td&gt;
&lt;td&gt;handler + request/response&lt;/td&gt;
&lt;td&gt;method args + return value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knows the controller?&lt;/td&gt;
&lt;td&gt;❌ no&lt;/td&gt;
&lt;td&gt;✅ yes&lt;/td&gt;
&lt;td&gt;it &lt;em&gt;is&lt;/em&gt; a bean method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fires for non-web calls?&lt;/td&gt;
&lt;td&gt;❌ no&lt;/td&gt;
&lt;td&gt;❌ no&lt;/td&gt;
&lt;td&gt;✅ yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Continue" call&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chain.doFilter()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;return true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pjp.proceed()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;CORS, compression, security gate&lt;/td&gt;
&lt;td&gt;per-endpoint auth, MVC logging&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Transactional&lt;/code&gt;, caching, audit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Decision rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Need it for &lt;strong&gt;every HTTP request&lt;/strong&gt; — even errors and static files — or must it run &lt;strong&gt;before Spring&lt;/strong&gt;? → &lt;strong&gt;Filter&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Need to &lt;strong&gt;know which controller/endpoint&lt;/strong&gt; runs, or hook the MVC lifecycle? → &lt;strong&gt;Interceptor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Need it around &lt;strong&gt;service/business methods&lt;/strong&gt;, or for &lt;strong&gt;non-web&lt;/strong&gt; calls too? → &lt;strong&gt;Aspect (AOP)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Rough guide: &lt;strong&gt;outer = HTTP plumbing, inner = business logic.&lt;/strong&gt; Pick the outermost layer that still has everything you need.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Remember this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Three layers, outer to inner: &lt;strong&gt;Filter → Interceptor → Aspect.&lt;/strong&gt; Each sees less raw HTTP and more Spring/business detail.&lt;/li&gt;
&lt;li&gt;The "continue" step is the heart of all three: &lt;code&gt;chain.doFilter&lt;/code&gt; / &lt;code&gt;return true&lt;/code&gt; / &lt;code&gt;proceed()&lt;/code&gt;. Skip it to stop the flow.&lt;/li&gt;
&lt;li&gt;Only the &lt;strong&gt;filter&lt;/strong&gt; runs without Spring MVC; only the &lt;strong&gt;aspect&lt;/strong&gt; fires for non-web method calls.&lt;/li&gt;
&lt;li&gt;AOP works through a &lt;strong&gt;proxy&lt;/strong&gt;, so a self-invocation inside the same bean silently skips the advice.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Exception handling: @ExceptionHandler / @ControllerAdvice / ProblemDetail</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Fri, 18 Sep 2026 09:07:41 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/exception-handling-exceptionhandler-controlleradvice-problemdetail-52n0</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/exception-handling-exceptionhandler-controlleradvice-problemdetail-52n0</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The big idea in one line
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When your controller code throws, &lt;strong&gt;something has to turn that exception into an HTTP response&lt;/strong&gt; — a status code and a body — instead of leaking a stack trace.&lt;/li&gt;
&lt;li&gt;Spring gives you a small set of hooks to say &lt;em&gt;"when this exception happens, send back this status and this body."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A REST endpoint can't just crash. The caller is a program that needs a &lt;strong&gt;clear status code&lt;/strong&gt; (404, 400, 409…) and a &lt;strong&gt;predictable error body&lt;/strong&gt; to react to.&lt;/li&gt;
&lt;li&gt;Without a plan, every controller writes its own &lt;code&gt;try/catch&lt;/code&gt;, and error responses come out inconsistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you meet it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The moment you build a real API. As soon as "the user wasn't found" or "that input was invalid" needs to become a proper HTTP response.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌊 What Spring does when you do nothing
&lt;/h2&gt;

&lt;p&gt;Before adding any error handling, it helps to see the default path.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Spring MVC, one servlet receives &lt;strong&gt;every&lt;/strong&gt; incoming request and routes it to the right controller method. That single entry point is the &lt;strong&gt;DispatcherServlet&lt;/strong&gt; — think of it as the front door that dispatches each request to a handler.&lt;/li&gt;
&lt;li&gt;If your controller method throws and nothing catches it, the exception travels back up to that front door.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;HTTP request
   │
   ▼
DispatcherServlet ──► your @Controller method  ──► throws RuntimeException
   │                                                    │
   │   ◄────────────── exception bubbles back up ───────┘
   ▼
Exception resolvers  ──►  default: "Whitelabel Error Page" / generic 500 JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The DispatcherServlet hands the exception to a chain of &lt;strong&gt;exception resolvers&lt;/strong&gt; — objects whose job is to convert an exception into a response.&lt;/li&gt;
&lt;li&gt;With no configuration, the default resolver produces a generic &lt;strong&gt;500 Internal Server Error&lt;/strong&gt; (the plain "Whitelabel Error Page" in a browser, or a bare JSON error).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;500&lt;/code&gt; is wrong for most errors. "User not found" should be &lt;strong&gt;404&lt;/strong&gt;; "bad input" should be &lt;strong&gt;400&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The body is generic. Your API client gets nothing useful to branch on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the whole topic is really: &lt;strong&gt;how do I plug into that resolver step and return the right status and body?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Tool 1 — &lt;code&gt;@ExceptionHandler&lt;/code&gt; on a controller
&lt;/h2&gt;

&lt;p&gt;The first hook lives right inside a controller. You write a method and mark it as the handler for a given exception type.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suppose a lookup fails and you throw a custom &lt;code&gt;UserNotFoundException&lt;/code&gt;.
&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="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&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="s"&gt;"/users/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&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;new&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleNotFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&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="mi"&gt;404&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&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;What happened here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@ExceptionHandler(UserNotFoundException.class)&lt;/code&gt;&lt;/strong&gt; marks a method as the catch point for that exception &lt;em&gt;within this controller&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;When any method in &lt;code&gt;UserController&lt;/code&gt; throws &lt;code&gt;UserNotFoundException&lt;/code&gt;, Spring skips the normal return path and calls &lt;code&gt;handleNotFound&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;The method returns a &lt;strong&gt;&lt;code&gt;ResponseEntity&lt;/code&gt;&lt;/strong&gt; — a full HTTP response: status line, headers, and body, all under your control. Here: status &lt;code&gt;404&lt;/code&gt;, body = the message.&lt;/li&gt;
&lt;li&gt;The exception object is passed in, so you can read its details to build the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Easy to confuse: throwing vs. returning.&lt;/strong&gt; The controller method &lt;em&gt;throws&lt;/em&gt;; the handler method &lt;em&gt;returns&lt;/em&gt;. The handler is not in the call stack of the failing method — Spring catches the exception and invokes the handler separately.&lt;/p&gt;




&lt;h3&gt;
  
  
  A shortcut for simple cases: &lt;code&gt;@ResponseStatus&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If all you want is "this exception means this status code," you can skip the handler method entirely.
&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="nd"&gt;@ResponseStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 404&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No user with id "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@ResponseStatus&lt;/code&gt;&lt;/strong&gt; on the exception class tells Spring: whenever this exception reaches the resolver, respond with this status.&lt;/li&gt;
&lt;li&gt;No handler method needed. Good for simple, body-less cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ Use &lt;code&gt;@ResponseStatus&lt;/code&gt; when the status &lt;em&gt;is&lt;/em&gt; the whole response.&lt;/p&gt;

&lt;p&gt;❌ Avoid it when you need a structured body or to add headers — reach for a handler method instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌍 Tool 2 — &lt;code&gt;@ControllerAdvice&lt;/code&gt; for the whole app
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@ExceptionHandler&lt;/code&gt; inside one controller only helps that controller. Real apps have many controllers that throw the same errors. Copying handlers everywhere is the duplication we wanted to avoid.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The fix is a class that holds handlers shared by &lt;strong&gt;every&lt;/strong&gt; controller. In Spring, a class of cross-cutting controller logic is called an &lt;strong&gt;advice&lt;/strong&gt;.
&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="nd"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalErrorHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleNotFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&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="mi"&gt;404&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleBadInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;badRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 400&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;Walking through it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@ControllerAdvice&lt;/code&gt;&lt;/strong&gt; marks a class whose &lt;code&gt;@ExceptionHandler&lt;/code&gt; methods apply to &lt;em&gt;all&lt;/em&gt; controllers, not just one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;&lt;/strong&gt; is the same thing plus &lt;code&gt;@ResponseBody&lt;/code&gt; behavior baked in — the returned object becomes the JSON body directly. Use it for REST APIs.&lt;/li&gt;
&lt;li&gt;One class now owns the mapping from exception → response for the entire application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Easy to confuse — the two advice annotations.&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;Annotation&lt;/th&gt;
&lt;th&gt;Applies to all controllers?&lt;/th&gt;
&lt;th&gt;Return value becomes response body?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@ControllerAdvice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only if you add &lt;code&gt;@ResponseBody&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For JSON APIs, &lt;code&gt;@RestControllerAdvice&lt;/code&gt; is almost always what you want.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Spring picks &lt;em&gt;which&lt;/em&gt; handler runs
&lt;/h3&gt;

&lt;p&gt;With handlers in both a controller and a global advice, Spring needs a rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Exception thrown
      │
      ▼
&lt;span class="p"&gt;1.&lt;/span&gt; Look in the SAME controller for a matching @ExceptionHandler  ──► found? use it
      │ (none)
      ▼
&lt;span class="p"&gt;2.&lt;/span&gt; Look in @ControllerAdvice classes                            ──► found? use it
      │ (none)
      ▼
&lt;span class="p"&gt;3.&lt;/span&gt; Fall back to default resolver (generic 500)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Controller-local handlers win&lt;/strong&gt; over global ones. A controller can override the app-wide behavior for its own errors.&lt;/li&gt;
&lt;li&gt;Among matching handlers, Spring prefers the one whose exception type is &lt;strong&gt;most specific&lt;/strong&gt; (closest in the class hierarchy). A handler for &lt;code&gt;UserNotFoundException&lt;/code&gt; beats a handler for &lt;code&gt;RuntimeException&lt;/code&gt; when a &lt;code&gt;UserNotFoundException&lt;/code&gt; is thrown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This "most specific wins" rule is why a broad &lt;code&gt;@ExceptionHandler(Exception.class)&lt;/code&gt; is a safe &lt;strong&gt;catch-all&lt;/strong&gt; and not a trap — narrower handlers still take priority.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Tool 3 — &lt;code&gt;ProblemDetail&lt;/code&gt; for a standard error body
&lt;/h2&gt;

&lt;p&gt;Returning a bare string works, but every endpoint can shape its errors differently, and clients hate guessing. There is a standard for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProblemDetail&lt;/code&gt;&lt;/strong&gt; is Spring's built-in model for &lt;strong&gt;RFC 9457&lt;/strong&gt; ("Problem Details for HTTP APIs") — an agreed-upon JSON shape for errors, sent with the media type &lt;code&gt;application/problem+json&lt;/code&gt;. (Available from Spring Framework 6 / Spring Boot 3.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standard fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URI identifying the error kind (a stable id, optionally a doc link)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short human-readable summary of the error kind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The HTTP status code, repeated in the body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Human-readable explanation of &lt;em&gt;this&lt;/em&gt; occurrence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URI for this specific occurrence (e.g. the request path)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a handler that returns one:&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;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;ProblemDetail&lt;/span&gt; &lt;span class="nf"&gt;handleNotFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forStatusAndDetail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTitle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User not found"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/errors/user-not-found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// custom extension field&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&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;What each line does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProblemDetail.forStatusAndDetail(...)&lt;/code&gt;&lt;/strong&gt; builds the object with the status and the per-occurrence &lt;code&gt;detail&lt;/code&gt; message set.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setTitle&lt;/code&gt; / &lt;code&gt;setType&lt;/code&gt; fill in the stable, error-&lt;em&gt;kind&lt;/em&gt; fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;setProperty&lt;/code&gt;&lt;/strong&gt; adds a custom field beyond the standard ones — the spec allows these &lt;strong&gt;extensions&lt;/strong&gt;, so you can attach domain data like &lt;code&gt;userId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Returning a &lt;code&gt;ProblemDetail&lt;/code&gt; from a &lt;code&gt;@RestControllerAdvice&lt;/code&gt; makes Spring serialize it as &lt;code&gt;application/problem+json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response body looks like this:&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/errors/user-not-found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No user with id 42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&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;Why this is worth it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every error across the API has the &lt;strong&gt;same shape&lt;/strong&gt;, so clients write one parser.&lt;/li&gt;
&lt;li&gt;It's a public standard, so tools and other teams already understand it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Handling Spring's own errors: &lt;code&gt;ResponseEntityExceptionHandler&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Not every exception is yours. Spring MVC itself throws for things like a malformed body or a missing parameter — and by default those become plain responses that don't match your &lt;code&gt;ProblemDetail&lt;/code&gt; style.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extend &lt;strong&gt;&lt;code&gt;ResponseEntityExceptionHandler&lt;/code&gt;&lt;/strong&gt; in your advice. It already has &lt;code&gt;@ExceptionHandler&lt;/code&gt; methods for the built-in MVC exceptions (bad JSON, unsupported media type, validation failures, and more), and in Spring 6 it returns &lt;code&gt;ProblemDetail&lt;/code&gt; bodies too.
&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="nd"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalErrorHandler&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntityExceptionHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt; &lt;span class="nf"&gt;handleNotFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forStatusAndDetail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// framework exceptions are handled by the parent class, as ProblemDetail&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Your custom handlers sit alongside the inherited ones.&lt;/li&gt;
&lt;li&gt;Now &lt;strong&gt;both&lt;/strong&gt; your errors and Spring's framework errors come back in the same standard format. One consistent contract for the whole API.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚧 Gotchas the mechanism creates
&lt;/h2&gt;

&lt;p&gt;These follow directly from &lt;em&gt;how&lt;/em&gt; the resolver works.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Errors before the controller are invisible here.&lt;/strong&gt; Requests pass through &lt;strong&gt;filters&lt;/strong&gt; (a layer that runs before the DispatcherServlet) for things like authentication. An exception thrown in a filter never reaches &lt;code&gt;@ControllerAdvice&lt;/code&gt; — the request hasn't entered the dispatch machinery yet. Security errors (401/403) are usually handled by a separate mechanism, not your advice.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;request ─► Filters ─► DispatcherServlet ─► Controller
             ▲                │
       throws here            └─ throws here → @ControllerAdvice CAN handle
       → advice CANNOT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An advice can't handle an exception thrown by another advice.&lt;/strong&gt; If your handler method itself throws, that new exception is not re-fed into the resolver chain — you'll fall back to a generic 500. Keep handler methods simple and safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order among advices isn't guaranteed&lt;/strong&gt; unless you set it. If two &lt;code&gt;@ControllerAdvice&lt;/code&gt; classes both match an exception, use &lt;code&gt;@Order&lt;/code&gt; to make the winner explicit rather than relying on chance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't over-catch.&lt;/strong&gt; A handler for &lt;code&gt;Exception.class&lt;/code&gt; will also swallow bugs you'd rather see as a loud 500. Handle the exceptions you understand; let truly unexpected ones fall through to a generic 500 (or a deliberate catch-all that still logs the stack trace).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A returned status beats the annotation.&lt;/strong&gt; If a handler returns a &lt;code&gt;ResponseEntity&lt;/code&gt; with an explicit status, that status is used — the &lt;code&gt;@ResponseStatus&lt;/code&gt; on the exception class is ignored for that path. Pick one source of truth per exception.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@ResponseStatus&lt;/code&gt; on exception&lt;/td&gt;
&lt;td&gt;That exception, everywhere&lt;/td&gt;
&lt;td&gt;Status-only errors, no body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@ExceptionHandler&lt;/code&gt; in a controller&lt;/td&gt;
&lt;td&gt;One controller&lt;/td&gt;
&lt;td&gt;Errors special to that controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@ControllerAdvice&lt;/code&gt; / &lt;code&gt;@RestControllerAdvice&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All controllers&lt;/td&gt;
&lt;td&gt;App-wide error mapping (the default home)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProblemDetail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The response body&lt;/td&gt;
&lt;td&gt;A standard, consistent error shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseEntityExceptionHandler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spring's own MVC errors&lt;/td&gt;
&lt;td&gt;Making framework errors match your style&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Selection order Spring uses: &lt;strong&gt;controller-local handler → global advice → default 500&lt;/strong&gt;, and within each, &lt;strong&gt;most specific exception type wins&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Decision rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Just need a status code, no body → &lt;strong&gt;&lt;code&gt;@ResponseStatus&lt;/code&gt;&lt;/strong&gt; on the exception.&lt;/li&gt;
&lt;li&gt;Need a custom body or headers → an &lt;strong&gt;&lt;code&gt;@ExceptionHandler&lt;/code&gt;&lt;/strong&gt; method.&lt;/li&gt;
&lt;li&gt;The same error appears across many controllers → move the handler into &lt;strong&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Building a real API clients depend on → return &lt;strong&gt;&lt;code&gt;ProblemDetail&lt;/code&gt;&lt;/strong&gt; so every error has one shape.&lt;/li&gt;
&lt;li&gt;Want Spring's built-in errors to match → extend &lt;strong&gt;&lt;code&gt;ResponseEntityExceptionHandler&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Error looks like it's being ignored → check whether it's thrown in a &lt;strong&gt;filter&lt;/strong&gt; (before dispatch) rather than a controller.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Remember this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every uncaught controller exception flows to the &lt;strong&gt;DispatcherServlet&lt;/strong&gt;, which asks &lt;strong&gt;exception resolvers&lt;/strong&gt; to turn it into a response — the default is a generic 500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@ExceptionHandler&lt;/code&gt;&lt;/strong&gt; maps an exception type to a response; put it in a &lt;strong&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;&lt;/strong&gt; to share it across the whole app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controller-local beats global, and most-specific exception type beats broader ones.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProblemDetail&lt;/code&gt;&lt;/strong&gt; (RFC 9457) gives every error one standard JSON shape; extend &lt;strong&gt;&lt;code&gt;ResponseEntityExceptionHandler&lt;/code&gt;&lt;/strong&gt; so Spring's own errors match it too.&lt;/li&gt;
&lt;li&gt;The chain only covers exceptions thrown &lt;strong&gt;inside dispatch&lt;/strong&gt; — anything from a &lt;strong&gt;filter&lt;/strong&gt; needs handling elsewhere.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bean Validation (@Valid) + binding errors</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Thu, 17 Sep 2026 09:35:27 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/bean-validation-valid-binding-errors-1309</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/bean-validation-valid-binding-errors-1309</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The big idea in one line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bean Validation&lt;/strong&gt; lets you &lt;em&gt;declare&lt;/em&gt; the rules your incoming data must obey right on the data object, and Spring checks them for you at the edge of your app — so bad input is rejected before it ever reaches your logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why it exists:&lt;/strong&gt; every web app receives junk — empty names, negative ages, malformed emails. Checking all of that by hand, in every controller method, is repetitive and easy to get wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shift:&lt;/strong&gt; instead of &lt;em&gt;writing&lt;/em&gt; checks, you &lt;em&gt;attach&lt;/em&gt; rules to the fields, and something else runs them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you meet it:&lt;/strong&gt; the moment a request carries a body or form — a signup, an order, a search filter — and you want to trust the fields before you use them.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🩹 The problem: hand-written checks everywhere
&lt;/h2&gt;

&lt;p&gt;Imagine a controller that creates a user. Without any help, you check each field yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;User&lt;/span&gt; &lt;span class="nf"&gt;create&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;UserRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;req&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;isBlank&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;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name required"&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;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAge&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&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;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"must be 18+"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... and on, and on&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;ul&gt;
&lt;li&gt;The rules are &lt;strong&gt;buried in the method&lt;/strong&gt;, mixed with real work.&lt;/li&gt;
&lt;li&gt;The same checks get &lt;strong&gt;copy-pasted&lt;/strong&gt; into every endpoint that touches a user.&lt;/li&gt;
&lt;li&gt;The error you throw is a raw exception — no clean list of &lt;em&gt;what&lt;/em&gt; was wrong.&lt;/li&gt;
&lt;li&gt;Change a rule and you must hunt down every copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rules really belong &lt;em&gt;to the data&lt;/em&gt;, not to one method. That is the idea Bean Validation makes real.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏷️ Step 1 — Rules become annotations (constraints)
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;constraint&lt;/strong&gt; is a single rule attached to a field, written as an annotation. "This must not be blank." "This must be at least 18." You put the rule &lt;em&gt;on the field it governs&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&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;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Email&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters / setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@NotBlank&lt;/code&gt;, &lt;code&gt;@Min&lt;/code&gt;, &lt;code&gt;@Email&lt;/code&gt; are &lt;strong&gt;constraint annotations&lt;/strong&gt; — each names one rule.&lt;/li&gt;
&lt;li&gt;The rules now live &lt;strong&gt;with the data&lt;/strong&gt;, readable at a glance, defined once.&lt;/li&gt;
&lt;li&gt;These annotations come from the &lt;strong&gt;Jakarta Bean Validation&lt;/strong&gt; standard (the specification; &lt;strong&gt;Hibernate Validator&lt;/strong&gt; is the usual implementation that actually enforces them). It is a Java standard, not a Spring invention — Spring just plugs into it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick tour of the everyday constraints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Passes when…&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@NotNull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;value is not null&lt;/td&gt;
&lt;td&gt;says nothing about emptiness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@NotEmpty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not null &lt;strong&gt;and&lt;/strong&gt; length/size &amp;gt; 0&lt;/td&gt;
&lt;td&gt;for String, Collection, Map, array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@NotBlank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not null &lt;strong&gt;and&lt;/strong&gt; has non-whitespace text&lt;/td&gt;
&lt;td&gt;String only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@Min&lt;/code&gt; / &lt;code&gt;@Max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;number ≥ / ≤ a bound&lt;/td&gt;
&lt;td&gt;on numeric types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Size(min, max)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;length/size in range&lt;/td&gt;
&lt;td&gt;String or collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;looks like an email&lt;/td&gt;
&lt;td&gt;format check only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Pattern(regexp)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;matches a regex&lt;/td&gt;
&lt;td&gt;your own format rule&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Easy to confuse:&lt;/strong&gt; the three "not empty" checks are different.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@NotNull&lt;/code&gt; → only rejects &lt;code&gt;null&lt;/code&gt;. An empty string passes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@NotEmpty&lt;/code&gt; → rejects &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;""&lt;/code&gt;, but &lt;code&gt;"   "&lt;/code&gt; (spaces) passes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@NotBlank&lt;/code&gt; → rejects &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;""&lt;/code&gt;, and &lt;code&gt;"   "&lt;/code&gt;. For user-typed strings, this is almost always the one you want.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚙️ Step 2 — Who actually runs the rules?
&lt;/h2&gt;

&lt;p&gt;Declaring a rule does nothing on its own — something has to read the annotations and check the object. That something is a &lt;strong&gt;validator&lt;/strong&gt;: an object that takes your populated data object, runs every constraint on it, and reports back the failures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Boot, when the validation library is on the classpath, builds a validator and wires it in automatically — you don't create one by hand.&lt;/li&gt;
&lt;li&gt;You get that library through the starter:
&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="c1"&gt;// build.gradle&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;spring&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;starter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Without this dependency the annotations are just &lt;strong&gt;silently ignored&lt;/strong&gt; — a classic "why isn't my validation running?" trap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now we have rules on the object and a validator ready to run them. The last piece is &lt;em&gt;telling Spring to actually run it&lt;/em&gt; on a request.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Step 3 — &lt;code&gt;@Valid&lt;/code&gt; triggers the check at the boundary
&lt;/h2&gt;

&lt;p&gt;Spring reads a JSON body or form into your object automatically — this mapping of request data onto object fields is called &lt;strong&gt;binding&lt;/strong&gt;. You mark the bound parameter with &lt;strong&gt;&lt;code&gt;@Valid&lt;/code&gt;&lt;/strong&gt; to say: &lt;em&gt;after binding, run the validator on it.&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;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;User&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// reached ONLY if every constraint passed&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;ul&gt;
&lt;li&gt;
&lt;code&gt;@RequestBody&lt;/code&gt; binds the JSON into &lt;code&gt;req&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Valid&lt;/code&gt; tells Spring to validate &lt;code&gt;req&lt;/code&gt; right after binding, before your code runs.&lt;/li&gt;
&lt;li&gt;If everything passes, the method body runs with data you can trust.&lt;/li&gt;
&lt;li&gt;If anything fails, &lt;strong&gt;the method body never runs&lt;/strong&gt; — Spring stops at the boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failures collected during binding and validation are called &lt;strong&gt;binding errors&lt;/strong&gt;. The next question is: where do they go?&lt;/p&gt;




&lt;h2&gt;
  
  
  📥 Step 4 — Where the errors live, and the two paths
&lt;/h2&gt;

&lt;p&gt;Every failure lands in an &lt;strong&gt;&lt;code&gt;Errors&lt;/code&gt;&lt;/strong&gt; object (its common subtype is &lt;strong&gt;&lt;code&gt;BindingResult&lt;/code&gt;&lt;/strong&gt;) — a container holding each thing that went wrong. What Spring does with it depends on &lt;strong&gt;whether you ask for that container as a parameter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path A — Spring throws.&lt;/strong&gt; No &lt;code&gt;BindingResult&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;User&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="n"&gt;req&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;ul&gt;
&lt;li&gt;Validation fails → Spring raises an exception and your method is skipped.&lt;/li&gt;
&lt;li&gt;With no handler, the client gets an automatic &lt;code&gt;400 Bad Request&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This is the common, clean choice: let it throw, handle it in one place (see Step 6).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Path B — you inspect it yourself.&lt;/strong&gt; Add a &lt;code&gt;BindingResult&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                &lt;span class="nc"&gt;BindingResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasErrors&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;badRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAllErrors&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;ul&gt;
&lt;li&gt;The &lt;code&gt;BindingResult&lt;/code&gt; &lt;strong&gt;catches&lt;/strong&gt; the errors instead of letting them throw.&lt;/li&gt;
&lt;li&gt;Now the method &lt;em&gt;does&lt;/em&gt; run, and you decide what to do with the failures.
&amp;gt; ⚠️ &lt;strong&gt;The parameter order is a hard rule.&lt;/strong&gt; The &lt;code&gt;BindingResult&lt;/code&gt; must come &lt;strong&gt;immediately after&lt;/strong&gt; the object it validates. Put anything between them and Spring goes back to Path A and throws — a subtle, much-hit bug.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;JSON/form  ──►  bind to object  ──►  @Valid runs validator
                                         │
                       ┌─────────────────┴─────────────────┐
                       ▼                                     ▼
             BindingResult param?                    no such param
                       │                                     │
               inspect result yourself             Spring throws → 400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧩 Step 5 — The failure looks different for JSON vs forms
&lt;/h2&gt;

&lt;p&gt;The exception Spring throws is &lt;strong&gt;not the same&lt;/strong&gt; depending on how the data arrived. Both carry a &lt;code&gt;BindingResult&lt;/code&gt; inside, but they have different types — which matters when you write a handler.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input style&lt;/th&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Exception on failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JSON body&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Valid @RequestBody&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form / query params&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Valid @ModelAttribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BindException&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;For a REST API sending JSON, you will almost always be handling &lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both expose the same &lt;code&gt;getBindingResult()&lt;/code&gt;, so once you have the result the handling code looks the same.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📤 Step 6 — Turning binding errors into a clean response
&lt;/h2&gt;

&lt;p&gt;You rarely want the raw exception page. You catch it in &lt;strong&gt;one place&lt;/strong&gt; and shape a tidy reply. Spring gives a standard error body type, &lt;strong&gt;&lt;code&gt;ProblemDetail&lt;/code&gt;&lt;/strong&gt; (RFC 9457), so responses look consistent:&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;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidationAdvice&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodArgumentNotValidException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodArgumentNotValidException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProblemDetail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTitle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Validation failed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"errors"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBindingResult&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getFieldErrors&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;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;FieldError:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getField&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                      &lt;span class="nl"&gt;FieldError:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getDefaultMessage&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;pd&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;ul&gt;
&lt;li&gt;
&lt;code&gt;getFieldErrors()&lt;/code&gt; gives one entry per field that failed.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;FieldError&lt;/code&gt; knows the field name and a message (&lt;code&gt;getDefaultMessage()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The result is a &lt;code&gt;400&lt;/code&gt; with a clean &lt;code&gt;{ field: message }&lt;/code&gt; map — the client learns exactly what to fix.&lt;/li&gt;
&lt;li&gt;Wiring these handlers in one advice class is its own topic; here just note that a thrown validation error becomes a friendly response in a single spot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can control the message per constraint:&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;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name is required"&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;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Field errors&lt;/strong&gt; are tied to one field (&lt;code&gt;name&lt;/code&gt; was blank).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global errors&lt;/strong&gt; (also called object errors) are about the whole object — e.g. "password and confirmation must match," a rule that spans two fields.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🪆 Step 7 — Nested objects and collections
&lt;/h2&gt;

&lt;p&gt;Validation does &lt;strong&gt;not&lt;/strong&gt; automatically dive into nested objects. You must mark the nested field with &lt;code&gt;@Valid&lt;/code&gt; too, or its constraints are skipped.&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;OrderRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Valid&lt;/span&gt;                       &lt;span class="c1"&gt;// &amp;lt;-- without this, Address rules are ignored&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Valid&lt;/span&gt;                       &lt;span class="c1"&gt;// &amp;lt;-- validates every Item in the list&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&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;ul&gt;
&lt;li&gt;
&lt;code&gt;@Valid&lt;/code&gt; on &lt;code&gt;address&lt;/code&gt; tells the validator to descend into the &lt;code&gt;Address&lt;/code&gt; object and run &lt;em&gt;its&lt;/em&gt; constraints.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Valid&lt;/code&gt; on a &lt;code&gt;List&lt;/code&gt; validates &lt;strong&gt;each element&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Forget the inner &lt;code&gt;@Valid&lt;/code&gt; and the nested rules quietly never run — another silent trap.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔀 Step 8 — &lt;code&gt;@Validated&lt;/code&gt;: groups and validating single params
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Valid&lt;/code&gt; is the plain standard annotation. Spring adds its own &lt;strong&gt;&lt;code&gt;@Validated&lt;/code&gt;&lt;/strong&gt;, which does two extra things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Validation groups&lt;/strong&gt; — apply different rules in different situations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A field might be required on &lt;em&gt;update&lt;/em&gt; but not on &lt;em&gt;create&lt;/em&gt;. You tag constraints with a &lt;strong&gt;group&lt;/strong&gt; (a marker interface) and activate the group you want.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Update&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="c1"&gt;// required only when updating&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Create&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Update&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// activate a group for this endpoint:&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Validated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Update&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;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="n"&gt;req&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;ul&gt;
&lt;li&gt;
&lt;code&gt;@Valid&lt;/code&gt; cannot select a group; &lt;code&gt;@Validated(Group.class)&lt;/code&gt; can.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Validating loose method parameters&lt;/strong&gt; — not a whole object.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To validate a bare &lt;code&gt;@RequestParam&lt;/code&gt; or &lt;code&gt;@PathVariable&lt;/code&gt;, you put &lt;code&gt;@Validated&lt;/code&gt; on the &lt;strong&gt;class&lt;/strong&gt;, then constraints directly on the parameters:
&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="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@Validated&lt;/span&gt;                              &lt;span class="c1"&gt;// &amp;lt;-- enables param-level checks&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchController&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="s"&gt;"/search"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Hit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nd"&gt;@Min&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                     &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nd"&gt;@Size&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&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;q&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Here the failure is a &lt;strong&gt;third&lt;/strong&gt; exception type: &lt;code&gt;ConstraintViolationException&lt;/code&gt; (not the two from Step 5), because there is no object and no &lt;code&gt;BindingResult&lt;/code&gt; — just individual parameters.
&amp;gt; ⚠️ &lt;strong&gt;Easy to confuse:&lt;/strong&gt; &lt;code&gt;@Valid&lt;/code&gt; and &lt;code&gt;@Validated&lt;/code&gt; are not the same annotation.
&amp;gt; - &lt;code&gt;@Valid&lt;/code&gt; → the Java standard annotation. Validates a whole object, cascades into nested &lt;code&gt;@Valid&lt;/code&gt; fields. No groups.
&amp;gt; - &lt;code&gt;@Validated&lt;/code&gt; → Spring's annotation. Supports &lt;strong&gt;groups&lt;/strong&gt;, and on a &lt;strong&gt;class&lt;/strong&gt; enables validating single &lt;code&gt;@RequestParam&lt;/code&gt; / &lt;code&gt;@PathVariable&lt;/code&gt; values.
&amp;gt; - Rule of thumb: use &lt;code&gt;@Valid&lt;/code&gt; on the body object; use &lt;code&gt;@Validated&lt;/code&gt; when you need groups or method-parameter checks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Step 9 — Writing your own constraint (briefly)
&lt;/h2&gt;

&lt;p&gt;When no built-in rule fits, you can define one. A custom constraint is two pieces: an &lt;strong&gt;annotation&lt;/strong&gt; and a &lt;strong&gt;validator class&lt;/strong&gt; that holds the logic.&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;@Constraint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validatedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NotReservedValidator&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;@Target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FIELD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetentionPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;NotReserved&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s"&gt;"value is reserved"&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;?&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&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="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Payload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="nf"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotReservedValidator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ConstraintValidator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NotReserved&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;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isValid&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;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ConstraintValidatorContext&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin"&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;ul&gt;
&lt;li&gt;The annotation points to its validator with &lt;code&gt;validatedBy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isValid&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;; the three members (&lt;code&gt;message&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;, &lt;code&gt;payload&lt;/code&gt;) are required boilerplate the spec expects.&lt;/li&gt;
&lt;li&gt;Now &lt;code&gt;@NotReserved&lt;/code&gt; behaves like any built-in constraint — reusable across your whole app.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Step 10 — Traps the mechanism creates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Missing dependency, silent no-op.&lt;/strong&gt; Without &lt;code&gt;spring-boot-starter-validation&lt;/code&gt; on the classpath the annotations are simply ignored. Validation "works on my machine" but not after a slimmed-down build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misplaced result container.&lt;/strong&gt; The &lt;code&gt;BindingResult&lt;/code&gt; must sit &lt;em&gt;immediately&lt;/em&gt; after the validated parameter, or Spring throws instead of handing it to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgotten nested cascade.&lt;/strong&gt; Inner objects and list elements are only validated when their field carries &lt;code&gt;@Valid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong emptiness check.&lt;/strong&gt; A blank string sails past &lt;code&gt;@NotNull&lt;/code&gt;. Pick the constraint (&lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@NotEmpty&lt;/code&gt;, &lt;code&gt;@NotBlank&lt;/code&gt;) that matches what "empty" means for that field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groups without the right annotation.&lt;/strong&gt; Groups only fire under &lt;code&gt;@Validated&lt;/code&gt;; using plain &lt;code&gt;@Valid&lt;/code&gt; silently applies the default group and skips your group-specific rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three exception types, one habit.&lt;/strong&gt; JSON gives &lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;, forms give &lt;code&gt;BindException&lt;/code&gt;, loose params give &lt;code&gt;ConstraintViolationException&lt;/code&gt;. A handler written for one will not catch the others.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Constraint (&lt;code&gt;@NotBlank&lt;/code&gt;, &lt;code&gt;@Min&lt;/code&gt;, …)&lt;/td&gt;
&lt;td&gt;one rule, declared on the field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validator&lt;/td&gt;
&lt;td&gt;runs the rules on the object (auto-wired by Boot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Valid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trigger validation after binding; cascades into nested &lt;code&gt;@Valid&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Validated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spring's variant: groups + single-parameter validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;BindingResult&lt;/code&gt; / &lt;code&gt;Errors&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;holds the failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;thrown for a failed &lt;code&gt;@RequestBody&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BindException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;thrown for a failed form / &lt;code&gt;@ModelAttribute&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConstraintViolationException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;thrown for failed loose &lt;code&gt;@RequestParam&lt;/code&gt; / &lt;code&gt;@PathVariable&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@ExceptionHandler&lt;/code&gt;  • &lt;code&gt;ProblemDetail&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;turn the failure into a clean 400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Decision rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validating a request body or form object?&lt;/strong&gt; → put &lt;code&gt;@Valid&lt;/code&gt; on the parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want to inspect errors inline?&lt;/strong&gt; → add a &lt;code&gt;BindingResult&lt;/code&gt; &lt;em&gt;right after&lt;/em&gt; it. Otherwise let it throw and handle centrally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need rules that differ by create/update, or to validate a bare param?&lt;/strong&gt; → reach for &lt;code&gt;@Validated&lt;/code&gt; (with a group, or on the class).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation not running at all?&lt;/strong&gt; → check the &lt;code&gt;spring-boot-starter-validation&lt;/code&gt; dependency first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nested object or list not being checked?&lt;/strong&gt; → add &lt;code&gt;@Valid&lt;/code&gt; on that field.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Remember this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rules live on the data as annotations; Spring runs them for you at the boundary.&lt;/strong&gt; That is the whole point — no hand-written checks scattered through controllers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The trigger, then the container.&lt;/strong&gt; &lt;code&gt;@Valid&lt;/code&gt; runs the check; the errors land in a &lt;code&gt;BindingResult&lt;/code&gt; — ask for that parameter to inspect them, or let Spring throw a &lt;code&gt;400&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two annotations, two jobs.&lt;/strong&gt; Use &lt;code&gt;@Valid&lt;/code&gt; for whole objects and &lt;code&gt;@Validated&lt;/code&gt; for groups and single parameters — and the exception type depends on how the data arrived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silence usually means a missing piece:&lt;/strong&gt; the starter dependency, a misplaced &lt;code&gt;BindingResult&lt;/code&gt;, or a forgotten nested &lt;code&gt;@Valid&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Content negotiation &amp; message converters (Jackson)</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Wed, 16 Sep 2026 09:25:29 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/content-negotiation-message-converters-jackson-233m</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/content-negotiation-message-converters-jackson-233m</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The big idea in one line
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your controller returns a plain Java object, and Spring quietly turns it into JSON (or reads JSON back into an object) — &lt;strong&gt;content negotiation&lt;/strong&gt; is how Spring decides &lt;em&gt;which&lt;/em&gt; format, and a &lt;strong&gt;message converter&lt;/strong&gt; is the thing that actually does the translation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this topic exists:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A browser, a mobile app, and a curl script can all call the same endpoint but want the body in different shapes: JSON, XML, plain text, a PDF.&lt;/li&gt;
&lt;li&gt;You do not want to write &lt;code&gt;if (wantsJson) ... else if (wantsXml) ...&lt;/code&gt; in every method.&lt;/li&gt;
&lt;li&gt;Spring pushes that decision to the edge of the request, so your method stays about business logic and returns one object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When you meet it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The moment you write &lt;code&gt;@RestController&lt;/code&gt; and return an object instead of a view name.&lt;/li&gt;
&lt;li&gt;The first time a client gets a &lt;strong&gt;406&lt;/strong&gt; or &lt;strong&gt;415&lt;/strong&gt; error and you have no idea why.&lt;/li&gt;
&lt;li&gt;The first time a date serializes as &lt;code&gt;1699999999000&lt;/code&gt; instead of &lt;code&gt;2026-09-16&lt;/code&gt; and you go hunting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 The starting point: a method that returns an object
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In classic Spring MVC, a controller method returned a &lt;strong&gt;view name&lt;/strong&gt; — a string like &lt;code&gt;"userProfile"&lt;/code&gt; — and Spring rendered an HTML template.&lt;/li&gt;
&lt;li&gt;For an API you do not want HTML. You want the object itself, as data, in the response body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the shape almost every API method has:&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="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&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="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// just a Java object&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;ul&gt;
&lt;li&gt;The method returns a &lt;code&gt;User&lt;/code&gt;. No JSON anywhere in sight.&lt;/li&gt;
&lt;li&gt;Something between your method and the network socket has to turn that &lt;code&gt;User&lt;/code&gt; into bytes on the wire.&lt;/li&gt;
&lt;li&gt;That "something" is a message converter, and picking the right one is content negotiation. Let's build both up.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔌 What a message converter is
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;message converter&lt;/strong&gt; is an object that knows how to translate between a Java type and one HTTP body format.&lt;/li&gt;
&lt;li&gt;It works in both directions:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write:&lt;/strong&gt; Java object → bytes in the response body (serialization).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read:&lt;/strong&gt; bytes in the request body → Java object (deserialization).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spring models this with one interface, &lt;code&gt;HttpMessageConverter&lt;/code&gt;. The important part is these four questions it can answer:&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;boolean&lt;/span&gt; &lt;span class="nf"&gt;canRead&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;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt; &lt;span class="n"&gt;mediaType&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// can I turn this body into that type?&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canWrite&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;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt; &lt;span class="n"&gt;mediaType&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// can I turn that type into this body?&lt;/span&gt;
&lt;span class="nc"&gt;Object&lt;/span&gt;  &lt;span class="nf"&gt;read&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;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpInputMessage&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// do the reading&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt;    &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpOutputMessage&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// do the writing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;MediaType&lt;/code&gt;&lt;/strong&gt; is just a content type like &lt;code&gt;application/json&lt;/code&gt; or &lt;code&gt;text/plain&lt;/code&gt; — the label that says what a body &lt;em&gt;is&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Each converter is tied to one or a few media types. For example:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MappingJackson2HttpMessageConverter&lt;/code&gt; handles &lt;code&gt;application/json&lt;/code&gt; using the Jackson library.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StringHttpMessageConverter&lt;/code&gt; handles &lt;code&gt;text/plain&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ByteArrayHttpMessageConverter&lt;/code&gt; handles raw &lt;code&gt;application/octet-stream&lt;/code&gt; bytes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Spring holds a &lt;strong&gt;list of converters&lt;/strong&gt;, and for any request it asks each one, "can you handle this type and this media type?" The first that says yes wins.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Content negotiation: which format does the client want?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;response&lt;/strong&gt; side, Spring has your &lt;code&gt;User&lt;/code&gt; object and a list of converters. It still needs to know which &lt;em&gt;format&lt;/em&gt; the client wants back.&lt;/li&gt;
&lt;li&gt;Deciding that format is &lt;strong&gt;content negotiation&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client states its preference with the &lt;strong&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/strong&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;GET /users/42
Accept: application/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Accept: application/json&lt;/code&gt; means "send me JSON."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accept: application/xml&lt;/code&gt; means "send me XML."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accept: */*&lt;/code&gt; (what curl sends by default) means "I'll take anything."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow on the way out looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;controller returns User
        │
        ▼
read Accept header  ──►  application/json
        │
        ▼
find a converter where canWrite(User, application/json) == true
        │
        ▼
MappingJackson2HttpMessageConverter.write(user, ...)
        │
        ▼
{"id":42,"name":"Ada"}  ──►  response body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Spring intersects two lists: the media types the client will accept, and the media types the converters can produce.&lt;/li&gt;
&lt;li&gt;It picks the best match and hands the object to that converter.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The other strategies (and why Accept is the default)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Accept&lt;/code&gt; header is the default and preferred way to negotiate. But Spring supports a few other &lt;strong&gt;strategies&lt;/strong&gt;, each managed by a &lt;code&gt;ContentNegotiationStrategy&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;How the client asks&lt;/th&gt;
&lt;th&gt;Status today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Header&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept: application/json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Default, recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parameter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /users/42?format=json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Off by default; opt in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Path extension&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /users/42.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌ Deprecated (security risks), off by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚠️ Easy to confuse —&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;vs&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/strong&gt;&lt;strong&gt;:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/strong&gt; is on the request and describes the body the client &lt;em&gt;wants back&lt;/em&gt;. It drives the response converter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/strong&gt; describes the body that is &lt;em&gt;actually attached right now&lt;/em&gt;. On a request it labels what the client sent; on a response it labels what the server sent.&lt;/li&gt;
&lt;li&gt;Same idea (a media type), opposite direction. Mixing them up is the classic content-negotiation bug.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can turn the parameter strategy on with a small config bean:&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;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&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;configureContentNegotiation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentNegotiationConfigurer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;favorParameter&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="c1"&gt;// enable ?format=...&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parameterName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"format"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultContentType&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&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// fallback when Accept is silent&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;ul&gt;
&lt;li&gt;
&lt;code&gt;defaultContentType&lt;/code&gt; matters: if a client sends no &lt;code&gt;Accept&lt;/code&gt; header at all, this is what they get.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📥 The request side: reading a body in
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Everything so far was the response. Reading a request body works with the same converters, but the trigger and the header are different.&lt;/li&gt;
&lt;li&gt;You mark a parameter with &lt;strong&gt;&lt;code&gt;@RequestBody&lt;/code&gt;&lt;/strong&gt; to say "fill this from the request body."
&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="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;create&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;User&lt;/span&gt; &lt;span class="n"&gt;newUser&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newUser&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;ul&gt;
&lt;li&gt;Here the client sends a JSON body, and Spring must turn it into a &lt;code&gt;User&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Now the header that matters is &lt;strong&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/strong&gt;, because it describes the body the client actually sent:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;POST /users
Content-Type: application/json

{"name":"Ada"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Spring asks each converter &lt;code&gt;canRead(User, application/json)&lt;/code&gt;. Jackson's converter says yes and deserializes the body into a &lt;code&gt;User&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the two sides mirror each other:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Header read&lt;/th&gt;
&lt;th&gt;Converter method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Response out&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@ResponseBody&lt;/code&gt; / &lt;code&gt;@RestController&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;canWrite&lt;/code&gt; → &lt;code&gt;write&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request in&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@RequestBody&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;canRead&lt;/code&gt; → &lt;code&gt;read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;@RestController&lt;/code&gt; is just &lt;code&gt;@Controller&lt;/code&gt; + &lt;code&gt;@ResponseBody&lt;/code&gt; on every method, which is why returning an object "just works" without writing &lt;code&gt;@ResponseBody&lt;/code&gt; each time.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚦 When negotiation fails: 406 and 415
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Two HTTP errors come straight out of this machinery, and knowing which is which saves real debugging time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;406 Not Acceptable&lt;/strong&gt; → a &lt;em&gt;response&lt;/em&gt; problem.

&lt;ul&gt;
&lt;li&gt;The client's &lt;code&gt;Accept&lt;/code&gt; header asks for a format no converter can produce.&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;Accept: application/xml&lt;/code&gt; but you only have Jackson (JSON) on the classpath.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;415 Unsupported Media Type&lt;/strong&gt; → a &lt;em&gt;request&lt;/em&gt; problem.

&lt;ul&gt;
&lt;li&gt;The client's &lt;code&gt;Content-Type&lt;/code&gt; labels a body no converter can read.&lt;/li&gt;
&lt;li&gt;Example: client posts &lt;code&gt;Content-Type: text/yaml&lt;/code&gt; and nothing can parse YAML.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚠️ Easy to confuse — 406 vs 415:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;406&lt;/strong&gt; = "I can't &lt;em&gt;give&lt;/em&gt; you what you'll accept" (about &lt;code&gt;Accept&lt;/code&gt;, the way out).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;415&lt;/strong&gt; = "I can't &lt;em&gt;read&lt;/em&gt; what you sent me" (about &lt;code&gt;Content-Type&lt;/code&gt;, the way in).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also constrain endpoints explicitly with &lt;strong&gt;&lt;code&gt;produces&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;consumes&lt;/code&gt;&lt;/strong&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;@PostMapping&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;"/users"&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="c1"&gt;// only accept JSON bodies&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="c1"&gt;// only ever return JSON&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;create&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;User&lt;/span&gt; &lt;span class="n"&gt;u&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;ul&gt;
&lt;li&gt;
&lt;code&gt;consumes&lt;/code&gt; narrows what the endpoint will read → a mismatch gives &lt;strong&gt;415&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;produces&lt;/code&gt; narrows what the endpoint will return → a mismatch with &lt;code&gt;Accept&lt;/code&gt; gives &lt;strong&gt;406&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;These also help Spring route: two methods on the same path can differ only by &lt;code&gt;produces&lt;/code&gt;, one for JSON and one for XML.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 Jackson: the converter that does the JSON work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For JSON, the converter is &lt;code&gt;MappingJackson2HttpMessageConverter&lt;/code&gt;, and inside it sits a single &lt;strong&gt;&lt;code&gt;ObjectMapper&lt;/code&gt;&lt;/strong&gt; — Jackson's core engine that maps between objects and JSON.&lt;/li&gt;
&lt;li&gt;Spring Boot auto-configures this &lt;code&gt;ObjectMapper&lt;/code&gt; for you, so you rarely create one by hand. But you do need to steer it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The date trap
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;By default, plain Jackson serializes a &lt;code&gt;java.time&lt;/code&gt; date as a number (epoch or an array), which almost nobody wants:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;❌  "createdAt": 1726444800.000
✅  "createdAt": "2026-09-16T10:00:00"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The fix has two parts, and Boot handles both automatically:

&lt;ul&gt;
&lt;li&gt;Register the &lt;strong&gt;JavaTimeModule&lt;/strong&gt; so Jackson understands &lt;code&gt;LocalDate&lt;/code&gt;, &lt;code&gt;Instant&lt;/code&gt;, and friends.&lt;/li&gt;
&lt;li&gt;Turn off &lt;code&gt;WRITE_DATES_AS_TIMESTAMPS&lt;/code&gt; so they render as ISO-8601 strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If you build an &lt;code&gt;ObjectMapper&lt;/code&gt; yourself, you must add these — forgetting the module is the single most common Jackson bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steering serialization with annotations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You control the JSON shape field by field, right on the class:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JsonProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// rename the JSON key&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@JsonIgnore&lt;/span&gt;                  &lt;span class="c1"&gt;// never serialize this&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;passwordHash&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@JsonInclude&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonInclude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Include&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NON_NULL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// drop nulls from output&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nickname&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;ul&gt;
&lt;li&gt;
&lt;code&gt;@JsonProperty&lt;/code&gt; bridges a Java name and a different JSON name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@JsonIgnore&lt;/code&gt; keeps secrets out of the body.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@JsonInclude(NON_NULL)&lt;/code&gt; trims empty fields so responses stay small.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The unknown-property trap
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;By default Jackson &lt;strong&gt;fails&lt;/strong&gt; when the incoming JSON has a field your class does not declare.&lt;/li&gt;
&lt;li&gt;That is strict and often surprising when a client sends an extra field.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;❌  Strict (default): unknown field  →  deserialization throws  →  400
✅  Lenient: ignore unknown fields, bind the rest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Boot flips this to lenient for you by default (&lt;code&gt;FAIL_ON_UNKNOWN_PROPERTIES=false&lt;/code&gt;), but know the switch exists — it decides whether an extra field is a hard error or quietly ignored.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚙️ Customizing converters the right way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You will eventually need to change global behavior. There is a clean order of preference, from least to most invasive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1. Properties first.&lt;/strong&gt; Most tuning needs no code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.default-property-inclusion=non_null
spring.jackson.deserialization.fail-on-unknown-properties=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2. A builder-customizer bean&lt;/strong&gt; when properties are not enough. This adjusts Boot's &lt;code&gt;ObjectMapper&lt;/code&gt; without replacing it:
&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="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="nc"&gt;Jackson2ObjectMapperBuilderCustomizer&lt;/span&gt; &lt;span class="nf"&gt;json&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;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;simpleDateFormat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yyyy-MM-dd"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                             &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;modulesToInstall&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;MyModule&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;3.&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;configureMessageConverters&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;/&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;extendMessageConverters&lt;/code&gt;&lt;/strong&gt; when you need to touch the converter list itself:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extendMessageConverters&lt;/code&gt; — adjust the list Boot already built (the safe choice).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;configureMessageConverters&lt;/code&gt; — replace the whole list (you now own &lt;em&gt;every&lt;/em&gt; converter, including the defaults you just dropped).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;❌ Don't&lt;/strong&gt; create a bare &lt;code&gt;new ObjectMapper()&lt;/code&gt; bean just to tweak one setting — you throw away all of Boot's sensible defaults (the JavaTimeModule, the lenient reading) and reintroduce the date trap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✅ Do&lt;/strong&gt; start at properties, then a customizer, and only reach for the converter list when you are adding a genuinely new format.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Header it reads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message converter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Translates Java object ↔ body bytes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Content negotiation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Picks the response format&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@RequestBody&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads request body into an object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@ResponseBody&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writes returned object to the body&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;produces&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Restricts formats the endpoint returns&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Accept&lt;/code&gt; (mismatch → 406)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;consumes&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Restricts formats the endpoint reads&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Content-Type&lt;/code&gt; (mismatch → 415)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Jackson&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;ObjectMapper&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;The engine doing the JSON mapping&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Decision rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Getting a 406?&lt;/strong&gt; The client's &lt;code&gt;Accept&lt;/code&gt; asks for a format you can't produce. Add the converter (e.g. XML) or fix the client's &lt;code&gt;Accept&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting a 415?&lt;/strong&gt; The client's &lt;code&gt;Content-Type&lt;/code&gt; names a body you can't read. Fix the header or add a converter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dates look like numbers?&lt;/strong&gt; You're missing the JavaTimeModule or &lt;code&gt;write-dates-as-timestamps&lt;/code&gt; is still on — you likely built your own &lt;code&gt;ObjectMapper&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need to change JSON globally?&lt;/strong&gt; Try &lt;code&gt;spring.jackson.*&lt;/code&gt; → then a &lt;code&gt;Jackson2ObjectMapperBuilderCustomizer&lt;/code&gt; → then the converter list, in that order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need per-field control?&lt;/strong&gt; Use &lt;code&gt;@JsonProperty&lt;/code&gt;, &lt;code&gt;@JsonIgnore&lt;/code&gt;, &lt;code&gt;@JsonInclude&lt;/code&gt; on the class.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Remember this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your controller returns an object; a &lt;strong&gt;message converter&lt;/strong&gt; turns it into bytes, and &lt;strong&gt;content negotiation&lt;/strong&gt; picks the format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/strong&gt; drives the response (out); &lt;strong&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/strong&gt; drives the request body (in). Keep the two straight and 406 vs 415 becomes obvious.&lt;/li&gt;
&lt;li&gt;Jackson's &lt;code&gt;ObjectMapper&lt;/code&gt; is the JSON engine — let Boot configure it, and tune it through properties or a customizer, never by replacing it wholesale.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@JsonProperty&lt;/code&gt;, &lt;code&gt;@JsonIgnore&lt;/code&gt;, and &lt;code&gt;@JsonInclude&lt;/code&gt; shape the JSON per field; the JavaTimeModule keeps your dates readable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Request lifecycle: HandlerMapping HandlerAdapter resolvers</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Tue, 15 Sep 2026 22:08:02 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/request-lifecycle-handlermapping-handleradapter-resolvers-1f79</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/request-lifecycle-handlermapping-handleradapter-resolvers-1f79</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The big idea in one line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spring finds the handler, prepares its arguments, calls it, and handles the result.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why this exists&lt;/strong&gt; — an HTTP request contains paths, headers, and bytes. Your Java method needs typed arguments.&lt;/li&gt;
&lt;li&gt;Spring bridges that gap using the parameter and return types it supports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you meet it&lt;/strong&gt; — a route fails, a parameter cannot be converted, or you want to supply a custom method argument.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you already have&lt;/strong&gt; — one servlet catches every request, and &lt;code&gt;@GetMapping&lt;/code&gt; marks a method. This lesson is everything that happens in between.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🗺️ Three questions, asked in order
&lt;/h2&gt;

&lt;p&gt;The DispatcherServlet does not contain your logic and does not know your URLs. On each request it asks three questions and hands each one to a different object:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Which method should run?&lt;/strong&gt; — answered by an object whose only job is matching a request to a method. That object is a &lt;strong&gt;handler mapping&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I actually call that method?&lt;/strong&gt; — answered by an object that knows one calling style. That is a &lt;strong&gt;handler adapter&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What value goes in each parameter?&lt;/strong&gt; — answered by small single-purpose objects called &lt;strong&gt;argument resolvers&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;request
      |
      v
+------------------+
| DispatcherServlet|
+------------------+
      |
      |  1. "who owns /users/42?"
      +---------------------------&amp;gt; HandlerMapping  --&amp;gt; HandlerMethod
      |
      |  2. "who can call that?"
      +---------------------------&amp;gt; HandlerAdapter
      |
      |  3. adapter fills the args
      |        @PathVariable Long id  &amp;lt;-- ArgumentResolver
      |        @RequestBody  User u   &amp;lt;-- ArgumentResolver
      |
      v
   your method runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each answer is looked up, never hard-coded. That is what makes the whole thing extensible.&lt;/p&gt;




&lt;h2&gt;
  
  
  📇 The lookup table is built at startup
&lt;/h2&gt;

&lt;p&gt;Matching a URL by scanning every controller on every request would be slow. So Spring does the scanning &lt;strong&gt;once, at startup&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At boot, Spring walks every bean marked &lt;code&gt;@Controller&lt;/code&gt; or &lt;code&gt;@RestController&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For each annotated method it reads the mapping annotations and builds a &lt;strong&gt;RequestMappingInfo&lt;/strong&gt; — a small object holding everything that must match: path, HTTP method, &lt;code&gt;params&lt;/code&gt;, &lt;code&gt;headers&lt;/code&gt;, &lt;code&gt;consumes&lt;/code&gt;, &lt;code&gt;produces&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It pairs that with a &lt;strong&gt;HandlerMethod&lt;/strong&gt; — just the controller bean plus a &lt;code&gt;java.lang.reflect.Method&lt;/code&gt; handle. That pair &lt;em&gt;is&lt;/em&gt; "the handler".&lt;/li&gt;
&lt;li&gt;Both go into one map, the &lt;strong&gt;mapping registry&lt;/strong&gt;, held by &lt;code&gt;RequestMappingHandlerMapping&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes, at startup, roughly one registry entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;RequestMappingInfo{ GET /users/{id}, produces=application/json }
        -&amp;gt;  HandlerMethod{ userController, getUser(Long) }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lookup at request time is a map read plus a little matching — fast.&lt;/li&gt;
&lt;li&gt;Creating an object with &lt;code&gt;new&lt;/code&gt; does not register a new route.&lt;/li&gt;
&lt;li&gt;The registry is not frozen. Spring exposes &lt;code&gt;registerMapping()&lt;/code&gt; and &lt;code&gt;unregisterMapping()&lt;/code&gt; for deliberate runtime changes.&lt;/li&gt;
&lt;li&gt;Ordinary applications let Spring discover their routes at startup.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔍 Step 1 — the handler mapping picks the method
&lt;/h2&gt;

&lt;p&gt;The DispatcherServlet holds an &lt;strong&gt;ordered list&lt;/strong&gt; of handler mappings, not just one. It asks each in turn and takes the &lt;strong&gt;first non-null answer&lt;/strong&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="c1"&gt;// Conceptually, inside DispatcherServlet&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HandlerMapping&lt;/span&gt; &lt;span class="n"&gt;mapping&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;handlerMappings&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;HandlerExecutionChain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;chain&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// first one to claim it wins&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="c1"&gt;// nobody claimed it -&amp;gt; 404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the usual Spring MVC configuration, annotated controllers are checked before static resources by &lt;code&gt;RequestMappingHandlerMapping&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Other mappings and custom ordering can change this list.&lt;/li&gt;
&lt;li&gt;Static resources (&lt;code&gt;/style.css&lt;/code&gt;) are checked by a &lt;strong&gt;later&lt;/strong&gt; mapping in the list.&lt;/li&gt;
&lt;li&gt;That ordering matters — a controller mapped to a very broad path answers first, and the static file is never reached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What comes back is not the method alone. It is a &lt;strong&gt;HandlerExecutionChain&lt;/strong&gt; — the handler &lt;em&gt;plus&lt;/em&gt; the list of interceptors that should run around it. We'll use those at the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a match is actually decided
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A route must satisfy its path and mapping conditions.&lt;/li&gt;
&lt;li&gt;If no full match exists, Spring checks partial matches to explain common failures.&lt;/li&gt;
&lt;li&gt;These are typical default responses for mapping failures. Other processing stages can produce the same codes.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Checks&lt;/th&gt;
&lt;th&gt;If nothing matches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;URL against the patterns&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;404&lt;/strong&gt; — no handler at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP method&lt;/td&gt;
&lt;td&gt;GET vs POST vs …&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;405&lt;/strong&gt; Method Not Allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;consumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;request &lt;code&gt;Content-Type&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;415&lt;/strong&gt; Unsupported Media Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;produces&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;request &lt;code&gt;Accept&lt;/code&gt; header&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;406&lt;/strong&gt; Not Acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;params&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;required request-parameter conditions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;400&lt;/strong&gt; Bad Request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;A failed &lt;code&gt;headers&lt;/code&gt; mapping condition does not automatically mean 400. It can leave the request without a matching controller.&lt;/li&gt;
&lt;li&gt;A 404 can mean no route matched, a static file was missing, or application code returned 404.&lt;/li&gt;
&lt;li&gt;A 415 can come from &lt;code&gt;consumes&lt;/code&gt; matching or from a body converter later.&lt;/li&gt;
&lt;li&gt;Check the exception or logs along with the status code.&lt;/li&gt;
&lt;li&gt;If several mappings match, Spring compares specificity. An exact path usually beats a variable pattern.&lt;/li&gt;
&lt;li&gt;If the best matches remain equally specific, Spring reports an ambiguous match.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔌 Step 2 — the handler adapter, and why there is a middleman
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The mapping returns a handler typed as plain &lt;code&gt;Object&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It might represent an annotated method, a static-resource handler, or a function-based route.&lt;/li&gt;
&lt;li&gt;The DispatcherServlet needs a different calling strategy for each kind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ &lt;strong&gt;The obvious design that fails&lt;/strong&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="c1"&gt;// If DispatcherServlet called handlers directly:&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;handler&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;HandlerMethod&lt;/span&gt; &lt;span class="n"&gt;hm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ResourceHttpRequestHandler&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;HandlerFunction&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// every new handler style = editing DispatcherServlet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Every new kind of handler forces a change to the core class.&lt;/li&gt;
&lt;li&gt;Third parties could never add a handler style at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;What Spring does instead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;handler adapter&lt;/strong&gt; knows how to call one kind of handler. Its two main operations are shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;HandlerAdapter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;supports&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&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;ModelAndView&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The DispatcherServlet walks its adapter list and takes the first one whose &lt;code&gt;supports()&lt;/code&gt; returns true.&lt;/li&gt;
&lt;li&gt;For your &lt;code&gt;@GetMapping&lt;/code&gt; methods that is &lt;strong&gt;&lt;code&gt;RequestMappingHandlerAdapter&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The DispatcherServlet stays completely unaware of annotations, reflection, or JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The adapter returns a &lt;strong&gt;ModelAndView&lt;/strong&gt; — a small holder for the data plus the name of a page to render. It may also be &lt;code&gt;null&lt;/code&gt;. Hold that thought — it matters in step 4.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Step 3 — argument resolvers fill the parameters
&lt;/h2&gt;

&lt;p&gt;Now the adapter has to call &lt;code&gt;getUser(Long id)&lt;/code&gt;. It has an &lt;code&gt;HttpServletRequest&lt;/code&gt;; it needs a &lt;code&gt;Long&lt;/code&gt;. This is where the real work happens.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;argument resolver&lt;/strong&gt; supplies values for the parameter types or annotations it supports. Here is a simplified outline of its two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;HandlerMethodArgumentResolver&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;supportsParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;resolveArgument&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter loops over the method's parameters. For each one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Walk the resolver list and find the first whose &lt;code&gt;supportsParameter()&lt;/code&gt; says yes.&lt;/li&gt;
&lt;li&gt;Call its &lt;code&gt;resolveArgument()&lt;/code&gt; to produce the value.&lt;/li&gt;
&lt;li&gt;Put the value in the argument array.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What happens on later requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring caches which resolver supports each parameter. It does not repeat that search on every request.&lt;/li&gt;
&lt;li&gt;It still resolves fresh argument values for each request.&lt;/li&gt;
&lt;li&gt;Text values also need type conversion: &lt;code&gt;"42"&lt;/code&gt; becomes a &lt;code&gt;Long&lt;/code&gt;; &lt;code&gt;"abc"&lt;/code&gt; cannot.&lt;/li&gt;
&lt;li&gt;A missing required value or failed conversion normally produces 400 before your method runs.&lt;/li&gt;
&lt;li&gt;An unannotated simple parameter is normally treated as a request parameter.&lt;/li&gt;
&lt;li&gt;A complex parameter can instead be built from form or query fields. This is &lt;strong&gt;model-attribute binding&lt;/strong&gt;, also requested explicitly with &lt;code&gt;@ModelAttribute&lt;/code&gt;. It does not parse a JSON body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once every slot is filled, the adapter invokes the method by reflection. This signature:&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="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&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;UserDto&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                   &lt;span class="nd"&gt;@RequestHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Trace"&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;trace&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;is not one magic step. It is &lt;strong&gt;three independent resolvers&lt;/strong&gt;, each filling one slot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Resolver responsible&lt;/th&gt;
&lt;th&gt;Where the value comes from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@PathVariable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PathVariableMethodArgumentResolver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL template variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@RequestParam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RequestParamMethodArgumentResolver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;query string or form fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@RequestBody&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RequestResponseBodyMethodProcessor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;request body, via message converters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@RequestHeader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RequestHeaderMethodArgumentResolver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a named header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;HttpServletRequest&lt;/code&gt;, &lt;code&gt;Principal&lt;/code&gt;, &lt;code&gt;Locale&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ServletRequestMethodArgumentResolver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the request itself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That &lt;code&gt;@RequestBody&lt;/code&gt; row is a boundary worth naming. The resolver does not parse JSON itself — it hands the body stream to a &lt;strong&gt;message converter&lt;/strong&gt;, which is the thing that knows JSON. The next lesson takes converters apart; here it is enough that the resolver delegates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your own resolver
&lt;/h3&gt;

&lt;p&gt;Because the list is just a list, you can extend it. Say every request carries a tenant header and you are tired of reading it by hand.&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;TenantResolver&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HandlerMethodArgumentResolver&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;supportsParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;p&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;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasParameterAnnotation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CurrentTenant&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;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;resolveArgument&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModelAndViewContainer&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                  &lt;span class="nc"&gt;NativeWebRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebDataBinderFactory&lt;/span&gt; &lt;span class="n"&gt;b&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="nf"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Tenant-Id"&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;ul&gt;
&lt;li&gt;
&lt;code&gt;CurrentTenant&lt;/code&gt; is a custom parameter annotation retained at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tenant&lt;/code&gt; is your value type with a constructor accepting the ID.&lt;/li&gt;
&lt;li&gt;Register the resolver through &lt;strong&gt;WebMvcConfigurer&lt;/strong&gt;, Spring MVC's configuration extension interface:
&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="nd"&gt;@Configuration&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;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&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;addArgumentResolvers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HandlerMethodArgumentResolver&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resolvers&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;resolvers&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TenantResolver&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;Your controller can then ask for that value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders"&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@CurrentTenant&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt; &lt;span class="n"&gt;tenant&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;ul&gt;
&lt;li&gt;
&lt;code&gt;supportsParameter&lt;/code&gt; is the filter: only parameters marked &lt;code&gt;@CurrentTenant&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolveArgument&lt;/code&gt; is the factory: build the value from the request.&lt;/li&gt;
&lt;li&gt;Controllers no longer repeat the header lookup.&lt;/li&gt;
&lt;li&gt;This small example omits missing-header checks. Reject a missing or invalid ID before using it.&lt;/li&gt;
&lt;li&gt;A client-supplied tenant ID is input, not proof of access. Verify access against the authenticated user.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📤 Step 4 — return value handlers deal with what comes back
&lt;/h2&gt;

&lt;p&gt;Arguments have a mirror image. A &lt;strong&gt;return value handler&lt;/strong&gt; decides what to do with whatever your method returned. Same two-method shape: does it support this return type, and how should it be handled.&lt;/p&gt;

&lt;p&gt;The common return-value paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Return type&lt;/th&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@ResponseBody&lt;/code&gt; value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RequestResponseBodyMethodProcessor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;serialized straight into the response body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseEntity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HttpEntityMethodProcessor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;body plus status and headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plain &lt;code&gt;String&lt;/code&gt; on &lt;code&gt;@Controller&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ViewNameMethodReturnValueHandler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;treated as a view name to render&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is the mechanism that connects this back to the DispatcherServlet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a return-value handler handles the response body, it marks the request as handled.&lt;/li&gt;
&lt;li&gt;That flag means no view is needed. It does not promise that network delivery has finished.&lt;/li&gt;
&lt;li&gt;The adapter then returns &lt;strong&gt;&lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; instead of a &lt;code&gt;ModelAndView&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The DispatcherServlet sees &lt;code&gt;null&lt;/code&gt; and &lt;strong&gt;skips view resolution entirely&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@RestController&lt;/code&gt; includes &lt;code&gt;@ResponseBody&lt;/code&gt; behavior for its methods.&lt;/li&gt;
&lt;li&gt;A method in a plain &lt;code&gt;@Controller&lt;/code&gt; can also use &lt;code&gt;@ResponseBody&lt;/code&gt;, or return &lt;code&gt;ResponseEntity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both controller styles use the same pipeline. Annotations and return types determine how the result is handled.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Interceptors wrap the whole thing
&lt;/h2&gt;

&lt;p&gt;Remember the chain carried interceptors alongside the handler. They run at three fixed points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;preHandle       -&amp;gt; before the adapter is called
   [ handler runs, response may be written ]
postHandle      -&amp;gt; after the handler, before any view is rendered
   [ view rendering, if any ]
afterCompletion -&amp;gt; cleanup for interceptors whose preHandle returned true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;preHandle&lt;/code&gt; returning &lt;code&gt;false&lt;/code&gt; stops the request there — the handler never runs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postHandle&lt;/code&gt; is &lt;strong&gt;skipped&lt;/strong&gt; if the handler threw an exception.&lt;/li&gt;
&lt;li&gt;Cleanup runs only for interceptors whose &lt;code&gt;preHandle&lt;/code&gt; completed and returned &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If your own &lt;code&gt;preHandle&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; or throws, release anything it acquired there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preHandle&lt;/code&gt; runs in list order. &lt;code&gt;postHandle&lt;/code&gt; and &lt;code&gt;afterCompletion&lt;/code&gt; run in reverse order.&lt;/li&gt;
&lt;li&gt;This diagram shows a synchronous request. Async processing defers completion and may dispatch again.&lt;/li&gt;
&lt;li&gt;If processing throws, the DispatcherServlet asks &lt;strong&gt;exception resolvers&lt;/strong&gt; to handle the failure.&lt;/li&gt;
&lt;li&gt;A resolver may produce an error response or view. An unresolved exception propagates to the servlet container.&lt;/li&gt;
&lt;li&gt;Exception handling gets its own lesson later in this module.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Easy to confuse
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handler mapping vs handler adapter&lt;/strong&gt; — the mapping answers &lt;em&gt;which&lt;/em&gt; method; the adapter answers &lt;em&gt;how to call it&lt;/em&gt;. A mapping failure means no method was chosen at all. By the time an adapter is involved, your method has already been picked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argument resolver vs message converter&lt;/strong&gt; — the resolver decides &lt;em&gt;which part of the request&lt;/em&gt; a parameter comes from. The converter turns &lt;em&gt;bytes into an object&lt;/em&gt;. Only body-related parameters involve a converter at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HandlerMethod vs handler&lt;/strong&gt; — "handler" is the general word for whatever answers a request. &lt;code&gt;HandlerMethod&lt;/code&gt; is the specific kind that wraps one annotated controller method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;404 vs 405&lt;/strong&gt; — a default routing 404 means no matching handler was found. A mapping-stage 405 means a path matched but the HTTP method did not. Application code can also return these statuses.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🕳️ Traps this design creates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not consume the body before Spring reads it.&lt;/strong&gt; A filter that drains the stream can leave &lt;code&gt;@RequestBody&lt;/code&gt; with no bytes.&lt;/li&gt;
&lt;li&gt;For logging, &lt;code&gt;ContentCachingRequestWrapper&lt;/code&gt; records bytes as downstream code reads them. Read its cache after the filter chain returns.&lt;/li&gt;
&lt;li&gt;That wrapper does not automatically make the input stream replayable. Reading the body first requires a wrapper that actually supplies a fresh stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing the model in postHandle will not change a JSON response.&lt;/strong&gt; Body handling already ran inside the adapter.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;ResponseBodyAdvice&lt;/strong&gt;, a hook that can adjust the body before a message converter writes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first supporting resolver wins.&lt;/strong&gt; Resolvers added through &lt;code&gt;addArgumentResolvers()&lt;/code&gt; sit after many standard resolvers, but before fallback resolvers.&lt;/li&gt;
&lt;li&gt;An explicit &lt;code&gt;@RequestParam&lt;/code&gt; is normally claimed by its standard resolver first. Use a dedicated annotation for a custom value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A broad controller path can claim static-file requests.&lt;/strong&gt; In the usual ordering, a root mapping like &lt;code&gt;/{path}&lt;/code&gt; can match &lt;code&gt;/favicon.ico&lt;/code&gt; before the resource handler sees it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating a controller object does not register its routes.&lt;/strong&gt; Dynamic registration is possible through the mapping registry's API. It must be deliberate.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;Produces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Startup&lt;/td&gt;
&lt;td&gt;mapping registry&lt;/td&gt;
&lt;td&gt;what routes exist?&lt;/td&gt;
&lt;td&gt;RequestMappingInfo → HandlerMethod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;HandlerMapping&lt;/td&gt;
&lt;td&gt;which method owns this request?&lt;/td&gt;
&lt;td&gt;HandlerExecutionChain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;HandlerAdapter&lt;/td&gt;
&lt;td&gt;how do I call this handler?&lt;/td&gt;
&lt;td&gt;calls the handler and returns ModelAndView or null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Argument resolvers&lt;/td&gt;
&lt;td&gt;what goes in each parameter?&lt;/td&gt;
&lt;td&gt;the argument array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Return value handlers&lt;/td&gt;
&lt;td&gt;what do I do with the result?&lt;/td&gt;
&lt;td&gt;a written body, or a view name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Decision rule
&lt;/h2&gt;

&lt;p&gt;Use the code as a clue, then check the exception and logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;404&lt;/strong&gt; → check mappings, class-level prefixes, and static-resource lookup. Also check whether application code returned it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;405&lt;/strong&gt; → check the request method against the mapped methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;415&lt;/strong&gt; → check &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;consumes&lt;/code&gt;, and whether a converter can read the body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;406&lt;/strong&gt; → check &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;produces&lt;/code&gt;, and whether a converter can write the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;400 with a missing-parameter message&lt;/strong&gt; → step 3, a resolver could not find a required value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameter is null but the request looks right&lt;/strong&gt; → step 3, check that the resolver you expect is actually the first one claiming that parameter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To inject a value of your own, add an argument resolver with your own annotation — never fight the built-in ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Remember this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The DispatcherServlet orchestrates and delegates. It asks &lt;strong&gt;which method&lt;/strong&gt;, &lt;strong&gt;how to call it&lt;/strong&gt;, and &lt;strong&gt;what to pass&lt;/strong&gt; — three questions, three replaceable answers.&lt;/li&gt;
&lt;li&gt;Spring discovers ordinary routes at startup. Creating an object later does not add a route, but explicit runtime registration is possible.&lt;/li&gt;
&lt;li&gt;Status codes help locate a failure. They do not uniquely identify its stage; read the exception too.&lt;/li&gt;
&lt;li&gt;A method signature is filled &lt;strong&gt;one parameter at a time&lt;/strong&gt; by independent resolvers, and the return value is handled by their mirror image. Both lists are open — that is the extension point.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>@Controller vs @RestController, request mapping</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:32:20 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/controller-vs-restcontroller-request-mapping-fl</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/controller-vs-restcontroller-request-mapping-fl</guid>
      <description>&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;When a browser or another service sends an HTTP request to your Spring application, something has to catch that request, run your code, and send a reply. In Spring, the class that does the catching is called a &lt;strong&gt;controller&lt;/strong&gt;. It is the front door of a web application — the layer where "a GET request just arrived for &lt;code&gt;/users/42&lt;/code&gt;" turns into "run this Java method and return this result."&lt;/p&gt;

&lt;p&gt;You meet controllers the moment you build anything that answers HTTP: a JSON API, a server-rendered web page, a webhook receiver. This article is about the two annotations that declare one — &lt;code&gt;@Controller&lt;/code&gt; and &lt;code&gt;@RestController&lt;/code&gt; — and about how Spring decides which method handles which request. That second part is called &lt;strong&gt;request mapping&lt;/strong&gt;, and it is where most of the everyday work happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  A controller is just a managed object
&lt;/h2&gt;

&lt;p&gt;One idea has to be in place before the rest makes sense. Spring runs a &lt;strong&gt;container&lt;/strong&gt;: an object created at startup that builds your objects, wires them together, and manages how long they live. Any object the container owns is called a &lt;strong&gt;bean&lt;/strong&gt;. You almost never write &lt;code&gt;new&lt;/code&gt; for a controller — you describe the class, and the container builds one instance and holds onto it.&lt;/p&gt;

&lt;p&gt;To tell the container "this class is one of yours," you put an annotation on it. At startup Spring scans your packages, finds those annotations, and registers a bean for each match. &lt;code&gt;@Controller&lt;/code&gt; is one of those annotations.&lt;/p&gt;

&lt;p&gt;So the first thing &lt;code&gt;@Controller&lt;/code&gt; does is ordinary: it marks the class as a bean for the container to create. But it does a second thing, and that second thing is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What @Controller actually signals
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Controller&lt;/code&gt; is a &lt;strong&gt;stereotype&lt;/strong&gt; — a specialized "this is a bean" marker that also tells Spring what &lt;em&gt;kind&lt;/em&gt; of bean it is. It says: this bean handles web requests, so inspect its methods for request mappings.&lt;/p&gt;

&lt;p&gt;Here is the smallest useful one.&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;@Controller&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;PageController&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="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hello&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;"welcome"&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 &lt;code&gt;@GetMapping("/hello")&lt;/code&gt; line says this method handles GET requests for the path &lt;code&gt;/hello&lt;/code&gt;. The method runs and returns the string &lt;code&gt;"welcome"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the part that surprises everyone the first time. With a plain &lt;code&gt;@Controller&lt;/code&gt;, that returned string is &lt;strong&gt;not&lt;/strong&gt; the response body. It is a &lt;strong&gt;view name&lt;/strong&gt; — the name of a template file (a &lt;code&gt;welcome.html&lt;/code&gt;, say) that Spring should find, render, and send back as a full HTML page.&lt;/p&gt;

&lt;p&gt;That default exists for a reason. &lt;code&gt;@Controller&lt;/code&gt; was born for server-side web pages, back when a request usually meant "give me a rendered page." So its baked-in assumption is: the String you return names a page to render, not the text to send.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling Spring "that's the body, not a view name"
&lt;/h2&gt;

&lt;p&gt;Often you do not want a rendered page. You want to send data straight back — plain text, or JSON. To flip the meaning of the return value, you add &lt;code&gt;@ResponseBody&lt;/code&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;@Controller&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;ApiController&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="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@ResponseBody&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hello&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;"welcome"&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;With &lt;code&gt;@ResponseBody&lt;/code&gt; present, the return value is written &lt;strong&gt;directly into the HTTP response body&lt;/strong&gt;. The client now receives the literal text &lt;code&gt;welcome&lt;/code&gt;, with no template lookup at all.&lt;/p&gt;

&lt;p&gt;The same rule covers objects, and this is where it earns its keep. Return a &lt;code&gt;User&lt;/code&gt; object from a &lt;code&gt;@ResponseBody&lt;/code&gt; method and Spring serializes it — to JSON by default — and writes that JSON to the response. This is how a Java method becomes a REST endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/42"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ResponseBody&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&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="nf"&gt;User&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// client receives:  {"id":42,"name":"Ada"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the whole difference between "web page" and "API" is that one annotation. But repeating &lt;code&gt;@ResponseBody&lt;/code&gt; on every method in an API class gets tedious fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  @RestController: the shortcut you almost always want
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@RestController&lt;/code&gt; is that repetition, removed.&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;ApiController&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="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hello&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;"welcome"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// sent as the body, not a view 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;&lt;code&gt;@RestController&lt;/code&gt; is a &lt;strong&gt;meta-annotation&lt;/strong&gt;: an annotation built out of other annotations. It bundles &lt;code&gt;@Controller&lt;/code&gt; and &lt;code&gt;@ResponseBody&lt;/code&gt; together, and the &lt;code&gt;@ResponseBody&lt;/code&gt; applies to &lt;em&gt;every&lt;/em&gt; method in the class. Nothing more, nothing less.&lt;/p&gt;

&lt;p&gt;That gives a clean rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@RestController&lt;/code&gt;&lt;/strong&gt; — for APIs that return data (JSON, XML, plain text).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Controller&lt;/code&gt;&lt;/strong&gt; — for endpoints that render server-side pages, or a class where some methods render pages and only some return data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it explains a classic bug. Someone builds a JSON API with &lt;code&gt;@Controller&lt;/code&gt;, forgets &lt;code&gt;@ResponseBody&lt;/code&gt;, and every endpoint returns a String. Spring dutifully treats each return value as a view name and tries to find a template called &lt;code&gt;welcome&lt;/code&gt; — so instead of JSON, they get a confusing 500 error about a missing view. The fix is almost always "switch to &lt;code&gt;@RestController&lt;/code&gt;."&lt;/p&gt;

&lt;h2&gt;
  
  
  Request mapping: matching a request to a method
&lt;/h2&gt;

&lt;p&gt;Declaring a controller is half the job. The other half is telling Spring &lt;em&gt;which&lt;/em&gt; requests each method should handle. That wiring is &lt;strong&gt;request mapping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The general-purpose annotation is &lt;code&gt;@RequestMapping&lt;/code&gt;, which takes the path and the HTTP method explicitly:&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;"/hello"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because "map this path to this one HTTP verb" is so common, Spring ships a shortcut for each verb. These two lines mean exactly the same thing:&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;"/hello"&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="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full set of shortcuts mirrors the HTTP verbs you already know: &lt;code&gt;@GetMapping&lt;/code&gt;, &lt;code&gt;@PostMapping&lt;/code&gt;, &lt;code&gt;@PutMapping&lt;/code&gt;, &lt;code&gt;@PatchMapping&lt;/code&gt;, &lt;code&gt;@DeleteMapping&lt;/code&gt;. Reach for these by default; fall back to the long &lt;code&gt;@RequestMapping&lt;/code&gt; form only when you need something they do not express.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing paths, and reading the request
&lt;/h2&gt;

&lt;p&gt;You can put &lt;code&gt;@RequestMapping&lt;/code&gt; on the &lt;strong&gt;class&lt;/strong&gt; too, to set a common prefix. Spring joins the class-level path and the method-level path together.&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="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&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="s"&gt;"/{id}"&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;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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 class prefix &lt;code&gt;/users&lt;/code&gt; plus the method path &lt;code&gt;/{id}&lt;/code&gt; gives the full mapping &lt;code&gt;/users/{id}&lt;/code&gt;. The second method has no method-level path, so it maps to &lt;code&gt;/users&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;Two small helpers appear here, and both pull pieces of the request into method parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@PathVariable&lt;/code&gt;&lt;/strong&gt; grabs a value out of the &lt;em&gt;path&lt;/em&gt;. In &lt;code&gt;/users/{id}&lt;/code&gt;, a request to &lt;code&gt;/users/42&lt;/code&gt; binds &lt;code&gt;id = 42&lt;/code&gt;. The &lt;code&gt;{id}&lt;/code&gt; in the mapping and the parameter name line up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@RequestParam&lt;/code&gt;&lt;/strong&gt; grabs a value from the &lt;em&gt;query string&lt;/em&gt;. For &lt;code&gt;/users?page=2&lt;/code&gt;, it binds &lt;code&gt;page = 2&lt;/code&gt;; the &lt;code&gt;defaultValue&lt;/code&gt; supplies &lt;code&gt;0&lt;/code&gt; when the parameter is absent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers the normal case end to end: mark the class, map the path and verb, pull what you need out of the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrowing a mapping further
&lt;/h2&gt;

&lt;p&gt;Path and verb are the usual filters, but &lt;code&gt;@RequestMapping&lt;/code&gt; (and the shortcuts) can narrow a match on more than that. The common two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;consumes&lt;/code&gt;&lt;/strong&gt; — only match if the request's &lt;code&gt;Content-Type&lt;/code&gt; fits, e.g. &lt;code&gt;consumes = "application/json"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;produces&lt;/code&gt;&lt;/strong&gt; — only match if the client will accept what you emit, e.g. &lt;code&gt;produces = "application/json"&lt;/code&gt;.
&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="nd"&gt;@PostMapping&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;"/users"&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;"application/json"&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;User&lt;/span&gt; &lt;span class="nf"&gt;create&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;User&lt;/span&gt; &lt;span class="n"&gt;user&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&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 &lt;code&gt;@RequestBody&lt;/code&gt; is the mirror image of &lt;code&gt;@ResponseBody&lt;/code&gt;: it takes the incoming JSON body and deserializes it into a &lt;code&gt;User&lt;/code&gt; for you. And &lt;code&gt;consumes&lt;/code&gt; means this method is only chosen when the caller actually sent JSON — a request with a different content type is turned away before your code runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  When two mappings could both match
&lt;/h2&gt;

&lt;p&gt;Because you can map broadly (a &lt;code&gt;/{id}&lt;/code&gt; pattern) and narrowly (an exact &lt;code&gt;/me&lt;/code&gt; path) in the same class, two methods can both look like valid matches for one request. Spring resolves this by &lt;strong&gt;specificity&lt;/strong&gt;: a more exact mapping wins over a more general one. An exact path like &lt;code&gt;/users/me&lt;/code&gt; beats the variable pattern &lt;code&gt;/users/{id}&lt;/code&gt;, even though &lt;code&gt;me&lt;/code&gt; would also satisfy &lt;code&gt;{id}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If two mappings are genuinely equally specific — a real tie Spring cannot break — it does not guess. You get an exception at request time complaining that the mapping is ambiguous. That is a signal to make one of the two more specific, not something to work around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A controller is a bean that handles HTTP. &lt;code&gt;@Controller&lt;/code&gt; marks it, and by default treats a returned String as a page to render. &lt;code&gt;@ResponseBody&lt;/code&gt; flips that method to write its return value straight into the response, serializing objects to JSON on the way out. &lt;code&gt;@RestController&lt;/code&gt; is simply &lt;code&gt;@Controller&lt;/code&gt; with &lt;code&gt;@ResponseBody&lt;/code&gt; on every method — the right default for a data API.&lt;/p&gt;

&lt;p&gt;Request mapping is the matching layer: &lt;code&gt;@RequestMapping&lt;/code&gt; and its per-verb shortcuts bind a path and an HTTP method to a method, class-level and method-level paths compose, and &lt;code&gt;@PathVariable&lt;/code&gt; and &lt;code&gt;@RequestParam&lt;/code&gt; hand you the pieces of the request you asked for. When several mappings could match, the most specific one wins. Get these two ideas — declare the controller, map the request — and the rest of Spring MVC is detail layered on top.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Servlet model &amp; DispatcherServlet flow</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Mon, 14 Sep 2026 10:03:04 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/servlet-model-dispatcherservlet-flow-3in2</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/servlet-model-dispatcherservlet-flow-3in2</guid>
      <description>&lt;h2&gt;
  
  
  Every Spring web request goes through one door
&lt;/h2&gt;

&lt;p&gt;When you build a web app with Spring, you write small methods that handle URLs — one for &lt;code&gt;/users&lt;/code&gt;, another for &lt;code&gt;/orders&lt;/code&gt;, and so on. It feels like each method is wired straight to the network. It isn't.&lt;/p&gt;

&lt;p&gt;Behind the scenes, &lt;strong&gt;every&lt;/strong&gt; incoming request for your app is first handed to a &lt;em&gt;single&lt;/em&gt; Java object. That object reads the request, works out which of your methods should answer it, calls that method, and turns whatever it returns into a response. That one object is the &lt;strong&gt;DispatcherServlet&lt;/strong&gt;, and it sits at the center of everything Spring MVC does.&lt;/p&gt;

&lt;p&gt;To see why it exists — and what it actually does on each request — we have to start one level below Spring, with the plain Java technology it is built on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Servlet: Java's original web building block
&lt;/h2&gt;

&lt;p&gt;Long before Spring, Java already had a standard way to answer web requests: the &lt;strong&gt;Servlet&lt;/strong&gt;. A servlet is just a Java class that receives an HTTP request and writes an HTTP response. That is the whole idea.&lt;/p&gt;

&lt;p&gt;Here is the shape of one:&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;HelloServlet&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HttpServlet&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doGet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;resp&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="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContentType&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="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWriter&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="s"&gt;"Hello, world"&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;&lt;code&gt;doGet&lt;/code&gt; runs when a GET request arrives. The &lt;code&gt;req&lt;/code&gt; object hands you everything about the incoming request — the path, the headers, the query parameters. The &lt;code&gt;resp&lt;/code&gt; object is where you write what goes back. That is genuinely all a servlet is.&lt;/p&gt;

&lt;p&gt;But a servlet cannot run on its own. Something has to listen on the network port, accept the raw bytes of a connection, parse them into that tidy &lt;code&gt;HttpServletRequest&lt;/code&gt;, and then decide which servlet to hand it to. That something is the &lt;strong&gt;servlet container&lt;/strong&gt; — a program (Tomcat is the usual one) that owns the HTTP socket, manages a pool of threads, and controls the lifecycle of your servlets. You write servlets; the container runs them and feeds them requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the container knows which servlet to call
&lt;/h2&gt;

&lt;p&gt;A container can hold many servlets, so it needs a rule for matching a URL to one of them. That rule is a &lt;strong&gt;servlet mapping&lt;/strong&gt; — a pattern that says "requests to this path go to that servlet."&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;@WebServlet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&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;class&lt;/span&gt; &lt;span class="nc"&gt;HelloServlet&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HttpServlet&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&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 the mapping is &lt;code&gt;/hello&lt;/code&gt;. A request to &lt;code&gt;/hello&lt;/code&gt; lands in this servlet's &lt;code&gt;doGet&lt;/code&gt;; a request to &lt;code&gt;/goodbye&lt;/code&gt; does not. The container keeps a little table of these patterns and consults it for every request.&lt;/p&gt;

&lt;p&gt;This works, and for one or two endpoints it is fine. The trouble starts when the app grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why raw servlets stop scaling
&lt;/h2&gt;

&lt;p&gt;Picture a real application: users, orders, invoices, search, each with several operations. With plain servlets, every distinct piece of behavior tends to become its own servlet with its own mapping. You accumulate dozens of them.&lt;/p&gt;

&lt;p&gt;Worse, each one repeats the same chores. Read the request body and parse the JSON. Validate the fields. Catch exceptions and turn them into a sensible error response. Set the right content type. None of that is your actual business logic, yet it gets copied into servlet after servlet.&lt;/p&gt;

&lt;p&gt;And the routing is rigid. The mapping table lives in the container's configuration, wired up before your logic ever runs. There is no single place where a request first lands — no natural spot to add a behavior that should apply to &lt;em&gt;every&lt;/em&gt; request, like logging or a security check.&lt;/p&gt;

&lt;p&gt;What you really want is one servlet that catches everything, does the shared chores once, and then routes each request to the right small piece of your code. That idea has a name.&lt;/p&gt;

&lt;h2&gt;
  
  
  The front controller idea
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;front controller&lt;/strong&gt; is a single entry point that receives every request for an application, performs the common work in one place, and then delegates to whichever component actually handles that specific request.&lt;/p&gt;

&lt;p&gt;Instead of the container knowing about your &lt;code&gt;/users&lt;/code&gt; and &lt;code&gt;/orders&lt;/code&gt; logic directly, it knows about exactly one servlet. That servlet becomes the front door. Everything comes through it, and it decides where each request goes next.&lt;/p&gt;

&lt;p&gt;This is not a Spring invention — it is a classic pattern. But Spring MVC's entire design rests on it, and Spring ships a ready-made front controller so you never write one yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  DispatcherServlet: Spring's front controller
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;DispatcherServlet&lt;/strong&gt; is that front controller, implemented as an ordinary servlet. When you use Spring MVC, Spring registers this one servlet with the container and maps it to a broad pattern — typically &lt;code&gt;/&lt;/code&gt;, meaning "send me everything."&lt;/p&gt;

&lt;p&gt;You do not write it or wire it up by hand. In a Spring Boot app it is registered automatically at startup. The mapping looks, conceptually, 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="c1"&gt;// You never write this — Spring Boot does it for you at startup.&lt;/span&gt;
&lt;span class="c1"&gt;// One servlet, mapped to "/", catches every request into the app.&lt;/span&gt;
&lt;span class="n"&gt;registration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addServlet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dispatcherServlet"&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;DispatcherServlet&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="n"&gt;registration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addMapping&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important shift: the servlet container now has essentially &lt;em&gt;one&lt;/em&gt; servlet to think about. Every request funnels into the DispatcherServlet, and from there Spring — not the container — decides what happens.&lt;/p&gt;

&lt;p&gt;But if there is only one servlet, how does a request to &lt;code&gt;/users&lt;/code&gt; reach a different method than a request to &lt;code&gt;/orders&lt;/code&gt;? That routing has moved inside Spring, and it needs a few helpers to do the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pieces the DispatcherServlet delegates to
&lt;/h2&gt;

&lt;p&gt;The DispatcherServlet does not contain your endpoint logic, and it does not hard-code the routing rules either. It orchestrates. To do that, it leans on a small set of collaborators, each with one responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;handler&lt;/strong&gt; is the specific piece of your code that answers one kind of request — in practice, one of your controller methods. "Handler" is just the general word for "the thing that handles this request."&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;handler mapping&lt;/strong&gt; answers the question &lt;em&gt;"given this request, which handler should run?"&lt;/em&gt; It holds the routing knowledge that used to live in the container's config.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;handler adapter&lt;/strong&gt; knows &lt;em&gt;how to actually invoke&lt;/em&gt; a given handler — how to call it and collect its result. Different styles of handler need different adapters, so this indirection keeps the DispatcherServlet from caring about the details.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;view resolver&lt;/strong&gt; comes in when a handler returns the name of a page to render rather than raw data; it turns that name into something that can produce the final HTML.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a Spring &lt;strong&gt;bean&lt;/strong&gt; — an object Spring creates and manages inside its application context — so the DispatcherServlet simply asks the context for them. Hold these four names loosely for now; the next lessons take the mapping and adapter apart in detail. Here we just need to watch them cooperate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking one request through the DispatcherServlet
&lt;/h2&gt;

&lt;p&gt;Now we can trace what happens the moment a request arrives. Say a browser sends &lt;code&gt;GET /users/42&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, the container hands it over.&lt;/strong&gt; Tomcat accepts the connection, parses the bytes into an &lt;code&gt;HttpServletRequest&lt;/code&gt;, sees that &lt;code&gt;/users/42&lt;/code&gt; matches the &lt;code&gt;/&lt;/code&gt; mapping, and calls the DispatcherServlet — exactly like calling &lt;code&gt;doGet&lt;/code&gt; on any servlet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, the DispatcherServlet asks: who handles this?&lt;/strong&gt; It walks through its handler mappings, passing them the request, until one reports a match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptually, inside the DispatcherServlet:&lt;/span&gt;
&lt;span class="nc"&gt;HandlerExecutionChain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HandlerMapping&lt;/span&gt; &lt;span class="n"&gt;mapping&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;handlerMappings&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// "do you own /users/42?"&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;chain&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// first match wins&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is the handler for &lt;code&gt;/users/42&lt;/code&gt; — your controller method — bundled with any interceptors that should run around it. If no mapping claims the request, this is the exact point where you get the familiar 404.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, it finds the right way to call that handler.&lt;/strong&gt; The DispatcherServlet does not call your method directly. It looks for a handler adapter that understands this handler type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pick the adapter that knows how to invoke this particular handler.&lt;/span&gt;
&lt;span class="nc"&gt;HandlerAdapter&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getHandlerAdapter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHandler&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fourth, the adapter invokes your code.&lt;/strong&gt; The adapter reads the request, prepares whatever your method needs as arguments, calls it, and captures what it returns. Your controller method finally runs here — this is the one moment your own logic executes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fifth, the DispatcherServlet handles the result.&lt;/strong&gt; What comes back is normalized into a single result object the DispatcherServlet knows how to process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ModelAndView&lt;/span&gt; &lt;span class="n"&gt;mv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHandler&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your handler produced data to send straight back (the typical REST case), the response has effectively already been written and the DispatcherServlet is nearly done. If instead it returned the &lt;em&gt;name&lt;/em&gt; of a page, the DispatcherServlet calls a view resolver to turn that name into a renderable view and asks it to write the HTML into the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, the response goes back down.&lt;/strong&gt; The finished &lt;code&gt;HttpServletResponse&lt;/code&gt; returns to the container, which serializes it onto the socket and back to the browser. The DispatcherServlet's work for this request is over — until the next one arrives and the whole cycle repeats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this shape is worth the indirection
&lt;/h2&gt;

&lt;p&gt;Notice what the single front door buys you. Because &lt;em&gt;every&lt;/em&gt; request passes through the DispatcherServlet before reaching any handler, there is now one obvious place to attach behavior that should apply to all of them — request logging, timing, security checks. You add it once, not in fifty servlets.&lt;/p&gt;

&lt;p&gt;And because the DispatcherServlet only &lt;em&gt;orchestrates&lt;/em&gt; — delegating the "which handler" question to mappings and the "how to call it" question to adapters — your controller code stays clean. You write a method that takes an id and returns a user. You never touch the socket, the thread, the parsing, or the routing table. All of that lives in machinery you did not have to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of traps the design creates
&lt;/h2&gt;

&lt;p&gt;The single-servlet setup has sharp edges worth knowing.&lt;/p&gt;

&lt;p&gt;The first is a mapping surprise. Because the DispatcherServlet is mapped to &lt;code&gt;/&lt;/code&gt;, it wants to handle &lt;em&gt;everything&lt;/em&gt;, including requests for static files like &lt;code&gt;/style.css&lt;/code&gt;. Spring has sensible defaults for serving static resources, but when a URL unexpectedly returns a 404 or the wrong content, the cause is often a collision between your handler mappings and static-resource handling — both living under that one broad mapping.&lt;/p&gt;

&lt;p&gt;The second is about threads. The container runs each request on a thread from its pool, and the same DispatcherServlet instance serves them all at once. That is fine, because the DispatcherServlet holds no per-request state. But it means anything you hang onto across a request — a field on a shared bean, a &lt;code&gt;ThreadLocal&lt;/code&gt; you forget to clear — is shared across concurrent requests too. The front controller is a single object handling many requests in parallel, and code downstream of it has to respect that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves us
&lt;/h2&gt;

&lt;p&gt;So the picture is this: the servlet container owns the network and hands each request to one servlet. That servlet is the DispatcherServlet — Spring's front controller — and it does not answer requests itself. It asks a handler mapping &lt;em&gt;which&lt;/em&gt; piece of your code should run, asks a handler adapter &lt;em&gt;how&lt;/em&gt; to run it, lets your controller method do the real work, and then turns the result into a response.&lt;/p&gt;

&lt;p&gt;We deliberately kept the mapping and the adapter at arm's length here, treating them as helpers that just "know" the answer. That is exactly where the next step goes: how a handler mapping actually decides that &lt;code&gt;/users/42&lt;/code&gt; belongs to your method, and how the adapter builds the arguments to call it — the request lifecycle, one stage at a time.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Recap — M2 Boot</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sun, 13 Sep 2026 09:40:22 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/recap-m2-boot-hga</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/recap-m2-boot-hga</guid>
      <description>&lt;h2&gt;
  
  
  What this module was really about
&lt;/h2&gt;

&lt;p&gt;Plain Spring hands you a powerful toolbox and then asks you to assemble the workshop yourself. You pick the libraries, wire the objects together, point them at a database, choose a web server, and write the glue that starts it all. It works, but the first hour of any new project is spent on plumbing you've written a dozen times before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spring Boot&lt;/strong&gt; is the layer that does that plumbing for you. It looks at what libraries you've added, guesses a sensible setup, and steps aside the moment you want to decide something yourself. Everything in module M2 was a different angle on that one promise. This recap ties the ten topics back into the single story they were always telling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one sentence that holds it all together
&lt;/h2&gt;

&lt;p&gt;Here is the mental model to keep:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What's on your classpath, plus the settings you provide, flows through a set of conditions that decide which pre-written configuration Boot applies — and you can override any of it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again, because every topic in this module is one clause of it. Starters put things &lt;em&gt;on the classpath&lt;/em&gt;. Externalized config supplies &lt;em&gt;the settings&lt;/em&gt;. Conditional annotations are &lt;em&gt;the conditions&lt;/em&gt;. Auto-configuration is &lt;em&gt;the pre-written configuration&lt;/em&gt;. &lt;code&gt;SpringApplication.run&lt;/code&gt; is what sets the whole flow in motion. Actuator lets you watch the result. The jar is how you ship it.&lt;/p&gt;

&lt;p&gt;Let's walk that sentence from left to right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starters: how things get onto the classpath
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;starter&lt;/strong&gt; is a dependency that pulls in other dependencies. It contains almost no code of its own — it's a curated shopping list. Adding one line:&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;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;quietly brings in Spring MVC, an embedded Tomcat, and the Jackson JSON library, all at versions known to work together. You asked for "web" and got a matched set instead of hunting down five compatible version numbers yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key thing to remember:&lt;/strong&gt; a starter's real job is to change what's on your classpath. That matters because the classpath is the signal everything downstream reads. Which brings us to the next clause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditional annotations: the "if" behind every decision
&lt;/h2&gt;

&lt;p&gt;Boot ships hundreds of small configuration classes, but it doesn't blindly apply them. Each one is guarded by a &lt;strong&gt;condition&lt;/strong&gt; — a rule that says "only do this &lt;em&gt;if&lt;/em&gt; something is true."&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;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@ConditionalOnClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&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;@ConditionalOnMissingBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataSourceAutoConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// configure a connection pool — but only if the conditions pass&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the two guards in plain English. &lt;strong&gt;&lt;code&gt;@ConditionalOnClass&lt;/code&gt;&lt;/strong&gt; means "only if the &lt;code&gt;DataSource&lt;/code&gt; type is on the classpath" — i.e. only if you added a database starter. &lt;strong&gt;&lt;code&gt;@ConditionalOnMissingBean&lt;/code&gt;&lt;/strong&gt; means "only if you haven't already defined a &lt;code&gt;DataSource&lt;/code&gt; yourself."&lt;/p&gt;

&lt;p&gt;Those two annotations together are the whole personality of Boot. It configures things &lt;em&gt;because a starter put the class there&lt;/em&gt;, and it &lt;em&gt;backs off the instant you take over&lt;/em&gt;. That is what people mean when they call Boot "opinionated but polite." Hold onto this — it explains the next clause completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-configuration: the pre-written setup that reads those conditions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Auto-configuration&lt;/strong&gt; is the pile of guarded &lt;code&gt;@Configuration&lt;/code&gt; classes we just met. At startup Boot gathers a long list of them and offers each one to the conditions. The ones whose conditions pass contribute beans; the rest silently do nothing.&lt;/p&gt;

&lt;p&gt;Where does the list come from? Boot reads a plain text file baked into the library jars:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



Each line is the name of one auto-configuration class. (Older Boot used `spring.factories` for the same job — same idea, different filename.) There's no magic scanning of your code and no reflection guesswork: it's a list in a file, filtered by conditions.


**The key thing to remember:** auto-configuration is not "Boot being clever." It's a fixed list of candidate configs, each one asking "do my conditions hold?" If you ever wonder _why_ a bean appeared, the honest answer is always "a class on that list had all its conditions satisfied."


## Externalized config: the settings that feed the machine


Auto-configuration still needs values — a port, a database URL, a pool size. Those live _outside_ your compiled code, in **externalized configuration**: `application.properties` or `application.yml`, environment variables, command-line arguments, and more.


The important part is **order**. When the same key is set in two places, the higher-precedence source wins. Roughly, from strongest to weakest:

- Command-line arguments (`--server.port=9000`)
- OS environment variables
- `application.properties` inside your jar

So a value baked into your jar is a _default_, and an environment variable at deploy time quietly overrides it — no rebuild required. That single rule is why the same jar can run unchanged on a laptop and in production.


## Binding: turning flat keys into real objects


Loose key–value pairs are awkward to use in code, so Boot **binds** them onto Java objects. There are two ways in.


**`@Value`** injects one key into one field:




```java
@Value("${server.port}")
private int port;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fine for a stray value, clumsy once you have a dozen related settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@ConfigurationProperties&lt;/code&gt;&lt;/strong&gt; binds a whole group onto a typed object:&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;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"app.mail"&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;class&lt;/span&gt; &lt;span class="nc"&gt;MailProperties&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;String&lt;/span&gt; &lt;span class="n"&gt;host&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;int&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every &lt;code&gt;app.mail.*&lt;/code&gt; key lands on a field automatically. And because of &lt;strong&gt;relaxed binding&lt;/strong&gt;, Boot treats &lt;code&gt;app.mail.host&lt;/code&gt;, &lt;code&gt;APP_MAIL_HOST&lt;/code&gt;, and &lt;code&gt;app.mail.HOST&lt;/code&gt; as the same property — so a kebab-case file and an UPPER_SNAKE environment variable both fill the same field. That's the quiet reason your properties file and your Docker environment variables agree without you doing anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember it as:&lt;/strong&gt; &lt;code&gt;@Value&lt;/code&gt; for one loose value, &lt;code&gt;@ConfigurationProperties&lt;/code&gt; for a family of related ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profiles: swapping settings per environment
&lt;/h2&gt;

&lt;p&gt;You rarely want one set of settings everywhere. A &lt;strong&gt;profile&lt;/strong&gt; is a named bundle of configuration that's only active when you switch it on.&lt;/p&gt;

&lt;p&gt;Put shared settings in &lt;code&gt;application.properties&lt;/code&gt;, and environment-specific ones in &lt;code&gt;application-dev.properties&lt;/code&gt; or &lt;code&gt;application-prod.properties&lt;/code&gt;. Activate one with a property:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
spring.profiles.active=prod&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



Boot layers the profile-specific file on top of the base file. You can also gate a whole bean on a profile:




```java
@Bean
@Profile("prod")
public MeterRegistry realMetrics() { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The key thing to remember:&lt;/strong&gt; profiles are how one build carries every environment's config and picks the right slice at launch — the natural partner to the precedence order from earlier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting it all: the run() method
&lt;/h2&gt;

&lt;p&gt;Everything above is potential energy until one line fires 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="nd"&gt;@SpringBootApplication&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;App&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="nc"&gt;SpringApplication&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="nc"&gt;App&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;args&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;&lt;code&gt;@SpringBootApplication&lt;/code&gt; is three annotations in one: it enables component scanning, marks the class as a config source, and — crucially — switches on auto-configuration. Then &lt;code&gt;run&lt;/code&gt; does the work in order: it reads your externalized config, creates the &lt;strong&gt;application context&lt;/strong&gt; (the container that holds your beans), lets auto-configuration and your own components populate it, and — for a web app — starts an &lt;strong&gt;embedded Tomcat&lt;/strong&gt; inside the same process.&lt;/p&gt;

&lt;p&gt;That embedded server is the mental shift from old Spring. There's no external server to install and deploy into; the web server is just another bean Boot started for you. Your application &lt;em&gt;is&lt;/em&gt; the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actuator: watching the thing once it runs
&lt;/h2&gt;

&lt;p&gt;A running app you can't see inside is a liability. &lt;strong&gt;Actuator&lt;/strong&gt; is a starter that adds ready-made HTTP endpoints reporting on the live application.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
GET /actuator/health   -&amp;gt; {"status":"UP"}&lt;br&gt;
GET /actuator/metrics  -&amp;gt; memory, request timings, pool usage&lt;br&gt;
GET /actuator/info     -&amp;gt; build version, git commit, whatever you add&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



`/health` is what a load balancer or Kubernetes probe pings to decide if traffic should reach you. `/metrics` is what a monitoring system scrapes. Notice the pattern holding again: you _added a starter_, a _condition_ saw Actuator on the classpath, and _auto-configuration_ wired the endpoints. Same story, one more time.


## Packaging: the fat jar, the layered jar, and DevTools


Finally you ship it. Boot builds a **fat jar** (also called an uber jar): a single `.jar` containing your code, every dependency, _and_ the embedded server. One file runs anywhere a JVM exists:




```bash
java -jar app.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No server to pre-install, no unpacking — the artifact is the whole application.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;layered jar&lt;/strong&gt; is that same fat jar organized into layers by how often they change — dependencies in one layer, your code in another. Docker caches unchanged layers, so rebuilding after a one-line code change re-ships only your thin top layer instead of hundreds of megabytes of libraries.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;DevTools&lt;/strong&gt; is a development-only helper that restarts the app automatically when it sees a class change, so you skip the manual stop-and-start loop. It disables itself in production, so it never ships with the fat jar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the whole sentence back together
&lt;/h2&gt;

&lt;p&gt;Trace one request through the story and the module clicks into a single line:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;starter&lt;/strong&gt; puts classes on the &lt;strong&gt;classpath&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional annotations&lt;/strong&gt; read that classpath — and your existing beans — and decide what applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-configuration&lt;/strong&gt; contributes the beans whose conditions passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Externalized config&lt;/strong&gt;, bound by &lt;strong&gt;&lt;code&gt;@ConfigurationProperties&lt;/code&gt;&lt;/strong&gt; and sliced by &lt;strong&gt;profiles&lt;/strong&gt;, supplies the settings those beans need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SpringApplication.run&lt;/code&gt;&lt;/strong&gt; builds the context and starts &lt;strong&gt;embedded Tomcat&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actuator&lt;/strong&gt; reports on the running result.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;fat or layered jar&lt;/strong&gt; ships it, with &lt;strong&gt;DevTools&lt;/strong&gt; smoothing the ride while you build.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you remember nothing else from M2, remember this: &lt;strong&gt;Boot is a chain of sensible defaults, each one guarded by a condition and open to your override.&lt;/strong&gt; Once that clicks, none of its behavior looks like magic — it looks like a list being filtered by what you asked for. That is exactly the footing you want before stepping into the web layer next.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Fat jar vs layered jar; DevTools</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sat, 12 Sep 2026 08:45:30 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/fat-jar-vs-layered-jar-devtools-5ajc</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/fat-jar-vs-layered-jar-devtools-5ajc</guid>
      <description>&lt;h2&gt;
  
  
  Why your Spring Boot app is one runnable file
&lt;/h2&gt;

&lt;p&gt;You finish a Spring Boot service, run a single command, and a full web server starts up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Tomcat to install first. No classpath to spell out by hand. No &lt;code&gt;WEB-INF&lt;/code&gt; folder dropped into a separate application server. One file, one command, a running app that answers HTTP on a port.&lt;/p&gt;

&lt;p&gt;That single file is what this article is about. We will open it up and see what is inside, understand &lt;strong&gt;why&lt;/strong&gt; it is built that way, and then look at the two packaging choices that decide how it runs and how quickly it ships: the &lt;strong&gt;fat jar&lt;/strong&gt; and the &lt;strong&gt;layered jar&lt;/strong&gt;. At the end we meet &lt;strong&gt;DevTools&lt;/strong&gt;, the piece that keeps the write-code-and-see-it loop fast while you are still building.&lt;/p&gt;

&lt;p&gt;Start with a plain question: why can't an ordinary jar already do this?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a jar cannot hold other jars
&lt;/h2&gt;

&lt;p&gt;A normal Java application is not one jar. It is your code plus every library it depends on — often dozens of jars. To run it, you list all of them on the classpath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-cp&lt;/span&gt; app.jar:lib/jackson.jar:lib/tomcat.jar:... com.example.App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is fragile. You have to ship a folder of jars alongside your own, and get the whole list right every time.&lt;/p&gt;

&lt;p&gt;The obvious wish is to put everything into one jar. But standard Java has a hard limit here: &lt;strong&gt;the classpath cannot see a jar nested inside another jar.&lt;/strong&gt; Java's built-in class loading knows how to read &lt;code&gt;.class&lt;/code&gt; files from inside a jar, and it knows how to read a jar sitting on the filesystem — but it does not know how to reach into &lt;code&gt;app.jar&lt;/code&gt;, find &lt;code&gt;jackson.jar&lt;/code&gt; inside it, and load classes out of &lt;em&gt;that&lt;/em&gt;. Nested jars are invisible to it.&lt;/p&gt;

&lt;p&gt;So "just one file" is not something plain Java hands you. Something has to make it work. That something is the Spring Boot build plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside the fat jar
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;mvn package&lt;/code&gt; (or the Gradle equivalent), the &lt;strong&gt;Spring Boot plugin&lt;/strong&gt; does an extra step called &lt;strong&gt;repackaging&lt;/strong&gt;. It takes the plain jar your compiler produced and rebuilds it into an &lt;strong&gt;executable jar&lt;/strong&gt; — the thing people casually call a &lt;strong&gt;fat jar&lt;/strong&gt;, because it is fat with all its dependencies inside.&lt;/p&gt;

&lt;p&gt;Peek inside one and the layout is deliberate:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
app.jar&lt;br&gt;
├── META-INF/&lt;br&gt;
│   └── MANIFEST.MF&lt;br&gt;
├── org/springframework/boot/loader/   ← Spring's own loader classes&lt;br&gt;
├── BOOT-INF/&lt;br&gt;
│   ├── classes/                        ← your compiled code&lt;br&gt;
│   └── lib/                            ← every dependency, as nested jars&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



Three things share the file. Your own classes live under **`BOOT-INF/classes`**. Every library you depend on sits as an untouched jar under **`BOOT-INF/lib`**. And at the root sits a small set of Spring's own classes — the loader.


Notice what Spring did _not_ do. It did not melt all the libraries down and pour their `.class` files into one flat pile. The older "uber jar" approach did exactly that, and it caused real pain: two libraries shipping a file at the same path would silently overwrite each other, and once unpacked you could no longer tell which library a class came from. Spring keeps each dependency as its own intact jar. Cleaner — but now we are back to the nested-jar problem Java can't solve on its own. That is what the loader classes are for.


## The launcher that boots the jar


Open the manifest and you see the trick:




```plain text
Main-Class:  org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.MyApplication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you type &lt;code&gt;java -jar app.jar&lt;/code&gt;, the JVM runs whatever &lt;code&gt;Main-Class&lt;/code&gt; names. That is &lt;strong&gt;not&lt;/strong&gt; your application. It is Spring's &lt;strong&gt;&lt;code&gt;JarLauncher&lt;/code&gt;&lt;/strong&gt;, the entry point that knows how to handle the nested layout.&lt;/p&gt;

&lt;p&gt;The launcher does two jobs. First it installs a &lt;strong&gt;custom classloader&lt;/strong&gt; — a small piece of Spring code that &lt;em&gt;does&lt;/em&gt; know how to read a jar-inside-a-jar, reaching into &lt;code&gt;BOOT-INF/lib&lt;/code&gt; to load classes straight out of those nested jars. Then it reads &lt;strong&gt;&lt;code&gt;Start-Class&lt;/code&gt;&lt;/strong&gt; from the manifest, which points at your real application, and hands control over to your &lt;code&gt;main&lt;/code&gt; method — now running with a classloader that can see everything.&lt;/p&gt;

&lt;p&gt;So the boot sequence is: JVM starts the launcher → launcher builds a classloader that can read nested jars → launcher calls your &lt;code&gt;main&lt;/code&gt;. From your code's point of view, every dependency is simply on the classpath and life is normal. The launcher quietly bridged the gap that plain Java left open.&lt;/p&gt;

&lt;p&gt;That is the whole fat jar story. It is a brilliant way to &lt;em&gt;run&lt;/em&gt; an app. The trouble starts when you want to &lt;em&gt;ship&lt;/em&gt; one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why one big jar is slow to ship
&lt;/h2&gt;

&lt;p&gt;Most Spring Boot apps run in a &lt;strong&gt;Docker image&lt;/strong&gt; these days. A Docker image is built in &lt;strong&gt;layers&lt;/strong&gt; — stacked, read-only slices of a filesystem. The reason layers exist is caching: when you rebuild an image, Docker reuses any layer whose inputs did not change and only rebuilds the ones that did. Unchanged layers are also skipped when pushing and pulling, so a rebuild that touches one small layer ships almost nothing.&lt;/p&gt;

&lt;p&gt;Now picture the naive Dockerfile:&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; eclipse-temurin:21-jre&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole fat jar is copied in as a single layer. And here is the sting: your 30 MB of dependencies and your 200 KB of code are fused into that one file. Change a single line in a controller, rebuild, and because the file's bytes changed, Docker throws away the cached layer and re-ships the entire 30 MB — even though the dependencies did not move an inch.&lt;/p&gt;

&lt;p&gt;The costs are lopsided. Dependencies are large and rarely change. Your code is tiny and changes constantly. Baking them into one layer means every code change pays the full weight of the dependencies. We want the opposite: the things that rarely change in their own layer, and the things that change often in another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layered jar
&lt;/h2&gt;

&lt;p&gt;This is exactly what a &lt;strong&gt;layered jar&lt;/strong&gt; gives you. Since Spring Boot 2.3, the repackaged jar can carry an index file, &lt;strong&gt;&lt;code&gt;BOOT-INF/layers.idx&lt;/code&gt;&lt;/strong&gt;, that sorts its contents into named &lt;strong&gt;layers&lt;/strong&gt;, ordered from least likely to change to most likely:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependencies             ← released third-party libraries&lt;/li&gt;
&lt;li&gt;spring-boot-loader        ← Spring's loader classes&lt;/li&gt;
&lt;li&gt;snapshot-dependencies     ← -SNAPSHOT libraries (change more often)&lt;/li&gt;
&lt;li&gt;application               ← your classes and resources
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ordering is the whole point. Your &lt;code&gt;dependencies&lt;/code&gt; almost never change between builds, so that layer stays cached for weeks. Your &lt;code&gt;application&lt;/code&gt; code changes every commit, so only that thin layer is rebuilt and re-shipped.&lt;/p&gt;

&lt;p&gt;To use the layers you first pull them out of the jar. The jar can unpack itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-Djarmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;layertools &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar extract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produces one folder per layer — &lt;code&gt;dependencies/&lt;/code&gt;, &lt;code&gt;spring-boot-loader/&lt;/code&gt;, &lt;code&gt;snapshot-dependencies/&lt;/code&gt;, &lt;code&gt;application/&lt;/code&gt; — each holding just its slice of the original jar.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Dockerfile that respects the layers
&lt;/h2&gt;

&lt;p&gt;Now the layers map cleanly onto Docker's caching. The trick is a &lt;strong&gt;multi-stage build&lt;/strong&gt;: one stage extracts the layers, and a second stage copies them in — one &lt;code&gt;COPY&lt;/code&gt; per layer, in change-frequency order.&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="c"&gt;# Stage 1: extract the layers from the fat jar&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:21-jre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;java &lt;span class="nt"&gt;-Djarmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;layertools &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar extract

&lt;span class="c"&gt;# Stage 2: assemble the image, layer by layer&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21-jre&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/spring-boot-loader/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/snapshot-dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/application/ ./&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "org.springframework.boot.loader.launch.JarLauncher"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;COPY&lt;/code&gt; becomes its own Docker layer. Because &lt;code&gt;dependencies&lt;/code&gt; is copied before &lt;code&gt;application&lt;/code&gt;, a code-only change invalidates only the last &lt;code&gt;COPY&lt;/code&gt; — Docker reuses the cached dependency layer and rebuilds just the tiny application layer. The 30 MB stays put; only your 200 KB moves.&lt;/p&gt;

&lt;p&gt;Notice the entry point is &lt;code&gt;JarLauncher&lt;/code&gt; again — the same launcher from the fat jar. Even unpacked across folders, Spring still uses its loader to start your app. The layered jar did not change how the app &lt;em&gt;runs&lt;/em&gt;; it only reorganised how it is &lt;em&gt;stored&lt;/em&gt; so Docker can cache it well.&lt;/p&gt;

&lt;p&gt;That covers building and shipping. The last piece is the loop you live in while writing the code in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  DevTools: a faster inner loop
&lt;/h2&gt;

&lt;p&gt;Restarting an app by hand after every change is the slow tax of development: save a file, stop the app, run it again, wait for Spring to start, click back to where you were. The dependency &lt;strong&gt;&lt;code&gt;spring-boot-devtools&lt;/code&gt;&lt;/strong&gt; exists to shrink that loop.&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;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-devtools&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;optional&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/optional&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;With DevTools on the classpath, Spring watches your project's files. The moment your IDE recompiles a class, DevTools notices and &lt;strong&gt;automatically restarts&lt;/strong&gt; the application. You save; a second later the running app already reflects the change. No manual stop-and-start.&lt;/p&gt;

&lt;p&gt;The natural worry is: isn't restarting the whole app still slow? It would be — if DevTools did a cold restart. It does not, and the reason is clever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two classloaders make the restart quick
&lt;/h2&gt;

&lt;p&gt;DevTools splits your app's classes across &lt;strong&gt;two classloaders&lt;/strong&gt;. The &lt;strong&gt;base classloader&lt;/strong&gt; loads the things that do not change while you work — the third-party libraries from your dependencies. The &lt;strong&gt;restart classloader&lt;/strong&gt; loads the code you are actively editing — your own project classes.&lt;/p&gt;

&lt;p&gt;When a file changes, DevTools throws away only the &lt;strong&gt;restart&lt;/strong&gt; classloader and builds a fresh one, reloading just your classes. The base classloader — holding all the heavy libraries — is left untouched, and the JVM itself never stops.&lt;/p&gt;

&lt;p&gt;That is why a DevTools restart feels near-instant while a cold start takes several seconds. A cold start reloads &lt;em&gt;everything&lt;/em&gt;: the JVM boots, every library class is read and verified again from scratch. DevTools reloads only the small, changing half and keeps the expensive, stable half warm in memory. Same fresh application state, a fraction of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the loop: caches off, browser refresh
&lt;/h2&gt;

&lt;p&gt;DevTools smooths two more rough edges.&lt;/p&gt;

&lt;p&gt;By default it &lt;strong&gt;overrides caching properties&lt;/strong&gt; that make sense in production but get in your way during development. Template engines like Thymeleaf normally cache compiled templates; DevTools switches that caching off so a tweak to an HTML template shows up on the next request without even a restart.&lt;/p&gt;

&lt;p&gt;It also runs a &lt;strong&gt;LiveReload&lt;/strong&gt; server. Paired with a small browser extension, it refreshes the page in your browser automatically the instant the app restarts — so you are not even reaching for the reload button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why none of this reaches production
&lt;/h2&gt;

&lt;p&gt;All of that convenience raises an obvious flag: you would never want auto-restart or disabled caches on a live server. DevTools handles this for you, on two levels.&lt;/p&gt;

&lt;p&gt;First, it &lt;strong&gt;disables itself&lt;/strong&gt; when it detects the app is running as a fully packaged jar — the &lt;code&gt;java -jar app.jar&lt;/code&gt; path a real deployment uses. DevTools is active when you run from your IDE or the build tool, and dormant when it sees it is running from a packaged archive.&lt;/p&gt;

&lt;p&gt;Second, the repackaging step &lt;strong&gt;strips DevTools out&lt;/strong&gt; of the fat jar entirely, because you declared it &lt;code&gt;optional&lt;/code&gt;. So the dependency is present while you develop and simply absent from the artifact you ship. The fast inner loop is a development-only tool, by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one connected picture
&lt;/h2&gt;

&lt;p&gt;Three ideas, one line of reasoning. The &lt;strong&gt;fat jar&lt;/strong&gt; solves &lt;em&gt;running&lt;/em&gt;: Spring's launcher and its custom classloader let a single file carry every nested dependency, so &lt;code&gt;java -jar&lt;/code&gt; just works. The &lt;strong&gt;layered jar&lt;/strong&gt; solves &lt;em&gt;shipping&lt;/em&gt;: the same jar is sorted into layers by how often each part changes, so Docker caches the heavy, stable dependencies and re-ships only your thin, fast-moving code. And &lt;strong&gt;DevTools&lt;/strong&gt; solves &lt;em&gt;developing&lt;/em&gt;: two classloaders keep restarts near-instant, caches step out of your way, and the whole thing removes itself before it ever reaches production.&lt;/p&gt;

&lt;p&gt;Build fat so it runs anywhere; layer it so it ships cheaply; lean on DevTools so getting there is quick.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Actuator: health / metrics / info</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:01:53 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/actuator-health-metrics-info-9mh</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/actuator-health-metrics-info-9mh</guid>
      <description>&lt;h2&gt;
  
  
  A running app is a black box
&lt;/h2&gt;

&lt;p&gt;Once your Spring Boot app leaves your laptop and lands on a server, it becomes a process you can't see into. From the outside, you can't tell whether it's healthy, how much memory it's burning, or even which build of the code is actually running. Then something breaks at 3 a.m., someone needs answers fast, and SSH-ing onto the box to poke around is slow and risky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spring Boot Actuator&lt;/strong&gt; is the module that fixes this. It bolts a set of ready-made &lt;em&gt;operational&lt;/em&gt; features onto your app — standard ways to inspect and monitor it while it runs — and serves them over HTTP so that tools and humans can ask the app about itself. You write none of it. You add one dependency, and the features appear.&lt;/p&gt;

&lt;p&gt;You meet Actuator the day your app goes to production: the load balancer needs a health check, the dashboards need metrics, and the on-call engineer needs to know what version is live. This article walks the three endpoints you'll reach for first — &lt;strong&gt;health&lt;/strong&gt;, &lt;strong&gt;metrics&lt;/strong&gt;, and &lt;strong&gt;info&lt;/strong&gt; — and finishes with how to expose them without handing the world a window into your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it on
&lt;/h2&gt;

&lt;p&gt;Everything starts with one starter dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;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-actuator&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;Boot's auto-configuration notices the starter on the classpath and wires everything up for you — no &lt;code&gt;@Bean&lt;/code&gt; definitions, no config class. Each operational feature it registers is called an &lt;strong&gt;endpoint&lt;/strong&gt;: a small, named unit of functionality (like &lt;code&gt;health&lt;/code&gt; or &lt;code&gt;metrics&lt;/code&gt;) that you can reach at a URL under a common base path, &lt;code&gt;/actuator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two separate switches govern every endpoint, and mixing them up is a classic source of confusion. An endpoint is &lt;strong&gt;enabled&lt;/strong&gt; (it exists and does its work) and, separately, &lt;strong&gt;exposed&lt;/strong&gt; (it's reachable over HTTP). Most endpoints are enabled by default, but for safety Boot only &lt;em&gt;exposes&lt;/em&gt; &lt;code&gt;health&lt;/code&gt; over HTTP out of the box. We'll open up the others deliberately at the end.&lt;/p&gt;

&lt;p&gt;With the starter added, hit the base path and the app lists what's on offer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's walk the three you'll use daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Health: is the app OK?
&lt;/h2&gt;

&lt;p&gt;The health endpoint answers the single most-asked question in operations: &lt;em&gt;is this thing working?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;UP&lt;/code&gt; is not a guess. Actuator ships small probes called &lt;strong&gt;health indicators&lt;/strong&gt;, and it auto-registers one for each piece of infrastructure it detects — a datasource, a message broker, disk space, and so on. Each indicator checks its own dependency and reports &lt;code&gt;UP&lt;/code&gt; or &lt;code&gt;DOWN&lt;/code&gt;. A database indicator, for example, runs a trivial validation query; if the query fails, that indicator goes &lt;code&gt;DOWN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The endpoint then &lt;strong&gt;aggregates&lt;/strong&gt; all indicators into the one top-level status, and the rule is simple: the &lt;em&gt;worst&lt;/em&gt; status wins. One &lt;code&gt;DOWN&lt;/code&gt; indicator drags the whole app to &lt;code&gt;DOWN&lt;/code&gt;, which is exactly what a load balancer wants — a mostly-working app that can't reach its database is not ready to serve traffic.&lt;/p&gt;

&lt;p&gt;By default you only see the rolled-up status, not the per-indicator breakdown. To see the details, turn them on:&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;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;health&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;show-details&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the response names each contributor:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"components"&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;"db"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&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;"diskSpace"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&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;When the built-in indicators aren't enough, you write your own by implementing the &lt;code&gt;HealthIndicator&lt;/code&gt; interface. Say your service depends on an external payment gateway and you want its reachability reflected in your health:&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;@Component&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;PaymentGatewayHealth&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HealthIndicator&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;PaymentGatewayClient&lt;/span&gt; &lt;span class="n"&gt;client&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;PaymentGatewayHealth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGatewayClient&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&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="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Health&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;up&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;withDetail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gateway"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"reachable"&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;down&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;withDetail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gateway"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unreachable"&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;Because it's a &lt;code&gt;@Component&lt;/code&gt;, component scanning picks it up and Actuator folds it into the aggregate automatically. The bean name (&lt;code&gt;paymentGatewayHealth&lt;/code&gt; → &lt;code&gt;paymentGateway&lt;/code&gt;) becomes its key in the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Liveness and readiness
&lt;/h3&gt;

&lt;p&gt;Under an orchestrator like Kubernetes, "is it OK?" splits into two genuinely different questions, and answering them with one signal causes real outages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liveness&lt;/strong&gt; asks: &lt;em&gt;is this app broken beyond repair?&lt;/em&gt; If the answer is no, restarting it is the fix. &lt;strong&gt;Readiness&lt;/strong&gt; asks: &lt;em&gt;can it take traffic right this second?&lt;/em&gt; An app can be perfectly alive yet not ready — still warming a cache, or briefly waiting on a dependency. The distinction matters because the wrong reaction is harmful: restart an app that's only &lt;em&gt;temporarily&lt;/em&gt; not ready and you kill a healthy process; keep routing traffic to an app that's &lt;em&gt;fatally&lt;/em&gt; broken and every request fails.&lt;/p&gt;

&lt;p&gt;Actuator exposes these as &lt;strong&gt;health groups&lt;/strong&gt; — named subsets of indicators — once you enable the probes:&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;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;health&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;probes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now get two extra URLs, &lt;code&gt;/actuator/health/liveness&lt;/code&gt; and &lt;code&gt;/actuator/health/readiness&lt;/code&gt;, each aggregating only its own group. You point Kubernetes' liveness probe at the first and its readiness probe at the second, and each gets the answer it should act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics: how is it behaving?
&lt;/h2&gt;

&lt;p&gt;Health is a yes/no. &lt;strong&gt;Metrics&lt;/strong&gt; are the numbers over time — memory used, request latency, how many orders were placed. Actuator collects these through a library called &lt;strong&gt;Micrometer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Micrometer is worth understanding for one reason: it's a &lt;em&gt;facade&lt;/em&gt;. Just as SLF4J lets you write logging calls without committing to a specific logging backend, Micrometer lets your app record measurements without committing to a specific monitoring system. Your code talks to Micrometer; Micrometer ships the numbers to whatever registry you plug in — Prometheus, Datadog, CloudWatch. Swap the backend by swapping a dependency, not by rewriting your code.&lt;/p&gt;

&lt;p&gt;The endpoint first lists the metric names it knows about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;names&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jvm.memory.used&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.server.requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system.cpu.usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read one, drill into it by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response carries the current measurement plus a list of &lt;strong&gt;tags&lt;/strong&gt; — dimensions you can slice the number by. A single metric like &lt;code&gt;http.server.requests&lt;/code&gt; is tagged with &lt;code&gt;uri&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt;, so instead of one blurry total you can ask a precise question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns only the requests that ended in a 500 — request counts and timings for your failing calls alone.&lt;/p&gt;

&lt;p&gt;Recording your own metric means asking Micrometer's registry for a meter. A &lt;strong&gt;counter&lt;/strong&gt; — a value that only ever goes up — is the simplest:&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;@Service&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;OrderService&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;Counter&lt;/span&gt; &lt;span class="n"&gt;ordersPlaced&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;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MeterRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&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;ordersPlaced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders.placed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&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;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... business logic ...&lt;/span&gt;
        &lt;span class="n"&gt;ordersPlaced&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&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;&lt;code&gt;MeterRegistry&lt;/code&gt; is a bean Actuator has already put in the context, so you just inject it. Now &lt;code&gt;orders.placed&lt;/code&gt; shows up alongside the built-in metrics, sliceable and shippable like the rest.&lt;/p&gt;

&lt;p&gt;In practice you rarely poll &lt;code&gt;/actuator/metrics&lt;/code&gt; by hand. You add &lt;code&gt;micrometer-registry-prometheus&lt;/code&gt;, which lights up a &lt;code&gt;/actuator/prometheus&lt;/code&gt; endpoint, and a Prometheus server periodically &lt;strong&gt;scrapes&lt;/strong&gt; that URL — pulls the current values on a schedule — into a time-series database your dashboards read from. Your app's only job is to expose the numbers; the monitoring stack does the collecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Info: what is this, exactly?
&lt;/h2&gt;

&lt;p&gt;The info endpoint answers the on-call engineer's first question: &lt;em&gt;what am I even looking at?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;actuator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out of the box it returns &lt;code&gt;{}&lt;/code&gt;, because info is assembled from &lt;strong&gt;info contributors&lt;/strong&gt;, and none carry data until you give them some. The quickest is static properties — anything you nest under an &lt;code&gt;info&lt;/code&gt; key in your configuration is surfaced verbatim:&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;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Orders Service&lt;/span&gt;
    &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Payments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more valuable contributors are generated at build time. Point the &lt;code&gt;spring-boot-maven-plugin&lt;/code&gt; at its &lt;code&gt;build-info&lt;/code&gt; goal and it writes the app's version and build timestamp into a file Actuator reads; add the &lt;code&gt;git-commit-id&lt;/code&gt; plugin and the exact commit hash of the running code appears too. Now &lt;code&gt;/actuator/info&lt;/code&gt; tells you precisely which build is live — no guessing whether the deploy actually went through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposing endpoints — and the trap in doing it
&lt;/h2&gt;

&lt;p&gt;Remember the enabled-versus-exposed split. Everything above is &lt;em&gt;enabled&lt;/em&gt;, but over HTTP only &lt;code&gt;health&lt;/code&gt; is &lt;em&gt;exposed&lt;/em&gt; by default. To serve the others, list them explicitly:&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;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;exposure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;health, info, metrics, prometheus&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's tempting to write &lt;code&gt;include: "*"&lt;/code&gt; and be done. &lt;strong&gt;Don't&lt;/strong&gt; — that's the trap. Actuator ships more than the three friendly endpoints. &lt;code&gt;env&lt;/code&gt; dumps your configuration (potentially including secrets), &lt;code&gt;heapdump&lt;/code&gt; downloads a full memory snapshot, &lt;code&gt;loggers&lt;/code&gt; lets a caller change log levels at runtime, &lt;code&gt;shutdown&lt;/code&gt; can stop the app. Exposing all of that on a public port hands an attacker a detailed map of your system, and sometimes the keys.&lt;/p&gt;

&lt;p&gt;Two defenses, ideally together. First, if the app already uses Spring Security, its filter chain protects the &lt;code&gt;/actuator/**&lt;/code&gt; paths like any other URL — require an authenticated admin role to reach them. Second, move the whole management surface onto its own port:&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;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actuator endpoints now live on &lt;code&gt;9001&lt;/code&gt; while your API stays on the main port. You firewall &lt;code&gt;9001&lt;/code&gt; off from the public internet, so only your monitoring systems inside the network can reach it — the numbers flow to your dashboards, and no further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leads
&lt;/h2&gt;

&lt;p&gt;Actuator turns the black box into something you can question while it runs: &lt;em&gt;is it healthy&lt;/em&gt; (health), &lt;em&gt;how is it behaving&lt;/em&gt; (metrics), and &lt;em&gt;what is it&lt;/em&gt; (info) — with one dependency and a little safe configuration. That's the app observing itself once it's deployed. The natural next question is how the app gets &lt;em&gt;packaged&lt;/em&gt; to be deployed in the first place — how Boot bundles everything into a runnable jar, and what a layered jar buys you — which is exactly where we go next.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>SpringApplication.run boot flow + embedded Tomcat</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:04:13 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/springapplicationrun-boot-flow-embedded-tomcat-5ag3</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/springapplicationrun-boot-flow-embedded-tomcat-5ag3</guid>
      <description>&lt;p&gt;Every Spring Boot application begins with the same unremarkable line. You have almost certainly typed it without thinking about 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="nd"&gt;@SpringBootApplication&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;StoreApplication&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="nc"&gt;SpringApplication&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="nc"&gt;StoreApplication&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;args&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;That single call &lt;em&gt;is&lt;/em&gt; the startup. There is no application server to install first, no WAR file to deploy, no &lt;code&gt;web.xml&lt;/code&gt; to configure. You run a plain Java &lt;code&gt;main&lt;/code&gt; method, and a second or two later an HTTP server is listening on port 8080. This post is about what happens inside that second — the ordered sequence &lt;code&gt;SpringApplication.run&lt;/code&gt; walks through, and exactly where the web server, running &lt;em&gt;inside&lt;/em&gt; your own process, slots into it.&lt;/p&gt;

&lt;p&gt;One word carries the whole story, so let me define it before using it. A &lt;strong&gt;container&lt;/strong&gt; is just an object whose job is to create your other objects, hold them, wire them together, and manage their lifetimes. In Spring, the objects it manages are called &lt;strong&gt;beans&lt;/strong&gt; — a bean is nothing more exotic than an object the container built and owns instead of you calling &lt;code&gt;new&lt;/code&gt; yourself. The container is also called the &lt;strong&gt;application context&lt;/strong&gt;. When you hear "context," picture that one big object holding every bean your app needs. Everything below is the story of how &lt;code&gt;run&lt;/code&gt; builds that context and then hands control to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one call, unpacked
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SpringApplication.run(StoreApplication.class, args)&lt;/code&gt; is a static convenience method. Under the hood it does two separate things, and it helps to see them apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what the static helper expands to&lt;/span&gt;
&lt;span class="nc"&gt;SpringApplication&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StoreApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;ConfigurableApplicationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First it &lt;strong&gt;constructs&lt;/strong&gt; a &lt;code&gt;SpringApplication&lt;/code&gt; object. Then it &lt;strong&gt;runs&lt;/strong&gt; it. The constructor makes a few decisions up front; the &lt;code&gt;run&lt;/code&gt; call executes the actual boot sequence. We will take them in that order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — the constructor decides what kind of app this is
&lt;/h2&gt;

&lt;p&gt;Before any bean exists, the &lt;code&gt;SpringApplication&lt;/code&gt; constructor asks one important question: &lt;em&gt;what type of application am I?&lt;/em&gt; It answers by looking at what is on the &lt;strong&gt;classpath&lt;/strong&gt; — the set of libraries your build pulled in.&lt;/p&gt;

&lt;p&gt;The result is a value called the &lt;strong&gt;&lt;code&gt;WebApplicationType&lt;/code&gt;&lt;/strong&gt;, and it has three possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SERVLET&lt;/code&gt;&lt;/strong&gt; — a servlet-based web class (Spring MVC's &lt;code&gt;DispatcherServlet&lt;/code&gt;) is on the classpath, so this is a traditional web app. This is the common case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;REACTIVE&lt;/code&gt;&lt;/strong&gt; — only the WebFlux reactive stack is present, no servlet stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NONE&lt;/code&gt;&lt;/strong&gt; — no web libraries at all, so this is a plain application that will run some logic and exit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody sets this flag by hand. The presence of &lt;code&gt;spring-boot-starter-web&lt;/code&gt; in your build is what makes it &lt;code&gt;SERVLET&lt;/code&gt;. This detection matters enormously, because it decides &lt;em&gt;which context to build in Step 3&lt;/em&gt; and &lt;em&gt;whether a web server starts at all&lt;/em&gt;. A batch job with no web starter gets &lt;code&gt;NONE&lt;/code&gt;, boots the same way, runs its work, and shuts down — no port is ever opened.&lt;/p&gt;

&lt;p&gt;The constructor also loads a couple of lists of helper objects (initializers and listeners) from configuration files bundled in the Spring jars, but you can treat those as pre-wiring for now. The headline decision is the application type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — preparing the environment
&lt;/h2&gt;

&lt;p&gt;Now &lt;code&gt;run(args)&lt;/code&gt; begins in earnest. Its first real job is to assemble the &lt;strong&gt;environment&lt;/strong&gt; — the merged bag of all configuration values your app can read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ConfigurableEnvironment&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prepareEnvironment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listeners&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment gathers your &lt;code&gt;application.properties&lt;/code&gt; (or &lt;code&gt;.yml&lt;/code&gt;), OS environment variables, command-line arguments, and any active &lt;strong&gt;profiles&lt;/strong&gt; — a profile being just a named set of config, like &lt;code&gt;dev&lt;/code&gt; or &lt;code&gt;prod&lt;/code&gt;, that you switch on to change behavior per deployment. This all happens &lt;em&gt;before&lt;/em&gt; any of your beans are created, and that ordering is deliberate: beans frequently need config values (a database URL, a port) at the moment they are built, so the config must already be resolved and waiting.&lt;/p&gt;

&lt;p&gt;This is also the point where Boot prints that ASCII banner. Small thing, but it marks the boundary: environment ready, context not yet built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — creating the right kind of context
&lt;/h2&gt;

&lt;p&gt;With the application type known and the environment ready, &lt;code&gt;run&lt;/code&gt; creates the context object 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="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createApplicationContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// picks a class based on WebApplicationType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is where the Step 1 decision pays off. For a &lt;code&gt;SERVLET&lt;/code&gt; app, Boot instantiates a context class whose full name is a mouthful — &lt;code&gt;AnnotationConfigServletWebServerApplicationContext&lt;/code&gt; — but whose meaning is simple: &lt;em&gt;a container that also knows how to run an embedded servlet web server.&lt;/em&gt; For a &lt;code&gt;NONE&lt;/code&gt; app it picks a plain context with no web machinery at all.&lt;/p&gt;

&lt;p&gt;At this moment the context is an empty shell. It knows &lt;em&gt;how&lt;/em&gt; to hold beans and &lt;em&gt;how&lt;/em&gt; to start a web server, but it contains no beans and has started nothing. Filling it is the next, and biggest, step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — refresh, where the beans actually come alive
&lt;/h2&gt;

&lt;p&gt;The heart of the whole boot is a single method call:&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;refreshContext&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="c1"&gt;// -&amp;gt; context.refresh()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Refresh&lt;/strong&gt; is the Spring lifecycle step that turns the empty shell into a fully wired, running application. It is worth knowing the sub-steps it runs, in order, because the web server appears in the middle of them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read all bean definitions.&lt;/strong&gt; The context scans your packages for &lt;code&gt;@Component&lt;/code&gt;, &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Controller&lt;/code&gt; and friends, and processes every &lt;code&gt;@Configuration&lt;/code&gt; class. Crucially, this is where &lt;strong&gt;auto-configuration&lt;/strong&gt; runs — Boot's mechanism that inspects the classpath and registers sensible default beans (a &lt;code&gt;DataSource&lt;/code&gt; if a JDBC driver is present, a JSON converter if Jackson is present, and so on). After this step the context has a full &lt;em&gt;catalogue&lt;/em&gt; of beans, but has not built most of them yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;onRefresh()&lt;/code&gt;&lt;/strong&gt; — a hook the servlet context overrides to &lt;strong&gt;create the embedded web server&lt;/strong&gt;. More on this in a moment; this is the key line for our topic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instantiate the singletons.&lt;/strong&gt; The context now actually builds every non-lazy singleton bean and injects their dependencies. Your services, repositories, and controllers become live objects here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;finishRefresh()&lt;/code&gt;&lt;/strong&gt; — the final step, which &lt;em&gt;starts&lt;/em&gt; the web server accepting traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice the shape: the server is &lt;strong&gt;created&lt;/strong&gt; in step 2 but only &lt;strong&gt;opened to traffic&lt;/strong&gt; in step 4, with all your beans instantiated in between. That gap is not an accident, and we will see why it is exactly right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the embedded server comes in
&lt;/h2&gt;

&lt;p&gt;Let me define &lt;strong&gt;embedded&lt;/strong&gt; plainly, because it is the whole trick. Traditionally you built a WAR file and deployed it &lt;em&gt;into&lt;/em&gt; a separately installed Tomcat. Spring Boot inverts that: Tomcat is just a &lt;strong&gt;library&lt;/strong&gt; on your classpath, and Boot starts it &lt;em&gt;from inside&lt;/em&gt; your application as an ordinary object. The server runs in your process; your process is not deployed into the server. That is what "embedded" means — the server is embedded in your app, not the other way around.&lt;/p&gt;

&lt;p&gt;So how does &lt;code&gt;onRefresh()&lt;/code&gt; create it? It looks in the context for a special bean: a &lt;strong&gt;&lt;code&gt;ServletWebServerFactory&lt;/code&gt;&lt;/strong&gt;. Auto-configuration will have registered one based on the classpath — &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt; when Tomcat is present (the default), or a Jetty or Undertow variant if you swapped the dependency. The context asks that factory to produce a running server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// inside the servlet context's createWebServer(), simplified&lt;/span&gt;
&lt;span class="nc"&gt;ServletWebServerFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getWebServerFactory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g. Tomcat&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;webServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWebServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getSelfInitializer&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;factory&lt;/strong&gt; is a bean whose only job is to build and configure a web server. &lt;code&gt;getWebServer(...)&lt;/code&gt; constructs the Tomcat instance, applies your settings (port, context path, thread pool), and returns it. Because the factory is an ordinary bean, customizing the server is just a matter of configuration or a bean — no server-install step anywhere:&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="nc"&gt;WebServerFactoryCustomizer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TomcatServletWebServerFactory&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;tuning&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;factory&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9090&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addConnectorCustomizers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"maxThreads"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"400"&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;That is the entire reason a Spring Boot &lt;strong&gt;fat jar&lt;/strong&gt; can be launched with &lt;code&gt;java -jar app.jar&lt;/code&gt; and just work. The server is a dependency packaged inside the jar, started by your own code. There is no external runtime to match versions with, and the same jar runs identically on your laptop and in a container.&lt;/p&gt;

&lt;h2&gt;
  
  
  The port opens last — and why that ordering matters
&lt;/h2&gt;

&lt;p&gt;Recall the gap from Step 4: the server is &lt;em&gt;created&lt;/em&gt; in &lt;code&gt;onRefresh()&lt;/code&gt; but does not &lt;em&gt;accept requests&lt;/em&gt; yet. Opening the port is deferred to &lt;code&gt;finishRefresh()&lt;/code&gt;, which runs only after every singleton bean has been instantiated.&lt;/p&gt;

&lt;p&gt;This ordering is a quiet piece of correctness. If Tomcat began accepting connections the instant it was created, requests could arrive while your services and database connections were still being wired — and hit half-built beans. By holding the connectors closed until the context is fully initialized, Boot guarantees that &lt;strong&gt;the first request only lands on a completely assembled application.&lt;/strong&gt; The port opening is the signal that the app is ready to serve.&lt;/p&gt;

&lt;p&gt;There is one genuine trap hiding here, though. After &lt;code&gt;refresh&lt;/code&gt; returns — port already open — &lt;code&gt;run&lt;/code&gt; does two more things:&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;callRunners&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="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// your ApplicationRunner / CommandLineRunner beans&lt;/span&gt;
&lt;span class="n"&gt;listeners&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ready&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="o"&gt;...);&lt;/span&gt;       &lt;span class="c1"&gt;// publishes ApplicationReadyEvent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;&lt;code&gt;CommandLineRunner&lt;/code&gt;&lt;/strong&gt; is a bean whose code runs once at startup, right after the context is ready — handy for warming a cache or seeding data:&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;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WarmupRunner&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CommandLineRunner&lt;/span&gt; &lt;span class="o"&gt;{&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;run&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="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// careful: the port is ALREADY open here&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;Because runners execute &lt;em&gt;after&lt;/em&gt; &lt;code&gt;finishRefresh&lt;/code&gt; has opened the port, Tomcat is already accepting traffic while your runner is still working. A slow runner means real requests can arrive before its warm-up finishes. If a task must complete &lt;em&gt;before&lt;/em&gt; any request is served, a &lt;code&gt;CommandLineRunner&lt;/code&gt; is the wrong place for it — do that work inside a bean's initialization instead, which happens back in step 3, before the port opens.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few gotchas worth carrying
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The port is already taken.&lt;/strong&gt; If something else holds 8080, the server fails during &lt;code&gt;finishRefresh&lt;/code&gt; and the whole boot aborts with &lt;code&gt;Port 8080 was already in use&lt;/code&gt;. Startup is all-or-nothing: a web server that cannot bind is treated as a failed application, not a warning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A web app that exits immediately.&lt;/strong&gt; If you expected a running server but the process starts and stops, the usual cause is a missing &lt;code&gt;spring-boot-starter-web&lt;/code&gt; — the constructor deduced &lt;code&gt;WebApplicationType.NONE&lt;/code&gt;, so no server was ever created. The fix is a dependency, not code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graceful shutdown.&lt;/strong&gt; The server that &lt;code&gt;run&lt;/code&gt; started is a managed bean, so it participates in shutdown too. Enabling graceful shutdown lets in-flight requests finish before the process dies, instead of being cut off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;graceful&lt;/span&gt;
&lt;span class="nx"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The whole flow in one breath
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SpringApplication.run&lt;/code&gt; constructs an application object that inspects the classpath to decide it is a servlet web app; prepares the environment so config is ready before any bean; creates a servlet-aware context; and then refreshes it. Refresh reads every bean definition (auto-configuration included), asks a factory bean to create an embedded Tomcat, instantiates all your singletons, and finally opens the port so the first request meets a fully assembled app. Runners fire last, after traffic is already flowing.&lt;/p&gt;

&lt;p&gt;Once you can see those phases, "embedded Tomcat" stops being magic. It is just a library, started as a bean, at a carefully chosen point in an ordered sequence — and the fat jar that runs anywhere is the natural consequence of the server living inside your app instead of your app living inside a server.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Profiles</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:05:12 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/profiles-38f7</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/profiles-38f7</guid>
      <description>&lt;h2&gt;
  
  
  The problem profiles solve
&lt;/h2&gt;

&lt;p&gt;Your application runs in more than one place. It runs on your laptop while you build it. It runs on a continuous-integration server that executes the tests. It runs in staging, where the team clicks around before a release. And it runs in production, serving real users.&lt;/p&gt;

&lt;p&gt;The code is identical in all four. What changes is the configuration &lt;em&gt;around&lt;/em&gt; it: the database it points at, how much it logs, which external services are real versus faked. On your laptop the database might be a throwaway one that lives in memory. In production it is a real, carefully guarded server.&lt;/p&gt;

&lt;p&gt;So you need one build that behaves differently depending on where it wakes up. That is exactly what a &lt;strong&gt;profile&lt;/strong&gt; is: a named set of configuration that Spring switches on or off as a group. Name the group &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;, or &lt;code&gt;test&lt;/code&gt;; turn one on; the pieces tagged with that name come alive, and the rest stay dormant.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, where configuration lives
&lt;/h2&gt;

&lt;p&gt;Before profiles make sense, one Spring idea has to be clear: the &lt;strong&gt;container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a Spring application starts, it builds a big registry of the objects your app is made of — the object that talks to the database, the one that handles web requests, the one that sends email. Spring creates them, wires them together, and hands them out wherever they are needed. Each managed object is a &lt;strong&gt;bean&lt;/strong&gt;, and the registry that holds them is the &lt;strong&gt;container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is the key part: the container is assembled once, at startup. Spring decides &lt;em&gt;which&lt;/em&gt; beans to create, and &lt;em&gt;how&lt;/em&gt; to configure them, in those first moments — before a single request is served.&lt;/p&gt;

&lt;p&gt;Profiles hook into exactly that decision. A profile can change which beans get created, and it can change the values they are configured with. Let's take those one at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tagging a bean with a profile
&lt;/h2&gt;

&lt;p&gt;Say you want an in-memory database on your laptop but the real one in production. You describe both, and mark each with the environment it belongs to.&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;@Configuration&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;DataSourceConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev"&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;DataSource&lt;/span&gt; &lt;span class="nf"&gt;devDataSource&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// an in-memory database, wiped on every restart&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EmbeddedDatabaseBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EmbeddedDatabaseType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;H2&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="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prod"&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;DataSource&lt;/span&gt; &lt;span class="nf"&gt;prodDataSource&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the real, persistent database&lt;/span&gt;
        &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt; &lt;span class="n"&gt;ds&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;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJdbcUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc:postgresql://db.internal:5432/orders"&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;ds&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;Two annotations do the work. &lt;strong&gt;&lt;code&gt;@Bean&lt;/code&gt;&lt;/strong&gt; marks a method whose return value becomes a bean in the container. &lt;strong&gt;&lt;code&gt;@Profile("dev")&lt;/code&gt;&lt;/strong&gt; attaches a condition to it: only register this bean when the &lt;code&gt;dev&lt;/code&gt; profile is active.&lt;/p&gt;

&lt;p&gt;So on your laptop with &lt;code&gt;dev&lt;/code&gt; active, the container builds the in-memory database and never even looks at the production one. Flip to &lt;code&gt;prod&lt;/code&gt; and the opposite happens.&lt;/p&gt;

&lt;p&gt;Notice what the rest of the app sees. Both methods produce a &lt;code&gt;DataSource&lt;/code&gt;. The code that needs a database just asks for a &lt;code&gt;DataSource&lt;/code&gt; and gets whichever one this environment built. The switch is invisible to everything downstream — which raises the obvious question: how does Spring know which profile is active?&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning a profile on
&lt;/h2&gt;

&lt;p&gt;A profile is just a string, and it stays inactive until something names it. That "something" is a property called &lt;code&gt;spring.profiles.active&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can set it several ways, and they all exist because the answer comes from a different place in each environment.&lt;/p&gt;

&lt;p&gt;In a properties file bundled with the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As an environment variable, which is how a deployment platform usually does it:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SPRING_PROFILES_ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or on the command line when you launch the jar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-jar&lt;/span&gt; orders.jar &lt;span class="nt"&gt;--spring&lt;/span&gt;.profiles.active&lt;span class="o"&gt;=&lt;/span&gt;prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three set the same underlying property. They differ only in &lt;em&gt;who&lt;/em&gt; gets to decide, and a more specific source wins: the command line overrides the environment variable, which overrides the file baked into the jar. That ordering is what lets an ops team point the app at a different database without rebuilding anything.&lt;/p&gt;

&lt;p&gt;You can also activate more than one profile at once, comma-separated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now three profiles are active together, and a bean tagged with &lt;em&gt;any&lt;/em&gt; of them joins the container. This is how you compose a full environment out of smaller, reusable slices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The property side: profile-specific files
&lt;/h2&gt;

&lt;p&gt;Beans are one half of the story. The other half is plain configuration values — a URL, a timeout, a log level. For these, Spring Boot gives profiles a naming convention.&lt;/p&gt;

&lt;p&gt;There is a base file, &lt;code&gt;application.properties&lt;/code&gt;, that always loads. Next to it you place one file per profile: &lt;code&gt;application-dev.properties&lt;/code&gt;, &lt;code&gt;application-prod.properties&lt;/code&gt;, and so on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;properties  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;everyone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Orders&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;properties  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;properties  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="nx"&gt;prod&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;WARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring loads the base file first, then the file for each active profile &lt;em&gt;on top&lt;/em&gt; of it. A key that appears in a profile file overrides the same key in the base. A key that appears only in the base is left untouched.&lt;/p&gt;

&lt;p&gt;So with &lt;code&gt;dev&lt;/code&gt; active, &lt;code&gt;app.name&lt;/code&gt; stays "Orders Service" from the base, &lt;code&gt;logging.level.root&lt;/code&gt; becomes &lt;code&gt;DEBUG&lt;/code&gt;, and &lt;code&gt;app.page-size&lt;/code&gt; becomes &lt;code&gt;5&lt;/code&gt; — the dev file won the last two. &lt;strong&gt;A profile file is an override layer, not a replacement.&lt;/strong&gt; You keep the shared defaults in one place and state only the differences per environment.&lt;/p&gt;

&lt;p&gt;(If you prefer YAML, the same idea lives in a single &lt;code&gt;application.yml&lt;/code&gt; split into sections with &lt;code&gt;---&lt;/code&gt;, each tagged by &lt;code&gt;spring.config.activate.on-profile&lt;/code&gt;. Same layering, one file.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The default profile
&lt;/h2&gt;

&lt;p&gt;What if nothing sets &lt;code&gt;spring.profiles.active&lt;/code&gt;? No named profile is active — but configuration still has to come from somewhere. Spring covers this with the &lt;strong&gt;default profile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Any bean or property file &lt;em&gt;not&lt;/em&gt; tagged with a profile belongs to the default and is always in play. On top of that, there is a fallback profile literally named &lt;code&gt;default&lt;/code&gt;, and it is active precisely when no other profile is. So &lt;code&gt;application-default.properties&lt;/code&gt; loads only when you launched with nothing set — a handy spot for sensible local values that a real environment overrides.&lt;/p&gt;

&lt;p&gt;This catches people out: a bean tagged &lt;code&gt;@Profile("default")&lt;/code&gt; disappears the moment you activate any other profile, even an unrelated one. "default" means "when nothing else," not "always."&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile expressions
&lt;/h2&gt;

&lt;p&gt;Beyond a single name, &lt;code&gt;@Profile&lt;/code&gt; understands a small expression language built from &lt;code&gt;!&lt;/code&gt; (not), &lt;code&gt;&amp;amp;&lt;/code&gt; (and), and &lt;code&gt;|&lt;/code&gt; (or).&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="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"!prod"&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;EmailSender&lt;/span&gt; &lt;span class="nf"&gt;loggingEmailSender&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// writes the "email" to the log instead of sending it&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LoggingEmailSender&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 &lt;code&gt;!prod&lt;/code&gt; means "every environment except production." Dev and staging get the harmless logging sender; production gets the real one, defined elsewhere with &lt;code&gt;@Profile("prod")&lt;/code&gt;. You can combine terms too: &lt;code&gt;@Profile("prod &amp;amp; cloud")&lt;/code&gt; needs both active, &lt;code&gt;@Profile("dev | test")&lt;/code&gt; needs either. This lets you express a real condition without inventing a brand-new profile name for every combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile groups
&lt;/h2&gt;

&lt;p&gt;Long comma-separated lists get repetitive and easy to get wrong. Spring Boot lets you name a bundle once, as a &lt;strong&gt;profile group&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;audit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now activating &lt;code&gt;prod&lt;/code&gt; automatically activates &lt;code&gt;metrics&lt;/code&gt;, &lt;code&gt;cloud&lt;/code&gt;, and &lt;code&gt;audit&lt;/code&gt; as well. One switch, four profiles. Your deployment config stays a single meaningful name — &lt;code&gt;spring.profiles.active=prod&lt;/code&gt; — while the composition it expands to lives in one readable line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profiles in tests
&lt;/h2&gt;

&lt;p&gt;A test run is just another environment, and it gets first-class support through &lt;code&gt;@ActiveProfiles&lt;/code&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;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@ActiveProfiles&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the container here is built with the "test" profile active&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container for this test is assembled with &lt;code&gt;test&lt;/code&gt; active, so &lt;code&gt;application-test.properties&lt;/code&gt; and any &lt;code&gt;@Profile("test")&lt;/code&gt; beans come into play — an in-memory database, a stubbed payment gateway — without touching how the app boots in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one trap worth internalising
&lt;/h2&gt;

&lt;p&gt;Profiles decide the &lt;em&gt;shape of the container at startup&lt;/em&gt;. That is the whole mechanism — and also its main limit.&lt;/p&gt;

&lt;p&gt;You cannot switch a profile per request, per user, or at runtime. By the time your code runs, the beans are already chosen and the profile decision is frozen. If you catch yourself wanting to "turn on the prod behaviour for just this one call," profiles are the wrong tool; that is a runtime flag, not a profile.&lt;/p&gt;

&lt;p&gt;There is a subtler trap as well. It is tempting to route every optional feature through &lt;code&gt;@Profile&lt;/code&gt;, but each profiled bean is a fork in your wiring that appears in only one environment — and therefore gets tested in only one. When the real question is "is this feature on or off," Spring Boot's &lt;strong&gt;&lt;code&gt;@ConditionalOnProperty&lt;/code&gt;&lt;/strong&gt; (a bean that appears based on a single property's value) is usually clearer, because the app boots the same way everywhere and simply reads a flag. Reserve profiles for genuine, environment-shaped differences — the database, the external services, the logging posture — and you keep their power without turning startup into a maze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A profile is a name. You attach that name to beans with &lt;code&gt;@Profile&lt;/code&gt;, or to configuration values by dropping them in an &lt;code&gt;application-&amp;lt;name&amp;gt;.properties&lt;/code&gt; file, and you switch it on with &lt;code&gt;spring.profiles.active&lt;/code&gt;. When the container is assembled at startup, Spring keeps the pieces whose name is active and skips the rest.&lt;/p&gt;

&lt;p&gt;That single idea — &lt;em&gt;choose the environment once, at boot&lt;/em&gt; — is what lets one identical build behave correctly on your laptop, in your tests, and in production, without a single &lt;code&gt;if (production)&lt;/code&gt; anywhere in your code.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
