<?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>Application events &amp; ApplicationEventPublisher</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:32:29 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/application-events-applicationeventpublisher-kff</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/application-events-applicationeventpublisher-kff</guid>
      <description>&lt;p&gt;Every application has moments where one thing happens and several other things need to react. A user signs up — and now you want to send a welcome email, create their starter workspace, and bump a signup counter. The signup itself is one job. The reactions are three more, and they have nothing to do with each other.&lt;/p&gt;

&lt;p&gt;The blunt way is to have the signup code call each of those directly. It works, but the signup code now has to know about email, workspaces, and metrics. Add a fourth reaction next quarter and you edit the signup code again.&lt;/p&gt;

&lt;p&gt;Spring offers a different shape. The signup code announces "a user was registered" and then forgets about it. Anything that cares listens for that announcement and reacts on its own. This is &lt;strong&gt;application events&lt;/strong&gt;, and it is built into the Spring container you already use.&lt;/p&gt;

&lt;p&gt;Let me first make sure the two words in that last sentence are real.&lt;/p&gt;

&lt;h2&gt;
  
  
  A one-paragraph recap of the container
&lt;/h2&gt;

&lt;p&gt;Spring keeps your objects for you. You hand it a class, it builds one instance, and it hands that instance to whatever else needs it. That managed instance is a &lt;strong&gt;bean&lt;/strong&gt;, and the thing holding all the beans is the &lt;strong&gt;container&lt;/strong&gt;. When you write a constructor that takes another object, the container looks up the matching bean and passes it in. Everything below rides on this: the event machinery is itself a bean the container hands you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Announcing that something happened
&lt;/h2&gt;

&lt;p&gt;To announce an event, you need the thing that broadcasts it. Spring exposes it as a bean called &lt;strong&gt;&lt;code&gt;ApplicationEventPublisher&lt;/code&gt;&lt;/strong&gt;. You ask for it the same way you ask for any bean — through the constructor.&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;class&lt;/span&gt; &lt;span class="nc"&gt;RegistrationService&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;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;RegistrationService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&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;publisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publisher&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;Now &lt;code&gt;RegistrationService&lt;/code&gt; can broadcast. But broadcast what? For a long time Spring required events to extend a base class, but since Spring 4.2 an event is just a plain object — any class you like. So the event is simply a small carrier of "what happened."&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;record&lt;/span&gt; &lt;span class="nf"&gt;UserRegistered&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;email&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 whole event: a record holding the new user's email. Publishing it is one line.&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;void&lt;/span&gt; &lt;span class="nf"&gt;register&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;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... save the user ...&lt;/span&gt;
    &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&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;UserRegistered&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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 is what just happened. &lt;code&gt;register&lt;/code&gt; did its own job — saving the user — and then handed a &lt;code&gt;UserRegistered&lt;/code&gt; object to the publisher. It did not call the email service. It does not know an email service exists. It said "this happened" and moved on. Which raises the obvious question: who hears it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Listening for the event
&lt;/h2&gt;

&lt;p&gt;A listener is any bean method marked with &lt;strong&gt;&lt;code&gt;@EventListener&lt;/code&gt;&lt;/strong&gt;, typed to the event it cares about.&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;WelcomeEmailListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@EventListener&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sending welcome email to "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&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;Spring reads the method's parameter type, &lt;code&gt;UserRegistered&lt;/code&gt;, and wires it up: whenever an event of that type is published, this method runs with the event passed in. Add a second listener for the same type and both run. The publisher's one line now fans out to as many reactions as you have, and it never learns their names.&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;SignupMetrics&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@EventListener&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// increment a counter&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 listeners, one event, zero coupling between them. To add analytics next quarter you write a third listener and touch nothing else. That is the whole point of the pattern — but the way Spring runs these listeners has consequences you need to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part everyone gets wrong: it is synchronous
&lt;/h2&gt;

&lt;p&gt;It is natural to picture the publisher tossing the event over a wall and carrying on while listeners work in the background. That is not what happens. By default, &lt;code&gt;publishEvent&lt;/code&gt; runs every listener &lt;strong&gt;right there, on the same thread, before it returns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So this line:&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;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&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;UserRegistered&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not finish until the welcome email has been sent and the metrics counter bumped. &lt;code&gt;register&lt;/code&gt; is blocked the entire time. The event system decouples &lt;em&gt;who knows about whom&lt;/em&gt;, not &lt;em&gt;when things run&lt;/em&gt;. It is still one straight-line call.&lt;/p&gt;

&lt;p&gt;Two consequences fall out of that, and both bite in production.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;speed&lt;/strong&gt;. If sending the email takes two seconds, &lt;code&gt;register&lt;/code&gt; takes two seconds longer. The user waits for work they do not care about.&lt;/p&gt;

&lt;p&gt;Second, and worse, &lt;strong&gt;failure&lt;/strong&gt;. Because it is all one thread, an exception thrown by a listener travels straight back up into &lt;code&gt;register&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;@EventListener&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mail server down"&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 exception is not caught for you. It propagates out of &lt;code&gt;publishEvent&lt;/code&gt;, out of &lt;code&gt;register&lt;/code&gt;, and — if &lt;code&gt;register&lt;/code&gt; was running inside a database transaction — it &lt;strong&gt;rolls the transaction back&lt;/strong&gt;. The user you just saved is un-saved because the welcome email failed. A reaction that was meant to be a harmless side effect has killed the main job.&lt;/p&gt;

&lt;p&gt;So the next question writes itself: how do we stop a listener from blocking or breaking the publisher?&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving the work off the thread with &lt;a class="mentioned-user" href="https://dev.to/async"&gt;@async&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If the problem is that the listener runs on the publisher's thread, the fix is to run it on a different one. Mark the listener &lt;strong&gt;&lt;code&gt;@Async&lt;/code&gt;&lt;/strong&gt; and Spring hands it to a background thread pool instead of running it inline.&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;WelcomeEmailListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Async&lt;/span&gt;
    &lt;span class="nd"&gt;@EventListener&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// now runs on a background thread&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;publishEvent&lt;/code&gt; now returns immediately; the email is sent later, elsewhere. The blocking is gone, and so is the failure path — an exception on the background thread cannot roll back the publisher's transaction, because it is not on the publisher's thread anymore.&lt;/p&gt;

&lt;p&gt;One catch: &lt;code&gt;@Async&lt;/code&gt; does nothing unless you switch it on. Somewhere in your configuration you need &lt;strong&gt;&lt;code&gt;@EnableAsync&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;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableAsync&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsyncConfig&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without it, &lt;code&gt;@Async&lt;/code&gt; is silently ignored and you are back to synchronous — a genuinely confusing bug, because the annotation is right there and looks active. But async trades one problem for a subtler one, which is worth seeing clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transaction trap
&lt;/h2&gt;

&lt;p&gt;Go back to the synchronous version for a moment, because it hides the nastiest gotcha of all. Picture the email being sent &lt;em&gt;inside&lt;/em&gt; the transaction:&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;@Transactional&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;register&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;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&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;UserRegistered&lt;/span&gt;&lt;span class="o"&gt;(&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;// listener runs, still inside the tx&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The listener runs before &lt;code&gt;register&lt;/code&gt; returns, which means it runs &lt;strong&gt;before the transaction commits&lt;/strong&gt;. The email goes out. Then, a line later, the transaction hits a constraint violation and rolls back. The user is gone from the database — but the welcome email is already in their inbox. You have sent mail about a user that does not exist.&lt;/p&gt;

&lt;p&gt;Async does not cleanly fix this either: the background thread might fire before or after the commit, and now you are racing.&lt;/p&gt;

&lt;p&gt;Spring's answer is a listener that waits for the transaction itself. Swap &lt;code&gt;@EventListener&lt;/code&gt; for &lt;strong&gt;&lt;code&gt;@TransactionalEventListener&lt;/code&gt;&lt;/strong&gt;, and it runs only at a chosen point in the transaction's lifecycle — by default, &lt;code&gt;AFTER_COMMIT&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;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WelcomeEmailListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@TransactionalEventListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TransactionPhase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AFTER_COMMIT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// runs only after the transaction has successfully committed&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;Now the ordering is guaranteed: the user is safely committed first, and only then does the email go out. If the transaction rolls back, the listener never runs at all. This is the correct home for any side effect that must not happen unless the main work actually committed. One thing to remember: if there is no active transaction when the event is published, an &lt;code&gt;AFTER_COMMIT&lt;/code&gt; listener has nothing to wait for and, by default, simply does not fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two smaller controls worth knowing
&lt;/h2&gt;

&lt;p&gt;Once you have several listeners on one event, two questions come up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order.&lt;/strong&gt; Listeners have no guaranteed order by default. If email must run before metrics, annotate them with &lt;strong&gt;&lt;code&gt;@Order&lt;/code&gt;&lt;/strong&gt; — the lower number runs first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Order&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="nd"&gt;@EventListener&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&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="nd"&gt;@Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@EventListener&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;recordMetric&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&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;Filtering.&lt;/strong&gt; Sometimes a listener only cares about &lt;em&gt;some&lt;/em&gt; events of a type. &lt;code&gt;@EventListener&lt;/code&gt; takes a &lt;strong&gt;&lt;code&gt;condition&lt;/code&gt;&lt;/strong&gt; written in Spring's expression language, and the method runs only when it evaluates to true. Here &lt;code&gt;#event&lt;/code&gt; refers to the published event.&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;@EventListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#event.email().endsWith('@vip.com')"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRegistered&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// runs only for VIP signups&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event is still published to everyone; this listener just opts out of the ones it does not want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this comes from for free
&lt;/h2&gt;

&lt;p&gt;You do not only publish your own events. Spring itself publishes events as your application starts and stops, using this exact mechanism. The most useful one is &lt;strong&gt;&lt;code&gt;ApplicationReadyEvent&lt;/code&gt;&lt;/strong&gt;, fired once the container is fully built and ready to serve traffic — the right place to run one-time startup work.&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;@EventListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApplicationReadyEvent&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;warmUp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// load caches, ping downstream services&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 is the same &lt;code&gt;@EventListener&lt;/code&gt; you already understand, framework events and your own events read identically. There is nothing new to learn — the container is just another publisher.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one mental model to keep
&lt;/h2&gt;

&lt;p&gt;Application events let one bean announce that something happened without knowing who reacts. A publisher broadcasts a plain object; any bean with a matching &lt;code&gt;@EventListener&lt;/code&gt; method receives it.&lt;/p&gt;

&lt;p&gt;The single fact that governs everything else: &lt;strong&gt;listeners run synchronously, on the publisher's thread, inside the publisher's transaction, unless you say otherwise.&lt;/strong&gt; From that one fact the rest follows — &lt;code&gt;@Async&lt;/code&gt; to move the work off the thread, &lt;code&gt;@EnableAsync&lt;/code&gt; to actually turn it on, and &lt;code&gt;@TransactionalEventListener(AFTER_COMMIT)&lt;/code&gt; to make a side effect wait for the commit that justifies it. Get that fact right, and the pattern is a clean way to keep your components from ever having to know about each other.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>SpEL basics</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:03:17 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/spel-basics-191b</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/spel-basics-191b</guid>
      <description>&lt;h2&gt;
  
  
  What SpEL actually is
&lt;/h2&gt;

&lt;p&gt;Spring lets you configure a lot of things with strings. You put a string in an annotation, and Spring turns it into a value at runtime. Most of the time that string is a plain constant. But sometimes you want the value to be &lt;em&gt;computed&lt;/em&gt; — read another bean, do a little math, pick between two options — and a plain constant can't do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SpEL&lt;/strong&gt;, the &lt;em&gt;Spring Expression Language&lt;/em&gt;, is Spring's answer. It's a tiny language you write &lt;em&gt;inside&lt;/em&gt; a string, and Spring evaluates it while it's wiring your application together. Think of it as a formula bar for your configuration: instead of a fixed value, you hand Spring a small expression, and Spring runs it to produce the value.&lt;/p&gt;

&lt;p&gt;You meet SpEL the first time you see a &lt;code&gt;#{ ... }&lt;/code&gt; inside an annotation. It shows up in &lt;code&gt;@Value&lt;/code&gt;, in caching conditions, in security rules, in &lt;code&gt;@ConditionalOnExpression&lt;/code&gt;. This article builds it up from the simplest possible expression to the parts that quietly bite people in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two dollar-hash cousins
&lt;/h2&gt;

&lt;p&gt;Before any SpEL, we have to clear up the single most common confusion, because the two look almost identical.&lt;/p&gt;

&lt;p&gt;You have probably written this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${server.port}"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;${ ... }&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; SpEL. It's a &lt;strong&gt;property placeholder&lt;/strong&gt; — Spring looks up the key &lt;code&gt;server.port&lt;/code&gt; in your properties (from &lt;code&gt;application.properties&lt;/code&gt;, environment variables, and so on) and drops the value in. It only does key lookups. It can't do math or call methods.&lt;/p&gt;

&lt;p&gt;SpEL uses a &lt;strong&gt;hash&lt;/strong&gt;, not a dollar:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{2 * 60 * 1000}"&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;long&lt;/span&gt; &lt;span class="n"&gt;cacheMillis&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 120000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;#{ ... }&lt;/code&gt; says "evaluate this as an expression." So the rule of thumb: &lt;strong&gt;&lt;code&gt;${}&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;looks something up,&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;#{}&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;computes something.&lt;/strong&gt; They can even work together, which we'll get to — but keep them separate in your head for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first expressions: literals and math
&lt;/h2&gt;

&lt;p&gt;The cleanest way to learn SpEL is to see what goes between the &lt;code&gt;#{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt;. At its simplest, an expression is just a literal 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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{true}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// boolean&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{'Freddie'}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// string, single-quoted&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{3.14159}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// double&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strings inside SpEL use &lt;strong&gt;single quotes&lt;/strong&gt;, because the whole expression already lives inside the double quotes of the Java annotation. Once you have literals, you get operators for free:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{100 * 1024}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;// 102400  — arithmetic&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{'Spring'.length()}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// 6        — method call on a string&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{'a,b,c'.split(',')}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ["a","b","c"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the key idea: &lt;strong&gt;inside an expression you can call any method on any value&lt;/strong&gt;, exactly as you would in Java. SpEL isn't a cut-down mini-language for constants — it can genuinely &lt;em&gt;run&lt;/em&gt; things. Which immediately raises the question: run things on &lt;em&gt;what&lt;/em&gt;? So far we've only touched literals. The real power starts when an expression can reach the objects Spring already manages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reaching into other beans
&lt;/h2&gt;

&lt;p&gt;Spring keeps every object it manages in a container, and each one has a name — its &lt;strong&gt;bean name&lt;/strong&gt;. SpEL can name a bean directly and pull a value out of it.&lt;/p&gt;

&lt;p&gt;Say you have a bean that holds some tuning numbers:&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="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tuning"&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;Tuning&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;int&lt;/span&gt; &lt;span class="nf"&gt;getPoolSize&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="mi"&gt;8&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;Another bean can borrow that value through SpEL:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{tuning.poolSize}"&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;poolSize&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that expression left to right: &lt;code&gt;tuning&lt;/code&gt; is the bean, and &lt;code&gt;.poolSize&lt;/code&gt; calls its getter. &lt;strong&gt;SpEL turns&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;.poolSize&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;into a call to&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;getPoolSize()&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;for you&lt;/strong&gt; — it follows the JavaBean property convention, so you write the short property name and Spring finds the getter.&lt;/p&gt;

&lt;p&gt;You can go further and call methods with arguments, chain calls, and reach built-in objects Spring exposes, like system properties:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{systemProperties['user.timezone']}"&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;tz&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;systemProperties&lt;/code&gt; is a &lt;code&gt;Map&lt;/code&gt; Spring makes available, and &lt;code&gt;['user.timezone']&lt;/code&gt; indexes into it — the same bracket syntax works for lists and arrays too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing properties and expressions
&lt;/h2&gt;

&lt;p&gt;Now we can bring the two cousins back together. Spring resolves the property placeholder &lt;code&gt;${}&lt;/code&gt; &lt;strong&gt;first&lt;/strong&gt;, before it hands the string to SpEL. That ordering lets you nest one inside the other: look a value up, then compute with 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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{'${app.admins}'.split(',')}"&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="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;admins&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk the order carefully. First &lt;code&gt;${app.admins}&lt;/code&gt; is replaced with, say, the string &lt;code&gt;alice,bob&lt;/code&gt;. That leaves SpEL looking at &lt;code&gt;#{'alice,bob'.split(',')}&lt;/code&gt;, which it evaluates into a two-element array. &lt;strong&gt;The property gives you the raw text; SpEL reshapes it.&lt;/strong&gt; This "look it up, then transform it" pattern is most of what people use SpEL for in day-to-day config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operators that make config decisions
&lt;/h2&gt;

&lt;p&gt;SpEL really earns its place when a value has to &lt;em&gt;depend&lt;/em&gt; on something. It has the operators you'd expect, plus three shorthands worth knowing by name.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ternary&lt;/strong&gt; operator picks between two values based on a condition:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{tuning.poolSize &amp;gt; 4 ? 'large' : 'small'}"&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;profile&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;Elvis operator&lt;/strong&gt;, &lt;code&gt;?:&lt;/code&gt;, is a shorter ternary for one specific job: "use this, or a fallback if it's null." It's named for the way &lt;code&gt;?:&lt;/code&gt; looks like Elvis's hair and eyes.&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{systemProperties['region'] ?: 'us-east'}"&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;region&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// the property, or 'us-east' if it's absent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;safe-navigation&lt;/strong&gt; operator, &lt;code&gt;?.&lt;/code&gt;, stops a null from blowing up a chain. If the left side is null, the whole expression is null instead of throwing:&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;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{tuning?.poolSize}"&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;Integer&lt;/span&gt; &lt;span class="n"&gt;poolSize&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// null if 'tuning' were null, no exception&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together these three let a single line of config express "compute a value, fall back gracefully, and don't crash on a missing piece" — logic you'd otherwise write in Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering collections without a loop
&lt;/h2&gt;

&lt;p&gt;This is the part of SpEL that feels like a superpower the first time you see it. Given a collection, SpEL can filter and transform it inline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selection&lt;/strong&gt; filters a collection with &lt;code&gt;.?[ ... ]&lt;/code&gt;, where inside the brackets each element is available as &lt;code&gt;#this&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="c1"&gt;// keep only the servers whose port is above 8000&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{servers.?[port &amp;gt; 8000]}"&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;highPorts&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;Projection&lt;/strong&gt; transforms each element with &lt;code&gt;.![ ... ]&lt;/code&gt;, pulling one piece out of every item:&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;// turn a list of Server objects into a list of their names&lt;/span&gt;
&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{servers.![name]}"&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;serverNames&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;.?[]&lt;/code&gt; as "select where" and &lt;code&gt;.![]&lt;/code&gt; as "map to." In two short expressions you've done a filter and a map that would each be a loop or a stream pipeline in Java. There are cousins too — &lt;code&gt;.^[]&lt;/code&gt; grabs the first match and &lt;code&gt;.$[]&lt;/code&gt; the last — but selection and projection are the two you'll reach for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running SpEL yourself
&lt;/h2&gt;

&lt;p&gt;Everything so far happened inside annotations, where Spring runs the expression for you. But SpEL is a normal library, and you can drive it directly — useful when &lt;em&gt;your own code&lt;/em&gt; needs to evaluate a formula, perhaps one a user or a config file supplied.&lt;/p&gt;

&lt;p&gt;The entry point is an &lt;strong&gt;&lt;code&gt;ExpressionParser&lt;/code&gt;&lt;/strong&gt;. You parse a string into an &lt;code&gt;Expression&lt;/code&gt;, then ask for its 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="nc"&gt;ExpressionParser&lt;/span&gt; &lt;span class="n"&gt;parser&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;SpelExpressionParser&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Expression&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseExpression&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"'Spring'.length() * 2"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// 12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Often you want the expression to run &lt;em&gt;against&lt;/em&gt; some object — evaluate it "in the context of" a particular target. You pass that target as the &lt;strong&gt;root object&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="nc"&gt;Tuning&lt;/span&gt; &lt;span class="n"&gt;tuning&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;Tuning&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Expression&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseExpression&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"poolSize &amp;gt; 4"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tuning&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// properties resolve against 'tuning'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;poolSize&lt;/code&gt; in the expression resolves against the &lt;code&gt;tuning&lt;/code&gt; object you handed in. That idea — &lt;em&gt;what the expression is allowed to see&lt;/em&gt; — is not just a convenience. It's the security boundary, and it's the last and most important thing to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context is the security boundary
&lt;/h2&gt;

&lt;p&gt;When SpEL evaluates an expression, it does so inside an &lt;strong&gt;&lt;code&gt;EvaluationContext&lt;/code&gt;&lt;/strong&gt; — the object that decides what the expression can reach: which variables, which beans, whether it can even name Java types. There are two you'll encounter, and the difference matters enormously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;StandardEvaluationContext&lt;/code&gt;&lt;/strong&gt; is the powerful one, and the default when Spring evaluates internally. It can do &lt;em&gt;everything&lt;/em&gt; SpEL allows — including naming any class and calling any static method through the &lt;code&gt;T()&lt;/code&gt; type operator:&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;// with a StandardEvaluationContext, this runs:&lt;/span&gt;
&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&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 not a hypothetical. If you ever pass a &lt;strong&gt;user-supplied string&lt;/strong&gt; into a &lt;code&gt;StandardEvaluationContext&lt;/code&gt;, you have handed the user the ability to run arbitrary code inside your JVM. This exact mistake — &lt;strong&gt;SpEL injection&lt;/strong&gt; — is behind a string of real, severe Spring vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SimpleEvaluationContext&lt;/code&gt;&lt;/strong&gt; exists precisely for that danger. It's a deliberately restricted context: it allows property access and simple operators but &lt;strong&gt;forbids type references and arbitrary method resolution&lt;/strong&gt;. When an expression comes from anywhere you don't fully trust, this is the one to use:&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;EvaluationContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleEvaluationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forReadOnlyDataBinding&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Expression&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseExpression&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userSuppliedString&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="n"&gt;exp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// can read properties, cannot reach Runtime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is simple and worth burning in: &lt;strong&gt;your own trusted expressions can use the standard context; anything a user can influence must use&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SimpleEvaluationContext&lt;/code&gt;&lt;/strong&gt;&lt;strong&gt;.&lt;/strong&gt; SpEL is powerful because it can run real code — which means the moment untrusted input reaches it, that power is the whole problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you'll meet it again
&lt;/h2&gt;

&lt;p&gt;You now have the shape of SpEL end to end: a &lt;code&gt;#{}&lt;/code&gt; string Spring evaluates at runtime, distinct from a &lt;code&gt;${}&lt;/code&gt; property lookup, able to do math, call methods, reach into beans, choose values with ternary and Elvis, filter collections with selection and projection, and — when you drive it yourself — run inside a context that is either fully powered or safely fenced.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;#{}&lt;/code&gt; you learned here reappears across Spring: in &lt;code&gt;@Cacheable(condition = "...")&lt;/code&gt; to decide when to cache, in &lt;code&gt;@PreAuthorize("hasRole('ADMIN')")&lt;/code&gt; for security rules, and in &lt;code&gt;@ConditionalOnExpression&lt;/code&gt; to switch beans on and off. They're all the same language you just built up — so wherever you see those hash-braces next, you already know how to read them.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Transaction isolation levels — what Spring controls</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sun, 30 Aug 2026 10:03:24 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-isolation-levels-what-spring-controls-2nn9</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-isolation-levels-what-spring-controls-2nn9</guid>
      <description>&lt;p&gt;Picture a bank account row in your database. Two requests land at the same instant — one reads the balance to show it on a screen, the other is halfway through moving money out of it. What should the first request see? The old balance? The new one? A half-finished number that never really existed?&lt;/p&gt;

&lt;p&gt;That question is what a &lt;strong&gt;transaction isolation level&lt;/strong&gt; answers. It is a dial that decides how much one in-flight transaction is allowed to see of another in-flight transaction that touches the same data. You meet it the moment your app has more than one user, because then more than one transaction runs at once, and they start stepping on each other.&lt;/p&gt;

&lt;p&gt;Spring gives you a single, small lever over this dial. This article builds up what the dial actually does, then shows exactly what Spring controls — and, just as important, what it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what a transaction guarantees on its own
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;transaction&lt;/strong&gt; is a group of database operations that either all take effect or none do. Inside one transaction, you can read a row, change it, read it again, and the outside world sees nothing until you commit.&lt;/p&gt;

&lt;p&gt;That is a clean story when transactions run one after another. The trouble is that a real server runs many at once, and they overlap in time. While your transaction is open, other transactions are opening, writing, and committing against the very same rows.&lt;/p&gt;

&lt;p&gt;Isolation is the rulebook for that overlap. So before we can talk about levels, we need to see the specific ways that overlap can go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three ways concurrent reads go wrong
&lt;/h2&gt;

&lt;p&gt;There are three classic anomalies. They are worth naming carefully, because the isolation levels are defined entirely in terms of which of these they forbid.&lt;/p&gt;

&lt;p&gt;The first is a &lt;strong&gt;dirty read&lt;/strong&gt;. Your transaction reads a row that another transaction has changed but not yet committed. If that other transaction then rolls back, you have read a value that never officially existed.&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;// Transaction B writes but has NOT committed:&lt;/span&gt;
&lt;span class="no"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="no"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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="c1"&gt;// was 100&lt;/span&gt;

&lt;span class="c1"&gt;// Transaction A reads right now and sees 500.&lt;/span&gt;
&lt;span class="c1"&gt;// Then B rolls back. A acted on a number that was never real.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The read was "dirty" because it saw uncommitted work. That is the most dangerous anomaly, and most systems forbid it by default.&lt;/p&gt;

&lt;p&gt;The second is a &lt;strong&gt;non-repeatable read&lt;/strong&gt;. Your transaction reads the same row twice and gets two different values, because another transaction committed a change in between.&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;// Transaction A:&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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="c1"&gt;// reads 100&lt;/span&gt;

&lt;span class="c1"&gt;// Transaction B commits: balance is now 500.&lt;/span&gt;

&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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="c1"&gt;// reads 500 — same query, new answer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here is dirty; both values were committed. The problem is that A cannot trust a value to hold still for the length of its own transaction.&lt;/p&gt;

&lt;p&gt;The third is a &lt;strong&gt;phantom read&lt;/strong&gt;. This time you re-run a query that matches a &lt;em&gt;set&lt;/em&gt; of rows, and the set changes size because another transaction inserted or deleted a matching row.&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;// Transaction A:&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="o"&gt;(*)&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 3 rows&lt;/span&gt;

&lt;span class="c1"&gt;// Transaction B inserts a new row with balance = 900 and commits.&lt;/span&gt;

&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="o"&gt;(*)&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 4 rows — a "phantom" appeared&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A non-repeatable read is about a row you already saw changing value. A phantom read is about new rows appearing (or vanishing) in a range you already queried. Keep that distinction in mind — the levels treat them separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four isolation levels
&lt;/h2&gt;

&lt;p&gt;Now the payoff. Each isolation level is just a promise about which of those three anomalies cannot happen. They stack: each stronger level forbids everything the weaker ones forbid, and one more.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt; — allows all three. You can even see uncommitted (dirty) data. Fastest, and almost never what you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;READ COMMITTED&lt;/strong&gt; — forbids dirty reads. You only ever see committed data, but a row can still change between two reads. This is the default in PostgreSQL, Oracle, and SQL Server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REPEATABLE READ&lt;/strong&gt; — also forbids non-repeatable reads. A row you have read will keep its value for the rest of your transaction. This is MySQL's default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SERIALIZABLE&lt;/strong&gt; — forbids all three, phantoms included. The database behaves as if transactions ran one at a time, in some order. Safest, and slowest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is a trade. As you climb, you rule out more surprises, but the database has to do more locking or more bookkeeping, so concurrency drops. Higher isolation buys correctness with throughput.&lt;/p&gt;

&lt;p&gt;That is the whole concept. Everything Spring does sits on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Spring enters: one annotation attribute
&lt;/h2&gt;

&lt;p&gt;You mark a method transactional with &lt;strong&gt;&lt;code&gt;@Transactional&lt;/code&gt;&lt;/strong&gt;. That is the annotation that tells Spring "wrap this method in a database transaction — begin before it runs, commit if it returns, roll back if it throws."&lt;/p&gt;

&lt;p&gt;The isolation level is one attribute on that annotation:&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Isolation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REPEATABLE_READ&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;transfer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&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 SQL statement in here runs at REPEATABLE_READ&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;Isolation&lt;/code&gt; is a Spring enum, and it mirrors the levels above one for one: &lt;code&gt;READ_UNCOMMITTED&lt;/code&gt;, &lt;code&gt;READ_COMMITTED&lt;/code&gt;, &lt;code&gt;REPEATABLE_READ&lt;/code&gt;, &lt;code&gt;SERIALIZABLE&lt;/code&gt;, plus one more we will get to — &lt;code&gt;DEFAULT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So from the developer's seat, choosing an isolation level in Spring is exactly this: pick a value for one attribute. That is the entire surface area. Which raises the real question — what does Spring &lt;em&gt;do&lt;/em&gt; with it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring sets the dial; the database does the work
&lt;/h2&gt;

&lt;p&gt;Here is the part that surprises people. Spring does not implement isolation. It does not prevent a single dirty read on its own. All it does is pass your choice down to the database.&lt;/p&gt;

&lt;p&gt;Under the hood, a Spring transaction runs on a JDBC &lt;strong&gt;&lt;code&gt;Connection&lt;/code&gt;&lt;/strong&gt; — the object that represents your app's open line to the database. That connection has a method for exactly this purpose:&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;// Roughly what Spring's transaction manager does when it opens the transaction:&lt;/span&gt;
&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTransactionIsolation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRANSACTION_REPEATABLE_READ&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 JDBC call is the whole mechanism. Spring translates your &lt;code&gt;Isolation.REPEATABLE_READ&lt;/code&gt; into the matching JDBC constant and sets it on the connection before your method's SQL runs. From there, enforcing the level is entirely the database's job.&lt;/p&gt;

&lt;p&gt;This is why the framing "what Spring controls" matters. Spring controls the &lt;em&gt;request&lt;/em&gt;. It picks up your annotation, opens a connection, and dials the level in. The actual guarantees — what a dirty read or a phantom means in practice — come from the database engine, not from Spring.&lt;/p&gt;

&lt;p&gt;When your transaction finishes, Spring is careful to set the connection's isolation back to what it was before, because that connection goes back into a pool and will be reused by the next transaction. You do not have to manage that reset; the transaction manager does it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolation.DEFAULT, and why it is the sensible default
&lt;/h2&gt;

&lt;p&gt;The extra enum value, &lt;strong&gt;&lt;code&gt;Isolation.DEFAULT&lt;/code&gt;&lt;/strong&gt;, means "do not call &lt;code&gt;setTransactionIsolation&lt;/code&gt; at all — just use whatever the database is already configured to use."&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;@Transactional&lt;/span&gt;   &lt;span class="c1"&gt;// isolation defaults to Isolation.DEFAULT&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="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;If you write &lt;code&gt;@Transactional&lt;/code&gt; with no isolation attribute, you get &lt;code&gt;DEFAULT&lt;/code&gt;. So your transaction runs at the database's own default level — READ COMMITTED on Postgres, REPEATABLE READ on MySQL. This is the right choice the vast majority of the time. You only reach for an explicit level when a specific piece of logic genuinely needs a stronger guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha: isolation is ignored when you join an existing transaction
&lt;/h2&gt;

&lt;p&gt;This is the trap the mechanism creates, and it catches people who assume the annotation always wins.&lt;/p&gt;

&lt;p&gt;Spring transactions can nest by &lt;em&gt;participating&lt;/em&gt;. With the default propagation, if a transactional method calls another transactional method, the inner one does &lt;strong&gt;not&lt;/strong&gt; start a new transaction — it joins the one already running. And a transaction's isolation level is fixed the moment it begins, on that one connection. You cannot change it midway.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Isolation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;READ_COMMITTED&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;outer&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// joins outer's transaction&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Isolation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SERIALIZABLE&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;inner&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// runs at READ_COMMITTED — outer's level — NOT serializable&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;inner()&lt;/code&gt; joins &lt;code&gt;outer()&lt;/code&gt;'s existing transaction, its &lt;code&gt;SERIALIZABLE&lt;/code&gt; request is quietly ignored. The transaction is already open at READ COMMITTED, and that is what &lt;code&gt;inner()&lt;/code&gt; gets. The annotation reads like a firm instruction, but on a participating call it is more of a wish.&lt;/p&gt;

&lt;p&gt;Spring can be told to shout instead of ignore. If you set the transaction manager's &lt;code&gt;validateExistingTransaction&lt;/code&gt; flag on, Spring will throw an exception when an inner method asks for an isolation level that conflicts with the transaction it is joining, rather than silently running at the outer level. By default that flag is off, so the silent case above is what you get out of the box.&lt;/p&gt;

&lt;p&gt;The clean way to actually get a different isolation level is to make the inner method start its own transaction — propagation &lt;code&gt;REQUIRES_NEW&lt;/code&gt; — which opens a fresh connection that Spring can dial independently. But that is a separate, heavier decision, because a brand-new transaction commits on its own and no longer shares the outer one's fate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Each database reads the levels its own way
&lt;/h2&gt;

&lt;p&gt;One more thing Spring does not smooth over: the four level names are a SQL standard, but databases implement them with real differences.&lt;/p&gt;

&lt;p&gt;The sharpest example is REPEATABLE READ. The standard only requires it to stop non-repeatable reads, leaving phantoms allowed. But MySQL's InnoDB engine, at REPEATABLE READ, also blocks most phantom reads through its snapshot mechanism. PostgreSQL does not even offer a distinct REPEATABLE READ that permits phantoms — its REPEATABLE READ is a full snapshot that rules them out too.&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Isolation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REPEATABLE_READ&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;BigDecimal&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// On MySQL and Postgres this behaves more strictly than&lt;/span&gt;
    &lt;span class="c1"&gt;// the SQL standard's bare minimum. On another engine it may not.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson is not to memorize every engine. It is to remember where the boundary sits: &lt;strong&gt;the same isolation value can mean different things on different databases&lt;/strong&gt;, because Spring only forwards the name — the behavior belongs to the engine. When a level matters enough to set explicitly, check what your specific database promises for it.&lt;/p&gt;

&lt;p&gt;There is also a plainer failure mode. Some databases or drivers do not support every level. If you ask for one the database cannot honor, the &lt;code&gt;setTransactionIsolation&lt;/code&gt; call fails, and your transaction fails to start. Spring surfaces that as an exception rather than silently downgrading, which is the safe behavior — you would rather know than believe you have a guarantee you do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually reach for
&lt;/h2&gt;

&lt;p&gt;Put the pieces together and the practical picture is simple.&lt;/p&gt;

&lt;p&gt;Most of the time, leave isolation alone. &lt;code&gt;@Transactional&lt;/code&gt; with &lt;code&gt;Isolation.DEFAULT&lt;/code&gt; runs at your database's default — usually READ COMMITTED — and that is a sound choice for ordinary create-read-update work.&lt;/p&gt;

&lt;p&gt;Raise the level only for a specific transaction that genuinely needs a steadier view of the data: a multi-step calculation that must not see rows shift underneath it, or money-movement logic where a phantom row would corrupt a total. And when you do raise it, remember the two edges — it is silently ignored on a participating call, and its exact meaning depends on the database underneath.&lt;/p&gt;

&lt;p&gt;Spring's role in all of this is narrow and worth stating plainly: it reads one annotation attribute, sets one value on one JDBC connection at the right moment, and restores it afterward. The isolation guarantees themselves live in the database. Spring just makes sure the right request reaches it.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Transaction propagation (REQUIRED / REQUIRES_NEW / NESTED ...) (Part 2 of 2)</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sat, 29 Aug 2026 11:17:44 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-propagation-required-requiresnew-nested-part-2-of-2-41fj</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-propagation-required-requiresnew-nested-part-2-of-2-41fj</guid>
      <description>&lt;h2&gt;
  
  
  Picking up between "join" and "start fresh"
&lt;/h2&gt;

&lt;p&gt;Part 1 left you with two propagation modes and a clean way to think about them. &lt;strong&gt;REQUIRED&lt;/strong&gt; joins whatever transaction is already running, so everything shares one all-or-nothing boundary. &lt;strong&gt;REQUIRES_NEW&lt;/strong&gt; ignores the outer transaction, suspends it, and runs a completely separate one that commits or fails on its own.&lt;/p&gt;

&lt;p&gt;Those two are opposites. Join everything, or share nothing. But real code sometimes wants something in between: &lt;em&gt;let the inner work fail and be undone on its own, yet still tie its success to the outer transaction's success.&lt;/em&gt; Neither mode gives you that. REQUIRED can't undo just the inner part; REQUIRES_NEW commits the inner part too early.&lt;/p&gt;

&lt;p&gt;The mode that fills that gap is &lt;strong&gt;NESTED&lt;/strong&gt;, and it works with a database feature called a savepoint. So before we can name NESTED, we have to build a savepoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a savepoint is
&lt;/h2&gt;

&lt;p&gt;Picture a single transaction running along, making changes. A &lt;strong&gt;savepoint&lt;/strong&gt; is a marker you drop partway through it. It says: &lt;em&gt;remember exactly what the world looked like at this spot.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Later, if something goes wrong, you have a new option. Instead of rolling back the whole transaction, you can &lt;strong&gt;roll back to the savepoint&lt;/strong&gt;. That undoes every change made &lt;em&gt;after&lt;/em&gt; the marker, keeps every change made &lt;em&gt;before&lt;/em&gt; it, and — this is the important part — the transaction is still alive and can carry on.&lt;/p&gt;

&lt;p&gt;So a savepoint is a partial undo button inside one transaction. Not the end of the transaction, just a rewind to a point you chose.&lt;/p&gt;

&lt;p&gt;Hold that picture, because NESTED is nothing more than Spring dropping a savepoint for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  NESTED — a savepoint around the inner method
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NESTED&lt;/strong&gt; means: if a transaction is already running, don't start a new one — instead drop a savepoint, run the inner method, and if it fails, roll back only to that savepoint. If no transaction is running yet, NESTED simply behaves like REQUIRED and starts a normal one.&lt;/p&gt;

&lt;p&gt;You set it the usual way:&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NESTED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;writeAuditLog&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="n"&gt;auditRepo&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuditEntry&lt;/span&gt;&lt;span class="o"&gt;(&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now walk the layered call again. &lt;code&gt;placeOrder&lt;/code&gt; opens a transaction. It calls &lt;code&gt;writeAuditLog&lt;/code&gt;, whose proxy sees a transaction already open and, because the mode is NESTED, drops a savepoint before running the body:&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;@Transactional&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="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;writeAuditLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// NESTED — runs after a savepoint&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// inner failed; only its work is undone&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// outer transaction is still healthy and commits here&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what makes NESTED different from the REQUIRED trap in Part 1. When &lt;code&gt;writeAuditLog&lt;/code&gt; throws, Spring rolls the transaction back &lt;strong&gt;to the savepoint&lt;/strong&gt; — undoing the audit write and nothing else. The transaction is &lt;em&gt;not&lt;/em&gt; marked rollback-only. So when you catch the exception and let &lt;code&gt;placeOrder&lt;/code&gt; continue, its commit goes through cleanly. The inner failure was contained; the outer work survived.&lt;/p&gt;

&lt;p&gt;That is the in-between behaviour we wanted: the inner method can fail and be cleanly undone, without poisoning the whole transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NESTED is not REQUIRES_NEW
&lt;/h2&gt;

&lt;p&gt;They sound similar — both let the inner part fail alone — so it is worth being exact about the difference, because it changes what you can rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is still only one physical transaction.&lt;/strong&gt; The savepoint lives inside it. That has one consequence that matters most: the inner work is &lt;strong&gt;not committed early&lt;/strong&gt;. It becomes permanent only when the outer transaction finally commits. Roll the outer one back, and the nested work goes with it — savepoint or no savepoint.&lt;/p&gt;

&lt;p&gt;Compare that to REQUIRES_NEW, where the inner transaction commits immediately and independently, and survives even if the outer one later rolls back.&lt;/p&gt;

&lt;p&gt;So the rule of thumb is about &lt;em&gt;durability on outer failure&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inner work must &lt;strong&gt;survive&lt;/strong&gt; the outer rolling back → &lt;strong&gt;REQUIRES_NEW&lt;/strong&gt; (audit trails, attempt counters).&lt;/li&gt;
&lt;li&gt;Inner work should be &lt;strong&gt;undoable on its own&lt;/strong&gt; but still tied to the outer commit → &lt;strong&gt;NESTED&lt;/strong&gt; (a sub-step you might retry or skip, but which is meaningless if the whole operation is abandoned).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a plumbing catch worth knowing. Savepoints are a JDBC feature, so NESTED only works on a transaction manager that speaks straight to JDBC — Spring's &lt;code&gt;DataSourceTransactionManager&lt;/code&gt;. The JPA manager (&lt;code&gt;JpaTransactionManager&lt;/code&gt;), which most Hibernate apps use, does not support NESTED and will throw when you ask for it. It is the least portable of the modes for exactly this reason, which is why you meet it less often than REQUIRED and REQUIRES_NEW.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four modes you meet less often
&lt;/h2&gt;

&lt;p&gt;That covers the three modes that carry almost all real code. The remaining four are quicker to describe, because each is just a rule about whether a transaction &lt;em&gt;must&lt;/em&gt;, &lt;em&gt;may&lt;/em&gt;, or &lt;em&gt;must not&lt;/em&gt; already exist. Meeting them completes the set of seven.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SUPPORTS&lt;/strong&gt; — join a transaction if one is running, otherwise run with no transaction at all. It does not insist on either. You reach for it on a read that is fine standing alone but should ride along inside a transaction when one happens to be open.&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SUPPORTS&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;Order&lt;/span&gt; &lt;span class="nf"&gt;findOrder&lt;/span&gt;&lt;span class="o"&gt;(&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;orderRepo&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="c1"&gt;// transactional only if a caller already had one&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;NOT_SUPPORTED&lt;/strong&gt; — the opposite instinct. Suspend any transaction that is running and execute with none, then resume it afterwards. Use it for work that should not sit inside a transaction — a slow report or a long external call you do not want holding a database connection and locks open the whole time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MANDATORY&lt;/strong&gt; — run in the caller's transaction, and if there isn't one, throw. It never starts a transaction itself. This is a guard: a method that is only ever meant to be a step inside a larger unit of work, and should refuse to run if someone calls it standalone.&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MANDATORY&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;applyLoyaltyPoints&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;// throws IllegalTransactionStateException if no transaction is already open&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;NEVER&lt;/strong&gt; — the mirror of MANDATORY. Run only if there is no transaction, and throw if one is open. It is a rare, defensive choice for code that must not be allowed to run inside a transaction under any circumstances.&lt;/p&gt;

&lt;p&gt;Line them up and the pattern is clear. REQUIRED, REQUIRES_NEW, and NESTED each &lt;em&gt;make&lt;/em&gt; a transaction happen in some form. SUPPORTS and NOT_SUPPORTED are relaxed — they take whatever is there. MANDATORY and NEVER are strict guards that throw when the world isn't the way they demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  When does a transaction actually roll back?
&lt;/h2&gt;

&lt;p&gt;One question is still open, and it surprises people as often as the propagation traps do. A transaction rolls back when an exception escapes the method — but &lt;em&gt;which&lt;/em&gt; exceptions? The answer is not "all of them," and the default is easy to get wrong.&lt;/p&gt;

&lt;p&gt;Recall from Java that exceptions come in two families. &lt;strong&gt;Unchecked&lt;/strong&gt; exceptions extend &lt;code&gt;RuntimeException&lt;/code&gt; (or &lt;code&gt;Error&lt;/code&gt;) — the compiler does not force you to declare or catch them. &lt;strong&gt;Checked&lt;/strong&gt; exceptions are everything else — the ones you must declare with &lt;code&gt;throws&lt;/code&gt; or handle.&lt;/p&gt;

&lt;p&gt;Spring's default rollback rule splits exactly along that line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;unchecked&lt;/strong&gt; exception (&lt;code&gt;RuntimeException&lt;/code&gt; or &lt;code&gt;Error&lt;/code&gt;) that escapes the method → &lt;strong&gt;rollback&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;checked&lt;/strong&gt; exception that escapes the method → &lt;strong&gt;commit anyway&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second line is the trap. Read it slowly, because it is the opposite of what most people expect:&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;@Transactional&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="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;PaymentException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// throws PaymentException (a checked exception)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;PaymentException&lt;/code&gt; is a checked exception, Spring &lt;strong&gt;commits the order anyway&lt;/strong&gt;. The save sticks even though payment failed and the method exited by throwing. Nothing about "an exception was thrown" saved you here — the &lt;em&gt;kind&lt;/em&gt; of exception decided it, and a checked one does not trigger rollback by default.&lt;/p&gt;

&lt;p&gt;This default is a historical inheritance from the old EJB world, where the same checked-versus-unchecked convention held. It rarely matches what a modern app wants. Fortunately you override it in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling Spring what to roll back on
&lt;/h2&gt;

&lt;p&gt;The override lives on the annotation. &lt;code&gt;rollbackFor&lt;/code&gt; adds exception types that &lt;em&gt;should&lt;/em&gt; roll back even though they normally wouldn't; &lt;code&gt;noRollbackFor&lt;/code&gt; does the reverse.&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollbackFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PaymentException&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="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="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;PaymentException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// now a thrown PaymentException rolls the save back&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;rollbackFor&lt;/code&gt; in place, the checked &lt;code&gt;PaymentException&lt;/code&gt; triggers a rollback like you wanted, and the order no longer sticks after a failed payment. The mirror case is rarer but real — a checked exception you throw for control flow, that you specifically do &lt;em&gt;not&lt;/em&gt; want to undo the work:&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noRollbackFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderAlreadyConfirmedException&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;confirmOrder&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="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;OrderAlreadyConfirmedException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this exception signals "nothing to do," so keep whatever was written&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The habit worth forming: whenever a &lt;code&gt;@Transactional&lt;/code&gt; method can throw a &lt;strong&gt;checked&lt;/strong&gt; exception that ought to undo its writes, name it in &lt;code&gt;rollbackFor&lt;/code&gt;. Leave it out and the default quietly commits work you meant to discard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole picture, in order
&lt;/h2&gt;

&lt;p&gt;Propagation is a per-method answer to one question — &lt;em&gt;when this method runs, what should happen to the transaction around it?&lt;/em&gt; The seven modes are just the seven honest answers. &lt;strong&gt;REQUIRED&lt;/strong&gt; joins one. &lt;strong&gt;REQUIRES_NEW&lt;/strong&gt; suspends the outer and runs a fully independent one. &lt;strong&gt;NESTED&lt;/strong&gt; stays inside the outer transaction but drops a savepoint so the inner step can be undone alone — as long as your transaction manager speaks JDBC. &lt;strong&gt;SUPPORTS&lt;/strong&gt; and &lt;strong&gt;NOT_SUPPORTED&lt;/strong&gt; go along with whatever is there. &lt;strong&gt;MANDATORY&lt;/strong&gt; and &lt;strong&gt;NEVER&lt;/strong&gt; stand guard and throw when the surroundings are wrong.&lt;/p&gt;

&lt;p&gt;And sitting underneath all of it is the rollback rule that decides whether a boundary commits or discards: unchecked exceptions roll back, checked ones commit, and &lt;code&gt;rollbackFor&lt;/code&gt; is how you fix the mismatch when your checked failure should have undone the work.&lt;/p&gt;

&lt;p&gt;Put Part 1 and Part 2 together and transactions stop being a single opaque annotation. They become a boundary you place deliberately, a propagation mode you choose per layer, and a rollback rule you make explicit — three decisions, each yours to make.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Transaction propagation (REQUIRED / REQUIRES_NEW / NESTED ...) (Part 1 of 2)</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Fri, 28 Aug 2026 16:50:32 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-propagation-required-requiresnew-nested-part-1-of-2-5fal</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/transaction-propagation-required-requiresnew-nested-part-1-of-2-5fal</guid>
      <description>&lt;h2&gt;
  
  
  When one transactional method calls another
&lt;/h2&gt;

&lt;p&gt;You have probably written a method like this and moved on:&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;@Transactional&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="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;@Transactional&lt;/code&gt; tells Spring one thing: run everything in this method as a single unit of work against the database. Either both writes stick, or neither does.&lt;/p&gt;

&lt;p&gt;That is simple enough when the method stands alone. But real code is layered. &lt;code&gt;placeOrder&lt;/code&gt; calls a service, which calls another service — and several of those methods also carry &lt;code&gt;@Transactional&lt;/code&gt;. So a question appears the moment your code has more than one layer: when a transactional method calls another transactional method, is that &lt;strong&gt;one&lt;/strong&gt; transaction, or two?&lt;/p&gt;

&lt;p&gt;The answer is not decided for you. You choose it, per method, with a setting called &lt;strong&gt;propagation&lt;/strong&gt;. This article is about what that choice means, the two modes that carry almost all real use, and a trap that can silently switch the whole thing off.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "a transaction" actually is here
&lt;/h2&gt;

&lt;p&gt;Before propagation can make sense, we need to be precise about the thing being propagated.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;transaction&lt;/strong&gt; is a boundary drawn around a group of database changes. Inside the boundary, the changes are provisional — pencilled in, not yet permanent. At the end, one of two things happens. You &lt;strong&gt;commit&lt;/strong&gt;, and every change inside becomes permanent together. Or you &lt;strong&gt;roll back&lt;/strong&gt;, and every change inside is thrown away together.&lt;/p&gt;

&lt;p&gt;All-or-nothing is the entire point. No half-finished state — an order saved but never paid for — ever reaches the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Spring opens one for you — the proxy
&lt;/h2&gt;

&lt;p&gt;Notice that you never wrote code to open, commit, or roll back that transaction. Spring did it around your method. Here is how, because the "how" is where the traps live.&lt;/p&gt;

&lt;p&gt;When a bean has a &lt;code&gt;@Transactional&lt;/code&gt; method, Spring does not hand your object to the rest of the app directly. It wraps your object in a &lt;strong&gt;proxy&lt;/strong&gt; — a stand-in object with the same method signatures. Everyone who calls your bean actually holds the proxy, not your real object.&lt;/p&gt;

&lt;p&gt;The proxy's job is to run extra work before and after your real 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="c1"&gt;// conceptually, the proxy wraps your call like this:&lt;/span&gt;
&lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;begin&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// before your method runs&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;realOrderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// your actual code&lt;/span&gt;
    &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// after — if nothing threw&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// after — if it threw&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&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;Read the wrapper, not the details. Two facts matter later. First, the transaction is opened by the &lt;strong&gt;proxy&lt;/strong&gt;, never by your own code. Second, it is opened only when a call arrives &lt;strong&gt;through&lt;/strong&gt; the proxy. Hold on to both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Propagation: join, or start fresh?
&lt;/h2&gt;

&lt;p&gt;Now the layered call. &lt;code&gt;placeOrder&lt;/code&gt; is transactional and calls &lt;code&gt;writeAuditLog&lt;/code&gt;, which is also transactional.&lt;/p&gt;

&lt;p&gt;The proxy around &lt;code&gt;writeAuditLog&lt;/code&gt; is about to run. It checks a simple thing: is a transaction already open on this thread? Yes — &lt;code&gt;placeOrder&lt;/code&gt; opened one a moment ago. So what should this inner proxy do? Join the transaction already running, or push it aside and start its own?&lt;/p&gt;

&lt;p&gt;That decision is &lt;strong&gt;propagation&lt;/strong&gt;. You set it on the annotation:&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRED&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;writeAuditLog&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="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;There are seven modes in total. Two of them carry almost every real case, so we build those fully here, then meet the trap that can quietly disable all seven. The remaining modes — and savepoints, a middle ground between the two — are Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  REQUIRED — the default, and what "join" costs you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;REQUIRED&lt;/strong&gt; means: if a transaction is already running, join it; if not, start a new one. It is the default. Every &lt;code&gt;@Transactional&lt;/code&gt; you write without naming a mode behaves this way.&lt;/p&gt;

&lt;p&gt;So in our example, both methods are REQUIRED:&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;@Transactional&lt;/span&gt;                       &lt;span class="c1"&gt;// REQUIRED by default&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="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;writeAuditLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// joins the same transaction&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Joining means one &lt;strong&gt;physical transaction&lt;/strong&gt; shared by both methods. Both writes land inside the same boundary, and a single commit at the very end — fired by the outermost method — makes them permanent together.&lt;/p&gt;

&lt;p&gt;That shared fate is usually exactly what you want. If &lt;code&gt;writeAuditLog&lt;/code&gt; succeeds but &lt;code&gt;placeOrder&lt;/code&gt; fails afterwards, the audit write rolls back too. You never keep a log entry for an order that never happened.&lt;/p&gt;

&lt;p&gt;But joining has a sharp edge, and it surprises people constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rollback-only trap
&lt;/h2&gt;

&lt;p&gt;Here is the code that trips everyone up. The inner method throws, and you catch the exception so the outer method can carry on:&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;@Transactional&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="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;writeAuditLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// inner REQUIRED — it throws&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// swallow it — auditing is not critical, keep going&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// proxy now tries to commit here...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You caught the exception. You expect the order to commit. Instead you get this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```plain text&lt;br&gt;
UnexpectedRollbackException: Transaction silently rolled back&lt;br&gt;
because it has been marked rollback-only&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



Why? Because the two methods share **one** transaction. When `writeAuditLog` throws, its proxy wants to roll back — but it cannot. It did not open the transaction, so it is not allowed to end it. The only thing it can do is set a flag on the shared transaction: **rollback-only**. Meaning: this transaction may no longer commit, no matter what happens next.


Your `catch` block swallowed the exception, but it could not unset that flag. When the outer proxy reaches its commit, the transaction manager sees rollback-only and refuses. The commit becomes a forced rollback, and you get the exception above.


The lesson is blunt: inside one shared transaction, a failure anywhere poisons the whole thing — even a failure you caught. If you genuinely need the inner work to fail on its own without dooming the order, joining is the wrong choice. You need a separate transaction. That is the next mode.


## REQUIRES_NEW — a genuinely separate transaction


**REQUIRES_NEW** means: always start a new, independent transaction. If one is already running, **suspend** it first — pause it, fully intact, changing nothing — run the new transaction to its own commit or rollback, then resume the outer one.




```java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(Order order) {
    auditRepo.save(new AuditEntry(order));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now there are two &lt;strong&gt;physical transactions&lt;/strong&gt;, and they are strangers to each other. The audit log commits on its own, immediately, in its own boundary. Two consequences fall out of that, and both are the reason you would reach for this mode.&lt;/p&gt;

&lt;p&gt;First, survival. If &lt;code&gt;placeOrder&lt;/code&gt; fails and rolls back afterwards, the audit row was already committed in its own transaction — so it stays. The log records the attempt even though the order was undone.&lt;/p&gt;

&lt;p&gt;Second, isolation of failure. If &lt;code&gt;writeAuditLog&lt;/code&gt; throws and you catch it, only its own transaction rolled back. The outer transaction was suspended and untouched, so there is no rollback-only flag on it. The order commits normally. The trap from the last section simply cannot happen across a REQUIRES_NEW boundary, because the two were never the same transaction.&lt;/p&gt;

&lt;p&gt;That independence is the whole use case: work that must land, or fail, regardless of what the surrounding transaction does — audit trails, attempt counters, "we tried to notify the user" records.&lt;/p&gt;

&lt;p&gt;It has one real cost, worth knowing before you sprinkle it everywhere. Suspending the outer transaction does not release its database connection — that connection stays open and idle while the new transaction borrows a second one. So for a moment you hold &lt;strong&gt;two&lt;/strong&gt; connections for one request. Under load, that can drain the connection pool faster than you expect. Use REQUIRES_NEW deliberately, where the independence earns its keep.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trap that silently disables all of this: self-invocation
&lt;/h2&gt;

&lt;p&gt;Everything above rests on one assumption: the call reaches the inner method &lt;strong&gt;through its proxy&lt;/strong&gt;. Here is the everyday way it quietly does not.&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="nd"&gt;@Transactional&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="n"&gt;writeAuditLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// a plain internal call&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRES_NEW&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;writeAuditLog&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="n"&gt;auditRepo&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuditEntry&lt;/span&gt;&lt;span class="o"&gt;(&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You asked for REQUIRES_NEW on &lt;code&gt;writeAuditLog&lt;/code&gt;. You will not get it. The audit write runs inside &lt;code&gt;placeOrder&lt;/code&gt;'s transaction, exactly as if the annotation were not there.&lt;/p&gt;

&lt;p&gt;The reason is the proxy again. &lt;code&gt;placeOrder&lt;/code&gt; calls &lt;code&gt;writeAuditLog(order)&lt;/code&gt; as a bare method call, which is really &lt;code&gt;this.writeAuditLog(order)&lt;/code&gt;. That call goes straight to the real object — &lt;code&gt;this&lt;/code&gt; — and never leaves it. It never passes through the proxy. And the proxy is the only thing that reads &lt;code&gt;@Transactional&lt;/code&gt; and acts on propagation. Skip the proxy, and you skip propagation entirely.&lt;/p&gt;

&lt;p&gt;The fix is to make the call cross a proxy boundary. The cleanest way is usually to move the second method onto a &lt;strong&gt;different bean&lt;/strong&gt; and inject 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;@Transactional&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="n"&gt;orderRepo&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;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeAuditLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// a real proxy is in between&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the call travels through &lt;code&gt;auditService&lt;/code&gt;'s proxy, that proxy reads REQUIRES_NEW, and you finally get the separate transaction you asked for. The rule to carry away: propagation only ever applies to a call that enters a bean from the outside, never to a method a bean calls on itself.&lt;/p&gt;

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

&lt;p&gt;Three things are worth keeping. &lt;strong&gt;REQUIRED&lt;/strong&gt; joins an existing transaction into one shared boundary — convenient, but a failure anywhere marks the whole thing rollback-only, even one you catch. &lt;strong&gt;REQUIRES_NEW&lt;/strong&gt; carves out a truly independent transaction that commits or fails on its own, at the price of a second connection held open. And none of it takes effect on a plain internal call, because propagation lives in the proxy your own method never touches.&lt;/p&gt;

&lt;p&gt;Part 2 picks up the middle ground between joining and full independence — &lt;strong&gt;NESTED&lt;/strong&gt; and savepoints — then walks the four remaining modes, and pins down exactly which exceptions actually trigger a rollback.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 2.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>@Transactional under the hood (proxy) + self-invocation gotcha</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Thu, 27 Aug 2026 15:24:20 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/transactional-under-the-hood-proxy-self-invocation-gotcha-3oki</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/transactional-under-the-hood-proxy-self-invocation-gotcha-3oki</guid>
      <description>&lt;p&gt;You put &lt;code&gt;@Transactional&lt;/code&gt; on a service method, and suddenly the database treats everything inside it as one unit: either all of it sticks, or none of it does. Save three rows, throw an exception halfway through, and the first two vanish as if you never wrote them. You didn't open a transaction. You didn't commit at the end. You didn't roll back on the error. Yet all three happened.&lt;/p&gt;

&lt;p&gt;So who did that work, and where does it run? That is what this article is about: the machinery Spring puts around your method the moment it sees &lt;code&gt;@Transactional&lt;/code&gt;, the exact rule that decides whether it commits or rolls back — and the one call pattern that makes the whole thing silently do nothing.&lt;/p&gt;

&lt;p&gt;We'll lean on one idea you've likely already met: the &lt;strong&gt;proxy&lt;/strong&gt;, the stand-in object Spring slips in front of your bean so it can add behaviour around your methods without touching their bodies. Everything here is about what that stand-in actually &lt;em&gt;does&lt;/em&gt; for transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what a transaction even is
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;transaction&lt;/strong&gt; is a group of database operations that must succeed or fail together. Move money from one account to another and you touch two rows: subtract from one, add to the other. If the second write fails, the first must be undone too — otherwise money simply disappears. "All or nothing" is the whole promise.&lt;/p&gt;

&lt;p&gt;The database gives you the tools to enforce that: a point where you &lt;em&gt;begin&lt;/em&gt;, a point where you &lt;em&gt;commit&lt;/em&gt; to make the changes permanent, and a &lt;em&gt;rollback&lt;/em&gt; that throws away everything since the begin. Left alone, most databases auto-commit each statement on its own. A transaction is you telling the database, "stop doing that — hold these together until I say go."&lt;/p&gt;

&lt;h2&gt;
  
  
  The boilerplate you'd otherwise write by hand
&lt;/h2&gt;

&lt;p&gt;Without Spring, wiring that up around some work looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAutoCommit&lt;/span&gt;&lt;span class="o"&gt;(&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;// begin: stop committing each statement&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... your inserts and updates ...&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                &lt;span class="c1"&gt;// all good — make it permanent&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// something failed — undo everything&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&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;Read it once and the shape is obvious: turn off auto-commit to open a transaction, do the work, commit if you reach the end, roll back if anything throws, and always release the connection.&lt;/p&gt;

&lt;p&gt;Now notice the problem. Your actual logic is the one commented line in the middle. Everything else is ceremony — and it would be &lt;em&gt;identical&lt;/em&gt; in every method that needs to be atomic. A concern that repeats across many unrelated methods like this is a &lt;strong&gt;cross-cutting concern&lt;/strong&gt;, and Spring's standing answer to those is: keep your method clean, and push the ceremony into a proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;@Transactional&lt;/code&gt; moves that ceremony into a proxy
&lt;/h2&gt;

&lt;p&gt;Here's the same operation the Spring way. The method holds only the business 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;@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;Orders&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&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;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reserve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// no begin, no commit, no rollback — just the work&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;When Spring builds this bean, it doesn't hand callers the real &lt;code&gt;Orders&lt;/code&gt; object. It hands them a &lt;strong&gt;proxy&lt;/strong&gt; — a stand-in of the same type that sits in front of the real bean and can run extra code before and after each method. &lt;code&gt;@Transactional&lt;/code&gt; is the marker that tells Spring, "this method needs the transaction ceremony wrapped around it."&lt;/p&gt;

&lt;p&gt;The code that does the wrapping is a piece of advice living inside the proxy called the &lt;strong&gt;TransactionInterceptor&lt;/strong&gt;. Every outside call to &lt;code&gt;placeOrder&lt;/code&gt; lands in the interceptor first, and it runs — almost literally — the try/commit/catch/rollback block you saw above:&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;Object&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Method&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;Object&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;TransactionStatus&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransaction&lt;/span&gt;&lt;span class="o"&gt;(...);&lt;/span&gt;   &lt;span class="c1"&gt;// begin&lt;/span&gt;
    &lt;span class="k"&gt;try&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;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&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 method body&lt;/span&gt;
        &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                                &lt;span class="c1"&gt;// commit on success&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="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&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;Error&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="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                              &lt;span class="c1"&gt;// rollback on failure&lt;/span&gt;
        &lt;span class="k"&gt;throw&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the hand-written boilerplate again — except it was generated for you, and it wraps &lt;em&gt;every&lt;/em&gt; &lt;code&gt;@Transactional&lt;/code&gt; method without you repeating a line. Your method body is the &lt;code&gt;method.invoke(target, args)&lt;/code&gt; in the middle; the interceptor supplies everything around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transaction manager does the real database work
&lt;/h2&gt;

&lt;p&gt;Notice the interceptor never touches a &lt;code&gt;Connection&lt;/code&gt; itself. It delegates to a &lt;code&gt;txManager&lt;/code&gt; — a &lt;strong&gt;PlatformTransactionManager&lt;/strong&gt;, Spring's abstraction over "a thing that can begin, commit, and roll back a transaction."&lt;/p&gt;

&lt;p&gt;Why an abstraction instead of raw JDBC? Because "a transaction" means different things to different persistence tools. Plain JDBC commits a &lt;code&gt;Connection&lt;/code&gt;; JPA commits an &lt;code&gt;EntityManager&lt;/code&gt;; each needs its own begin/commit calls. So Spring ships a different manager for each world — &lt;strong&gt;DataSourceTransactionManager&lt;/strong&gt; for JDBC, &lt;strong&gt;JpaTransactionManager&lt;/strong&gt; for JPA — and they all expose the same three methods. The interceptor calls &lt;code&gt;getTransaction&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, and &lt;code&gt;rollback&lt;/code&gt;, and the right manager underneath translates that into the real thing: grabbing a connection, calling &lt;code&gt;setAutoCommit(false)&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How your queries join the same transaction
&lt;/h2&gt;

&lt;p&gt;Here is the part that feels like magic until you see it. The manager opened a connection and began a transaction. But your method body calls a repository, a &lt;code&gt;JdbcTemplate&lt;/code&gt;, or a JPA &lt;code&gt;EntityManager&lt;/code&gt; — none of which you handed that connection to. How do &lt;em&gt;they&lt;/em&gt; end up inside the same transaction instead of opening their own?&lt;/p&gt;

&lt;p&gt;The answer is a thread-local handoff, run by a piece called the &lt;strong&gt;TransactionSynchronizationManager&lt;/strong&gt;. When the transaction manager begins a transaction, it stashes the live connection in a map keyed to the &lt;strong&gt;current thread&lt;/strong&gt;. When your repository later needs a connection, it doesn't call &lt;code&gt;dataSource.getConnection()&lt;/code&gt; directly — it asks Spring's connection helper, which first checks that thread-local map and hands back the connection already bound there.&lt;/p&gt;

&lt;p&gt;So the connection is shared not by passing it around, but by parking it on the thread everyone is running on. That single fact is why an entire call stack of repositories all land in one transaction without ever mentioning it.&lt;/p&gt;

&lt;p&gt;It also explains a sharp edge: a transaction is tied to &lt;strong&gt;one thread&lt;/strong&gt;. If your method spawns a new thread and does database work on it, that work runs on a different thread, finds nothing bound there, opens its own connection — and lives completely outside your transaction. The &lt;code&gt;@Transactional&lt;/code&gt; boundary does not follow you across threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: rollback only fires for unchecked exceptions
&lt;/h2&gt;

&lt;p&gt;Look again at the interceptor's catch clause: &lt;code&gt;catch (RuntimeException | Error)&lt;/code&gt;. That is not a simplification — it is the actual default rule. Spring rolls back when your method throws an &lt;strong&gt;unchecked&lt;/strong&gt; exception (a &lt;code&gt;RuntimeException&lt;/code&gt; or an &lt;code&gt;Error&lt;/code&gt;). If it throws a &lt;strong&gt;checked&lt;/strong&gt; exception, Spring &lt;strong&gt;commits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That surprises almost everyone the first time:&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;@Transactional&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;transfer&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;debit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAccount&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;IOException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"statement service down"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// checked → COMMITS the debit&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;IOException&lt;/code&gt; is checked, so the interceptor doesn't treat it as a rollback signal. The debit is committed and the money is gone, even though the method failed. The reason is historical: the framework's authors decided checked exceptions represent "expected business outcomes" the caller might recover from, so they let the transaction stand.&lt;/p&gt;

&lt;p&gt;You will usually disagree, and the override is one attribute:&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;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollbackFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;// roll back on any exception&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;rollbackFor&lt;/code&gt; whenever a checked exception should still undo the work. It's the most common &lt;code&gt;@Transactional&lt;/code&gt; tuning there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: swallow the exception and you lose the rollback
&lt;/h2&gt;

&lt;p&gt;The interceptor can only react to an exception that actually &lt;strong&gt;escapes&lt;/strong&gt; your method. If you catch it inside the method and don't rethrow, the proxy sees a clean return — and commits.&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;@Transactional&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;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;risky&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order failed"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// swallowed — no exception leaves the method&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// proxy sees a normal return → commits whatever risky() half-did&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction commits the partial work, because from the proxy's point of view nothing went wrong. If you must catch the exception but still want the work undone, mark the transaction for rollback 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="nc"&gt;TransactionAspectSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTransactionStatus&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setRollbackOnly&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That flag tells the manager, "no matter how this method returns, roll back." The commit then turns into a rollback at the boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: self-invocation — the transaction never starts
&lt;/h2&gt;

&lt;p&gt;This is the trap in the topic's title, and it falls straight out of how the proxy works: &lt;strong&gt;the proxy only wraps calls that arrive from outside the bean.&lt;/strong&gt; A call the bean makes to itself never leaves the object, so it never crosses the proxy — and the interceptor never runs.&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;Orders&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;placeAll&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;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&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;Order&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// internal call: really this.save(o)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&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;save&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;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// expected to run in its own transaction&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd expect each &lt;code&gt;save&lt;/code&gt; to open and commit its own transaction. It opens none. When &lt;code&gt;placeAll&lt;/code&gt; calls &lt;code&gt;save&lt;/code&gt;, that is a plain &lt;code&gt;this.save(o)&lt;/code&gt; running &lt;em&gt;inside&lt;/em&gt; the real object, underneath the proxy. The proxy — and its TransactionInterceptor — is standing outside, and this call never reaches it. So &lt;code&gt;save&lt;/code&gt; runs with no transaction at all, and, worse, there's no error to tell you.&lt;/p&gt;

&lt;p&gt;Only a call to &lt;code&gt;save&lt;/code&gt; from &lt;em&gt;another&lt;/em&gt; bean, one that enters through the proxy, gets wrapped. The fixes all amount to making the call cross the proxy boundary: move &lt;code&gt;save&lt;/code&gt; into a separate bean and inject it, inject the bean into itself and call through that reference, or reach for the current proxy with &lt;code&gt;AopContext.currentProxy()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: public and non-final only
&lt;/h2&gt;

&lt;p&gt;One more limit inherited from the proxy. When Spring proxies by subclassing your class (its Boot default), it can only wrap methods it can &lt;strong&gt;override&lt;/strong&gt;. A &lt;code&gt;private&lt;/code&gt; method can't be overridden, and a &lt;code&gt;final&lt;/code&gt; method can't be either — so &lt;code&gt;@Transactional&lt;/code&gt; on either one quietly does nothing, with no warning. Keep transactional methods &lt;code&gt;public&lt;/code&gt; and non-final and this never bites you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model to keep
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt; adds no code to your method. A &lt;strong&gt;proxy&lt;/strong&gt; wraps the bean, and inside it the &lt;strong&gt;TransactionInterceptor&lt;/strong&gt; runs the begin / commit / rollback ceremony around your call, delegating the real database work to a &lt;strong&gt;PlatformTransactionManager&lt;/strong&gt;. The connection that manager opens is parked on the current thread, so every repository and query on that thread quietly joins the same transaction.&lt;/p&gt;

&lt;p&gt;Two facts explain nearly every surprise. First, a rollback happens only when an &lt;strong&gt;unchecked exception escapes&lt;/strong&gt; the method — checked exceptions commit, and a swallowed exception commits, unless you say otherwise. Second, the whole mechanism only fires when the call &lt;strong&gt;enters through the proxy from outside&lt;/strong&gt;, so a self-invoked method gets no transaction at all. Hold those two facts and &lt;code&gt;@Transactional&lt;/code&gt; stops being magic and becomes something you can reason about.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>AOP: aspect / pointcut / advice / weaving</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Wed, 26 Aug 2026 22:31:30 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/aop-aspect-pointcut-advice-weaving-43ii</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/aop-aspect-pointcut-advice-weaving-43ii</guid>
      <description>&lt;p&gt;Every codebase grows a set of chores that have nothing to do with the actual job of a method, yet somehow end up inside every method anyway. Logging that a call started. Timing how long it took. Checking the user is allowed in. Opening a database transaction. These are &lt;strong&gt;cross-cutting concerns&lt;/strong&gt; — needs that cut across many unrelated methods, the same few lines copied into each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aspect-Oriented Programming&lt;/strong&gt; — AOP — is the tool for pulling that repeated code out of your methods and describing it in one place instead. You meet it in Spring the first time you use &lt;code&gt;@Transactional&lt;/code&gt;, &lt;code&gt;@Cacheable&lt;/code&gt;, or &lt;code&gt;@Async&lt;/code&gt;: all of them are AOP underneath. So understanding AOP is really understanding how Spring makes a plain method call quietly do more than the method's body says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, in code
&lt;/h2&gt;

&lt;p&gt;Here is a service method with a couple of those chores baked in:&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;Order&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;Cart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&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;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"placeOrder started"&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="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// the only line doing real work&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"placeOrder took {}ms"&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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only one line does the actual job. The rest is timing and logging. Now picture those same four lines wrapped around every service method in the app. Change the log format once and you are editing it in fifty places.&lt;/p&gt;

&lt;p&gt;The instinct is to move the noise into a helper. But a helper still has to be &lt;em&gt;called&lt;/em&gt; from inside each method — the boilerplate shrinks, it never disappears. What we really want is for &lt;code&gt;placeOrder&lt;/code&gt; to contain only its real work, and to describe the timing separately, somewhere else entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  A point where behavior could run
&lt;/h2&gt;

&lt;p&gt;Start by naming the moments where extra behavior could plausibly happen. Just before a method starts. Right after it returns. The instant it throws. Each of these is a &lt;strong&gt;join point&lt;/strong&gt; — a single, identifiable point during a program's execution where you could hook in extra code.&lt;/p&gt;

&lt;p&gt;Spring keeps this deliberately narrow. In Spring, the only join points are method executions on Spring-managed objects. Every public method call on a bean is a candidate; nothing else is. That one restriction explains a lot of Spring AOP's behavior later on, so keep it in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code that runs there: advice
&lt;/h2&gt;

&lt;p&gt;The extra behavior you want to run at a join point is called &lt;strong&gt;advice&lt;/strong&gt;. Advice is just a method you write, tagged with &lt;em&gt;when&lt;/em&gt; it should run relative to the join point.&lt;/p&gt;

&lt;p&gt;The simplest kind runs before the target 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;@Before&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// the "..." — which methods — comes later&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;logStart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JoinPoint&lt;/span&gt; &lt;span class="n"&gt;jp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{} started"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jp&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="na"&gt;getName&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;There are five kinds of advice, and the tag on the method picks which: &lt;code&gt;@Before&lt;/code&gt; (runs first), &lt;code&gt;@AfterReturning&lt;/code&gt; (only on success), &lt;code&gt;@AfterThrowing&lt;/code&gt; (only on failure), &lt;code&gt;@After&lt;/code&gt; (always, like a &lt;code&gt;finally&lt;/code&gt;), and &lt;code&gt;@Around&lt;/code&gt; (the most powerful — it &lt;em&gt;wraps&lt;/em&gt; the call).&lt;/p&gt;

&lt;p&gt;Around advice is worth dwelling on, because it can do what the other four can, and more. It receives the call itself as an object you must explicitly run:&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;@Around&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;time&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;// &amp;lt;-- this runs the real method&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{} took {}ms"&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="na"&gt;getName&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;pjp.proceed()&lt;/code&gt; call &lt;em&gt;is&lt;/em&gt; the target method. Everything above it runs before the method; everything below runs after. And if you never call &lt;code&gt;proceed()&lt;/code&gt;, the real method simply never runs — which is exactly how a security aspect can block a call outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing where advice applies: the pointcut
&lt;/h2&gt;

&lt;p&gt;We have advice, but the &lt;code&gt;"..."&lt;/code&gt; in those annotations is still empty. We need to say &lt;em&gt;which&lt;/em&gt; join points a piece of advice attaches to. That selector is a &lt;strong&gt;pointcut&lt;/strong&gt; — an expression that matches a set of join points.&lt;/p&gt;

&lt;p&gt;The most common form matches by method 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="nd"&gt;@Around&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execution(* com.shop.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;time&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="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;Read the &lt;code&gt;execution(...)&lt;/code&gt; expression left to right: the first &lt;code&gt;*&lt;/code&gt; is "any return type", &lt;code&gt;com.shop.service.*&lt;/code&gt; is "any class in that package", and &lt;code&gt;.*(..)&lt;/code&gt; is "any method, with any arguments". Put together, this one line means &lt;em&gt;every method of every class in the service package&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A pointcut can also match by annotation instead of by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Around&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@annotation(com.shop.Timed)"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches any method carrying a &lt;code&gt;@Timed&lt;/code&gt; annotation, wherever it lives. That is precisely how &lt;code&gt;@Transactional&lt;/code&gt; finds its targets — a pointcut looking for the annotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bundling it together: the aspect
&lt;/h2&gt;

&lt;p&gt;A pointcut plus the advice that runs at it is a natural pair. That pair, grouped into a class for one concern, is an &lt;strong&gt;aspect&lt;/strong&gt; — a class marked &lt;code&gt;@Aspect&lt;/code&gt; that holds related pointcuts and advice 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;@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;TimingAspect&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.shop.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;time&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="k"&gt;try&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;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="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{} took {}ms"&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="na"&gt;getName&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="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;@Aspect&lt;/code&gt; marks the class as holding advice. &lt;code&gt;@Component&lt;/code&gt; makes it a Spring bean, so the container discovers it. With this in place, &lt;code&gt;placeOrder&lt;/code&gt; goes back to containing only its real work — the timing now lives here, in exactly one place, and applies to the whole service package at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually gets applied: weaving
&lt;/h2&gt;

&lt;p&gt;Now the question that ties everything together. Your service methods contain no reference at all to &lt;code&gt;TimingAspect&lt;/code&gt;. So how does calling &lt;code&gt;placeOrder&lt;/code&gt; actually run the timing code? The act of combining aspects with the target code, so the advice really fires, is called &lt;strong&gt;weaving&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Weaving can happen at three different moments: when you compile, when classes load into the JVM, or at runtime while the app is running. Spring chooses the last one, and it does it with a &lt;strong&gt;proxy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A proxy is a stand-in object that has the very same methods as your bean, but each of its methods first runs the advice and then forwards to the real bean. The trick is in the wiring: when another bean asks the container for &lt;code&gt;OrderService&lt;/code&gt;, Spring hands over the proxy, not the real object. The caller cannot tell — same type, same method signatures.&lt;/p&gt;

&lt;p&gt;In spirit, the proxy's version of the method looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what the generated proxy does, conceptually&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Order&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;Cart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&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="k"&gt;try&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;realOrderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// delegate to the real bean&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"placeOrder took {}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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring builds that proxy in one of two ways. If your bean implements an interface, it can create a &lt;strong&gt;JDK dynamic proxy&lt;/strong&gt; — a runtime object implementing the same interface. If there is no interface (or you ask for it), it uses &lt;strong&gt;CGLIB&lt;/strong&gt;, which generates a subclass of your bean and overrides its methods. Spring Boot defaults to CGLIB, so it works whether or not you have interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap the proxy creates: self-invocation
&lt;/h2&gt;

&lt;p&gt;Because the woven behavior lives in the proxy and not inside your class, one everyday situation silently skips the advice: a method calling another method on the &lt;em&gt;same&lt;/em&gt; 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;@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;public&lt;/span&gt; &lt;span class="nc"&gt;Order&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;Cart&lt;/span&gt; &lt;span class="n"&gt;cart&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="nf"&gt;validateAndSave&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// internal call — bypasses the proxy&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Timed&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;validateAndSave&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt; &lt;span class="n"&gt;cart&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;The proxy only wraps calls that arrive &lt;em&gt;through&lt;/em&gt; it, from outside. But &lt;code&gt;placeOrder&lt;/code&gt; calls &lt;code&gt;validateAndSave&lt;/code&gt; directly on &lt;code&gt;this&lt;/code&gt; — the real object, with no proxy in between — so the &lt;code&gt;@Timed&lt;/code&gt; advice never runs. This catches everyone exactly once, and the mechanism explains it precisely: nothing sits between an object and a call to its own methods.&lt;/p&gt;

&lt;p&gt;The fixes follow from the same fact: move the annotated method into a separate bean, or inject the proxy into itself, so the call goes through the proxy again. (Spring's transaction handling has its own version of this trap, which gets its own deep dive later.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AspectJ comes in
&lt;/h2&gt;

&lt;p&gt;Spring's proxy weaving is powerful, but it is bounded by that one mechanism: only Spring beans, only method-execution join points, and only calls coming from outside the object. Full &lt;strong&gt;AspectJ&lt;/strong&gt; is a separate, more capable AOP system that Spring can integrate with. It weaves at compile time or class-load time by rewriting bytecode directly, so it can advise constructors, field access, and even internal calls — there is no proxy to bypass.&lt;/p&gt;

&lt;p&gt;Most applications never need it; proxy-based Spring AOP covers the everyday concerns cleanly. Reach for AspectJ only when you genuinely need to advise something a proxy cannot see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the vocabulary together
&lt;/h2&gt;

&lt;p&gt;The five words in this topic name five parts of one machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Join point&lt;/strong&gt; — a point where behavior could run. In Spring, a method call on a bean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advice&lt;/strong&gt; — the code that runs there, tagged with &lt;em&gt;when&lt;/em&gt; (before, around, after…).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointcut&lt;/strong&gt; — the expression selecting &lt;em&gt;which&lt;/em&gt; join points the advice attaches to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspect&lt;/strong&gt; — the class bundling a pointcut and its advice for one concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaving&lt;/strong&gt; — wiring the aspect into the target so the advice actually fires; Spring does it at runtime, through a proxy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time you write &lt;code&gt;@Transactional&lt;/code&gt; or &lt;code&gt;@Cacheable&lt;/code&gt;, this entire machine is what turns a one-word annotation into real behavior wrapped around your method — with not a line of boilerplate left in sight.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Spring proxies: JDK dynamic vs CGLIB</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Mon, 17 Aug 2026 05:08:46 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/spring-proxies-jdk-dynamic-vs-cglib-1fg1</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/spring-proxies-jdk-dynamic-vs-cglib-1fg1</guid>
      <description>&lt;p&gt;You put &lt;code&gt;@Transactional&lt;/code&gt; on a method and a database transaction opens before it runs and commits when it returns — yet you never wrote a line to start or end one. You add &lt;code&gt;@Cacheable&lt;/code&gt;, and the second call with the same arguments skips your method body entirely. You didn't touch the code inside those methods. So where does the extra behaviour actually run?&lt;/p&gt;

&lt;p&gt;It runs inside a &lt;strong&gt;proxy&lt;/strong&gt;: a stand-in object that Spring slips between whoever calls your bean and the real bean itself. You meet proxies the moment you use any Spring annotation that wraps behaviour around a method — transactions, caching, security, retries, async. Most of the time you never see it. This article is about what that stand-in is, the two very different ways Spring builds one at runtime, and the traps that appear once you know it sits in the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proxy is just a stand-in
&lt;/h2&gt;

&lt;p&gt;Forget Spring for a moment. Suppose you have a class that does real work behind an interface:&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;PaymentService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;charge&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;account&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;cents&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RealPaymentService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&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;charge&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;account&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;cents&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// actually move the money&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;Now say you want to log every charge, but you can't edit &lt;code&gt;RealPaymentService&lt;/code&gt;. You could write a second class that implements the same interface, holds the real one inside, and adds the logging:&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;LoggingPaymentService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&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;PaymentService&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;LoggingPaymentService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;target&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;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&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;charge&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;account&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;cents&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"charging "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cents&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cents&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// forward to the real 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;p&gt;Hand callers a &lt;code&gt;LoggingPaymentService&lt;/code&gt; and they can't tell the difference — it &lt;em&gt;is&lt;/em&gt; a &lt;code&gt;PaymentService&lt;/code&gt;. That wrapper is a &lt;strong&gt;proxy&lt;/strong&gt;: same surface, extra behaviour, forwarding to the real object underneath, which we'll call the &lt;strong&gt;target&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything Spring does with proxies is this pattern. The only twist is that you don't write the wrapper by hand. Spring manufactures one for you, at runtime, for a class it has never seen before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Spring needs them
&lt;/h2&gt;

&lt;p&gt;The logging above has nothing to do with payments. It would apply the same way to refunds, lookups, anything. A behaviour that cuts across many unrelated methods like that is a &lt;strong&gt;cross-cutting concern&lt;/strong&gt;. Transactions, security checks, caching, and metrics are all cross-cutting — you don't want to paste the same boilerplate into every method body.&lt;/p&gt;

&lt;p&gt;Spring's answer is to keep your method clean and push the concern into a proxy. You annotate the method; Spring wraps the bean; the annotation's behaviour lives in the wrapper. That leaves one real question: how does Spring build that wrapper on the fly? There are two mechanisms, and the difference between them is the whole point of this topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Way one: a JDK dynamic proxy
&lt;/h2&gt;

&lt;p&gt;Java has shipped a proxy tool since version 1.3: &lt;code&gt;java.lang.reflect.Proxy&lt;/code&gt;. Give it a set of interfaces and it generates — in memory, at runtime — a brand-new class that implements them. That generated object is a &lt;strong&gt;JDK dynamic proxy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every call on it is funnelled into a single method you write, an &lt;code&gt;InvocationHandler&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="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newProxyInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getClassLoader&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;Class&lt;/span&gt;&lt;span class="o"&gt;[]{&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&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;// interfaces to mimic&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyObj&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="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="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;"before "&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="na"&gt;getName&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;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real&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;// forward to target&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;"after "&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="na"&gt;getName&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda is the handler. When someone calls &lt;code&gt;proxy.charge(...)&lt;/code&gt;, Java routes it into the lambda with &lt;code&gt;method = charge&lt;/code&gt; and &lt;code&gt;args = [...]&lt;/code&gt;. You run your extra work, call &lt;code&gt;method.invoke(real, args)&lt;/code&gt; to reach the real object, and return its result. It's the hand-written wrapper from before — except the class was generated for you.&lt;/p&gt;

&lt;p&gt;The catch hides in that second argument: &lt;code&gt;new Class[]{ PaymentService.class }&lt;/code&gt;. A JDK dynamic proxy can only mimic &lt;strong&gt;interfaces&lt;/strong&gt;. The generated class implements &lt;code&gt;PaymentService&lt;/code&gt;, but it is &lt;em&gt;not&lt;/em&gt; a &lt;code&gt;RealPaymentService&lt;/code&gt; — it's a synthetic class that merely shares the interface. So this style works only when your bean has an interface to stand behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Way two: a CGLIB proxy
&lt;/h2&gt;

&lt;p&gt;What if the bean has no interface — just a plain class? Java's built-in proxy can't help, so Spring reaches for a library called &lt;strong&gt;CGLIB&lt;/strong&gt;. Instead of implementing an interface, CGLIB generates a &lt;strong&gt;subclass&lt;/strong&gt; of your class at runtime and overrides its methods:&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;Enhancer&lt;/span&gt; &lt;span class="n"&gt;enhancer&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;Enhancer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;enhancer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSuperclass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RealPaymentService&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;// subclass the real class&lt;/span&gt;
&lt;span class="n"&gt;enhancer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCallback&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;MethodInterceptor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxyRef&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="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;"before "&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="na"&gt;getName&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;proxyRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeSuper&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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;// call the original method&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;"after "&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="na"&gt;getName&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="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;enhancer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape is identical — do something, forward, do something — but the mechanism differs. The proxy here is a &lt;em&gt;subclass&lt;/em&gt;, so it genuinely &lt;strong&gt;is-a&lt;/strong&gt; &lt;code&gt;RealPaymentService&lt;/code&gt;. &lt;code&gt;invokeSuper&lt;/code&gt; calls the original parent method, which is CGLIB's way of reaching the real behaviour.&lt;/p&gt;

&lt;p&gt;Because it works by subclassing, CGLIB needs no interface at all. That is its entire reason to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Spring picks one
&lt;/h2&gt;

&lt;p&gt;Spring makes this choice for you. The classic rule, still true in plain Spring Framework, is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bean implements at least one interface → &lt;strong&gt;JDK dynamic proxy&lt;/strong&gt; against that interface.&lt;/li&gt;
&lt;li&gt;Bean has no interface → &lt;strong&gt;CGLIB&lt;/strong&gt; subclass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can force CGLIB even when interfaces exist, with a flag:&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;@EnableTransactionManagement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyTargetClass&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;proxyTargetClass = true&lt;/code&gt; says "subclass the class, don't proxy the interface." Spring Boot took this further: since Boot 2.0 the default is &lt;strong&gt;CGLIB for everything&lt;/strong&gt;, interface or not. The reasoning is practical — a JDK proxy exposes only the interface, so any code that injects the concrete class breaks (more on that shortly). Subclass proxies avoid that whole category of surprise, so Boot standardised on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: self-invocation
&lt;/h2&gt;

&lt;p&gt;Now the traps — and nearly all of them fall out of one fact: &lt;strong&gt;the proxy only wraps calls that arrive from outside the bean.&lt;/strong&gt; A call the bean makes to itself never leaves the object, so it never passes through the proxy.&lt;/p&gt;

&lt;p&gt;Picture a bean where one method calls another:&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;Orders&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;placeAll&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;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&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;Order&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// internal call — stays inside 'this'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&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;save&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;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// expected to run in its own transaction&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd expect every &lt;code&gt;save&lt;/code&gt; to run in a transaction. It doesn't. When &lt;code&gt;placeAll&lt;/code&gt; calls &lt;code&gt;save&lt;/code&gt;, that is a plain &lt;code&gt;this.save(o)&lt;/code&gt; — it happens &lt;em&gt;inside&lt;/em&gt; the real object, underneath the proxy. The proxy never sees it, so the &lt;code&gt;@Transactional&lt;/code&gt; wrapping is skipped. Only a call to &lt;code&gt;save&lt;/code&gt; from &lt;em&gt;another&lt;/em&gt; bean, which enters through the proxy, gets a transaction.&lt;/p&gt;

&lt;p&gt;This is the single most common Spring surprise. &lt;code&gt;@Transactional&lt;/code&gt;, &lt;code&gt;@Cacheable&lt;/code&gt;, &lt;code&gt;@Async&lt;/code&gt; — all of them silently do nothing on self-invocation, and the code looks completely correct. The fix is to make the call go through the proxy: move the annotated method into a separate bean, or inject the bean into itself and call through that injected reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: final and private methods
&lt;/h2&gt;

&lt;p&gt;CGLIB works by subclassing and overriding. Java won't let you override a &lt;strong&gt;final&lt;/strong&gt; method or subclass a &lt;strong&gt;final&lt;/strong&gt; class — so CGLIB simply can't wrap them. There's no error, just no proxying: a &lt;code&gt;@Transactional&lt;/code&gt; on a &lt;code&gt;final&lt;/code&gt; method quietly does nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;private&lt;/strong&gt; methods hit the same wall for a different reason. A subclass can't override a private method, and an interface can't declare one, so neither proxy style can wrap it. Keep proxied methods &lt;code&gt;public&lt;/code&gt; and non-final and you stay clear of this trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: inject the interface, not the class
&lt;/h2&gt;

&lt;p&gt;With a JDK dynamic proxy, recall that the proxy implements the interface but is not your class. So this injection blows up:&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;@Autowired&lt;/span&gt;
&lt;span class="nc"&gt;RealPaymentService&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// fails if a JDK proxy is in play&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy can be assigned to &lt;code&gt;PaymentService&lt;/code&gt; but not to &lt;code&gt;RealPaymentService&lt;/code&gt;, so Spring throws at startup. Inject the &lt;strong&gt;interface&lt;/strong&gt; instead:&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;@Autowired&lt;/span&gt;
&lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// fine — the proxy is-a PaymentService&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly the breakage that Boot's "CGLIB everywhere" default sidesteps: a subclass proxy &lt;em&gt;is&lt;/em&gt; the concrete type, so both forms work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: the constructor runs oddly
&lt;/h2&gt;

&lt;p&gt;CGLIB builds its subclass through a route that doesn't call your constructor the normal way — your bean's constructor can run twice, or field initialisers may not have run when you'd expect. The practical rule: don't put real work or important side effects in the constructor of a proxied bean. Do that setup in an &lt;code&gt;@PostConstruct&lt;/code&gt; method, which Spring calls once on the fully built instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one model to keep
&lt;/h2&gt;

&lt;p&gt;A Spring proxy is a stand-in that wraps your bean so annotations can add behaviour around your methods without touching their bodies. It's built one of two ways: &lt;strong&gt;JDK dynamic proxies&lt;/strong&gt; implement your interface; &lt;strong&gt;CGLIB proxies&lt;/strong&gt; subclass your class — and Spring Boot defaults to CGLIB so the concrete type still resolves. Every proxy gotcha falls out of two facts: the wrapper only sees calls that enter from outside, so self-invocation escapes it; and it can only wrap what it can override or implement, so &lt;code&gt;final&lt;/code&gt; and &lt;code&gt;private&lt;/code&gt; escape it. Hold those two facts and the rest is just detail.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>BeanPostProcessor &amp; BeanFactoryPostProcessor</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Mon, 17 Aug 2026 05:08:42 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/beanpostprocessor-beanfactorypostprocessor-42b3</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/beanpostprocessor-beanfactorypostprocessor-42b3</guid>
      <description>&lt;h2&gt;
  
  
  Two hooks with nearly the same name
&lt;/h2&gt;

&lt;p&gt;Spring's container builds your objects so you don't have to. You mark a class, and a fully-wired object — a &lt;strong&gt;bean&lt;/strong&gt; — appears, ready to use. Most days you never think about &lt;em&gt;how&lt;/em&gt; that happens.&lt;/p&gt;

&lt;p&gt;But Spring itself has to think about it constantly. To turn a &lt;code&gt;${db.url}&lt;/code&gt; placeholder into a real value, to satisfy an &lt;code&gt;@Autowired&lt;/code&gt; field, to wrap a service in a transaction proxy — Spring needs a way to reach into its own build process and change what comes out. It does this through two official extension points, and their names are the single most confused pair in the framework: &lt;strong&gt;BeanFactoryPostProcessor&lt;/strong&gt; and &lt;strong&gt;BeanPostProcessor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They sound like synonyms. They are not. The difference is &lt;em&gt;when&lt;/em&gt; they run and &lt;em&gt;what&lt;/em&gt; they are allowed to touch — and once you see that, the confusion disappears for good. So we'll build the timeline first, then meet each hook in its place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The container works in two passes
&lt;/h2&gt;

&lt;p&gt;When the context starts, it does not build your beans in one motion. It works in two distinct passes.&lt;/p&gt;

&lt;p&gt;First, it reads every source of configuration — your &lt;code&gt;@Configuration&lt;/code&gt; classes, component scans, any XML — and turns each one into a &lt;strong&gt;bean definition&lt;/strong&gt;: a plain metadata object describing &lt;em&gt;how&lt;/em&gt; a bean should be built. Its class, its scope, its constructor arguments, its property values. A definition is a blueprint. No object exists yet; at this point the container is just holding a stack of blueprints.&lt;/p&gt;

&lt;p&gt;Second, once all the blueprints are collected, the container starts &lt;em&gt;instantiating&lt;/em&gt; — walking the definitions and actually building live objects from them, wiring each one's dependencies, running its init code.&lt;/p&gt;

&lt;p&gt;Hold onto that split: &lt;strong&gt;blueprints first, buildings second.&lt;/strong&gt; Each of the two post-processors hooks into exactly one of those passes. That is the whole distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  BeanFactoryPostProcessor — editing the blueprints
&lt;/h2&gt;

&lt;p&gt;The first hook runs at the seam between the two passes: after every bean definition is loaded, but &lt;em&gt;before&lt;/em&gt; a single bean is instantiated. It is called &lt;strong&gt;BeanFactoryPostProcessor&lt;/strong&gt;, and it exists to let you edit the blueprints while they are still just blueprints.&lt;/p&gt;

&lt;p&gt;The interface has one method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;postProcessBeanFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConfigurableListableBeanFactory&lt;/span&gt; &lt;span class="n"&gt;beanFactory&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 &lt;code&gt;beanFactory&lt;/code&gt; argument is the container holding all the definitions. You can walk it, read any bean's definition, and change it before that bean is ever built.&lt;/p&gt;

&lt;p&gt;Here is a small one that forces every bean to be lazily initialised:&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;LazyEverything&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&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;postProcessBeanFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConfigurableListableBeanFactory&lt;/span&gt; &lt;span class="n"&gt;bf&lt;/span&gt;&lt;span class="o"&gt;)&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;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBeanDefinitionNames&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBeanDefinition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setLazyInit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We loop over every definition and flip its &lt;code&gt;lazyInit&lt;/code&gt; flag. Because this runs before instantiation, the change lands in time to matter — when the container later builds these beans, it reads the modified blueprint. We changed the plan, not the product.&lt;/p&gt;

&lt;p&gt;You have almost certainly relied on a &lt;code&gt;BeanFactoryPostProcessor&lt;/code&gt; without noticing. Whenever a &lt;code&gt;${...}&lt;/code&gt; placeholder appears in your configuration — a database URL, a port number — something has to replace it with a real value from your properties files. That something is &lt;strong&gt;PropertySourcesPlaceholderConfigurer&lt;/strong&gt;, a &lt;code&gt;BeanFactoryPostProcessor&lt;/code&gt; Spring registers for you. It runs in this between-the-passes window and wires up placeholder resolution before any bean that needs a value is built.&lt;/p&gt;

&lt;p&gt;There is a close cousin worth naming once: &lt;strong&gt;BeanDefinitionRegistryPostProcessor&lt;/strong&gt;. It runs a moment earlier and can &lt;em&gt;add&lt;/em&gt; whole new definitions, not just tweak existing ones — it is how your &lt;code&gt;@Configuration&lt;/code&gt; classes get turned into bean definitions in the first place. Same idea, one step further up the chain.&lt;/p&gt;

&lt;p&gt;The rule to carry: a &lt;code&gt;BeanFactoryPostProcessor&lt;/code&gt; sees &lt;strong&gt;definitions, never instances.&lt;/strong&gt; The moment you find yourself wanting the actual built object, you have reached for the wrong hook — which is the other one.&lt;/p&gt;

&lt;h2&gt;
  
  
  BeanPostProcessor — intercepting each finished bean
&lt;/h2&gt;

&lt;p&gt;The second hook runs in the &lt;em&gt;second&lt;/em&gt; pass, during instantiation. After the container builds a bean and injects its dependencies, and around the moment it runs the bean's init code, every &lt;strong&gt;BeanPostProcessor&lt;/strong&gt; gets a turn with that live object.&lt;/p&gt;

&lt;p&gt;This interface has two methods:&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;BeanPostProcessor&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;postProcessBeforeInitialization&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;bean&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;beanName&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;postProcessAfterInitialization&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;bean&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;beanName&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;Recall the init step of a bean's life — the callback that runs once a bean is fully wired, like a method marked &lt;code&gt;@PostConstruct&lt;/code&gt;. These two methods bracket that step: &lt;strong&gt;before&lt;/strong&gt; runs just before your init code, &lt;strong&gt;after&lt;/strong&gt; runs just after. Every bean in the container passes through both, one bean at a time.&lt;/p&gt;

&lt;p&gt;These callbacks are not exotic edge cases — Spring implements its own annotations with them. The &lt;code&gt;@Autowired&lt;/code&gt; field injection and the &lt;code&gt;@PostConstruct&lt;/code&gt; call you use every day are each carried out by a &lt;code&gt;BeanPostProcessor&lt;/code&gt; that Spring registers on your behalf. The hook is not a rarely-used escape hatch; it is the machinery the framework itself runs on.&lt;/p&gt;

&lt;p&gt;A trivial logger shows the bare shape:&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;InitLogger&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BeanPostProcessor&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;postProcessAfterInitialization&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;bean&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="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;"Finished building: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bean&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;Notice the method &lt;em&gt;returns&lt;/em&gt; a bean. That return value is the detail that makes this hook powerful — and it is worth slowing down on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The return value is a swap point
&lt;/h2&gt;

&lt;p&gt;Whatever object you return from &lt;code&gt;postProcessAfterInitialization&lt;/code&gt; is what the container uses from then on. Return the same bean, and nothing changes. Return a &lt;em&gt;different&lt;/em&gt; object that wraps the original, and you have quietly substituted the bean the whole application will see.&lt;/p&gt;

&lt;p&gt;That is exactly how Spring gives you proxies. When a method carries &lt;code&gt;@Transactional&lt;/code&gt;, the real bean is built normally — then a &lt;code&gt;BeanPostProcessor&lt;/code&gt; catches it in the after-init step and returns a &lt;strong&gt;proxy&lt;/strong&gt;: a stand-in object of the same type that opens a transaction, calls through to your real bean, and commits. Nobody who injected that bean can tell the difference. They asked the container for a &lt;code&gt;PaymentService&lt;/code&gt; and got something that looks and types like 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="c1"&gt;// what you wrote&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&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;transfer&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;// what the container actually hands out&lt;/span&gt;
&lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wrapInTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;realService&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same mechanism powers Spring AOP, caching, and security checks — any feature that needs to run code around your methods. All of it rides on that one move: a &lt;code&gt;BeanPostProcessor&lt;/code&gt; returning a wrapper in the after-init step. Once you see the swap, a large amount of Spring's "magic" stops being magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this order causes a real trap
&lt;/h2&gt;

&lt;p&gt;Now the gotcha, and it follows straight from the timeline. For a &lt;code&gt;BeanPostProcessor&lt;/code&gt; to wrap &lt;em&gt;other&lt;/em&gt; beans, it obviously has to exist before those beans are built. So Spring instantiates all &lt;code&gt;BeanPostProcessor&lt;/code&gt;s first, up front, before it starts on your ordinary beans.&lt;/p&gt;

&lt;p&gt;That early instantiation is a trap waiting to spring. Suppose your post-processor needs a dependency:&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;AuditingProcessor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BeanPostProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;paymentService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// trouble&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build this post-processor, Spring must first build &lt;code&gt;PaymentService&lt;/code&gt; to inject it. But this is happening in the early phase — &lt;em&gt;before all the other post-processors are registered&lt;/em&gt;. So &lt;code&gt;PaymentService&lt;/code&gt; gets created too soon and skips the post-processors that are not set up yet. If one of them was going to give it a transaction proxy, that proxy never happens. Your &lt;code&gt;@Transactional&lt;/code&gt; silently does nothing.&lt;/p&gt;

&lt;p&gt;Spring even warns about it in the log, in a line worth recognising:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bean 'paymentService' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix is to keep post-processors lean. They should depend on as little as possible, and never pull in the very beans they are meant to process. A post-processor is infrastructure, built before the world exists — treat it that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  When many hooks compete: ordering
&lt;/h2&gt;

&lt;p&gt;Because these hooks decide the shape of every bean, the order they run in matters. Two post-processors could both want to wrap the same object; which one wins? Spring lets a hook declare its place by implementing &lt;strong&gt;Ordered&lt;/strong&gt; (or &lt;code&gt;PriorityOrdered&lt;/code&gt; for the ones that must run first of all), returning a number — lower runs earlier.&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;FirstInLine&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;BeanPostProcessor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Ordered&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;int&lt;/span&gt; &lt;span class="nf"&gt;getOrder&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="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// runs before higher numbers&lt;/span&gt;
    &lt;span class="c1"&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 same ordering applies to &lt;code&gt;BeanFactoryPostProcessor&lt;/code&gt;s among themselves. It rarely matters for hand-written hooks, but when two of them interact — two things both rewriting definitions, or two proxies stacking on one bean — &lt;code&gt;Ordered&lt;/code&gt; is how you make the outcome deliberate instead of accidental.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one mental model
&lt;/h2&gt;

&lt;p&gt;Strip it all back and it is a single picture. The container runs in two passes: &lt;strong&gt;collect the blueprints, then construct the buildings.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;BeanFactoryPostProcessor&lt;/strong&gt; works on the blueprints, between the passes. It sees bean &lt;em&gt;definitions&lt;/em&gt; and can rewrite them before anything is built. Placeholder resolution lives here.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;BeanPostProcessor&lt;/strong&gt; works on the buildings, during the second pass. It sees each live &lt;em&gt;bean&lt;/em&gt; as it is initialised and can wrap or replace it. Proxies — transactions, AOP, caching — live here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are called "post-processor" because both are Spring's way of stepping into its own construction line. Once you know which pass a hook belongs to, you always know what it is allowed to touch — a definition or an object — and every confusingly-named "…PostProcessor" in the framework falls neatly into one of the two.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Recap — M0 Foundations</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Tue, 16 Jun 2026 09:40:01 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/recap-m0-foundations-ee6</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/recap-m0-foundations-ee6</guid>
      <description>&lt;p&gt;This module built one thing, from many angles: the &lt;strong&gt;container&lt;/strong&gt; — the part of Spring that creates your objects, wires them together, and hands them out. Eight articles each zoomed in on a different corner of it. This recap zooms back out. The goal here is not to re-explain each topic, but to show how they are all the same idea seen from different sides, so the whole module collapses into a picture you can hold in your head at once.&lt;/p&gt;

&lt;p&gt;So before the details, here is the single sentence the entire module hangs on: &lt;strong&gt;the container is a factory that runs at startup, and almost every feature you met is just that factory doing a little extra work while it builds a bean.&lt;/strong&gt; Keep that sentence close. Everything below is a way of filling it in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The factory, in one picture
&lt;/h2&gt;

&lt;p&gt;Picture an assembly line that runs exactly once, when your application boots. You hand it a list of what to build and how the pieces fit. It builds every object your app is made of, connects them, sets them on a shelf, and hands them out on request for the rest of the program's life.&lt;/p&gt;

&lt;p&gt;That assembly line is the container. The objects it builds and manages are &lt;strong&gt;beans&lt;/strong&gt;. An object you create yourself with &lt;code&gt;new&lt;/code&gt; is not a bean — Spring never touched it — and that distinction is the thread running through every trap in this module.&lt;/p&gt;

&lt;p&gt;The factory does four things at startup, and the order matters: it reads recipes, works out who needs whom, builds from the bottom up, and caches each result.&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;ApplicationContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;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="nc"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&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;// already built and wired&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the time &lt;code&gt;run&lt;/code&gt; returns, the work is done. Asking for a bean is instant because the building already happened. Every other topic in the module is a detail about &lt;em&gt;how&lt;/em&gt; that one startup pass works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we hand the work over at all
&lt;/h2&gt;

&lt;p&gt;The module opened with a question of control. Left alone, a class builds its own collaborators with &lt;code&gt;new&lt;/code&gt; — and in doing so it welds together two unrelated decisions: &lt;em&gt;what&lt;/em&gt; it needs, and &lt;em&gt;which&lt;/em&gt; exact thing it gets and how that thing is built.&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;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;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&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;PaymentGateway&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;HttpClient&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;Pulling those two decisions apart is the whole point. The class should declare &lt;em&gt;what&lt;/em&gt; it needs and let something outside choose and supply the &lt;em&gt;which&lt;/em&gt;. Handing that choice to the framework is &lt;strong&gt;Inversion of Control&lt;/strong&gt; — your code stops creating its own collaborators. The way the collaborator then arrives is &lt;strong&gt;Dependency Injection&lt;/strong&gt; — it is passed in from outside.&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;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;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// declares the need, receives it&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;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IoC is the idea; DI is the delivery. And the delivery has a preferred form for a mechanical reason, not a stylistic one: &lt;strong&gt;constructor injection&lt;/strong&gt; makes the field &lt;code&gt;final&lt;/code&gt;, makes the object impossible to build half-wired, and makes it testable with a plain &lt;code&gt;new&lt;/code&gt; in a unit test. That preference for the constructor is a decision the rest of the module keeps cashing in.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a single bean comes to life
&lt;/h2&gt;

&lt;p&gt;Once you accept that the factory builds your beans, the natural next question is &lt;em&gt;how&lt;/em&gt; it builds one. The answer is a fixed four-stage sequence — the &lt;strong&gt;bean lifecycle&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instantiate&lt;/strong&gt; — construct the raw object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Populate&lt;/strong&gt; — inject field- and setter-style dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initialize&lt;/strong&gt; — run setup now that everything is wired.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destroy&lt;/strong&gt; — run teardown at an orderly shutdown.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The order is the lesson. A field-injected dependency is &lt;code&gt;null&lt;/code&gt; if you touch it in the constructor, because populate (stage 2) has not run yet. Setup that needs dependencies belongs in &lt;code&gt;@PostConstruct&lt;/code&gt; (stage 3), where wiring is guaranteed complete. Teardown mirrors it in &lt;code&gt;@PreDestroy&lt;/code&gt; (stage 4).&lt;/p&gt;

&lt;p&gt;Notice how this rewards constructor injection again: it folds populate into instantiate, so a constructor-injected bean is never half-there. The lifecycle is just the factory's per-bean routine, and most of its surprises come from forgetting which stage runs when.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many, and how long
&lt;/h2&gt;

&lt;p&gt;The lifecycle describes one bean's journey. &lt;strong&gt;Scope&lt;/strong&gt; answers the other two questions about it: how many instances exist, and how long each lives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Singleton&lt;/strong&gt; — one instance per container, the default, shared by everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototype&lt;/strong&gt; — a fresh instance on every request, built on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request&lt;/strong&gt; and &lt;strong&gt;session&lt;/strong&gt; — one instance per HTTP request or per user session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Shared," in the singleton line, is the dangerous word, and it leads to the module's most common bug. Because one singleton serves every thread at once, any &lt;em&gt;mutable&lt;/em&gt; field on it is a race condition.&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;class&lt;/span&gt; &lt;span class="nc"&gt;CartService&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;itemCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// one field, every thread, lost updates&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;itemCount&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 rule that falls out is short: &lt;strong&gt;keep singletons stateless.&lt;/strong&gt; Immutable shared state — your injected dependencies — is exactly what a singleton is good at; mutable shared state is the trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which container is doing all this
&lt;/h2&gt;

&lt;p&gt;Up to here, "the container" has been a single idea. In Spring's code it wears two names, and the difference matters. &lt;strong&gt;&lt;code&gt;BeanFactory&lt;/code&gt;&lt;/strong&gt; is the minimal contract — store recipes, build beans lazily on request. &lt;strong&gt;&lt;code&gt;ApplicationContext&lt;/code&gt;&lt;/strong&gt; extends it and &lt;em&gt;staffs&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;That staffing is the whole reason you always use the &lt;code&gt;ApplicationContext&lt;/code&gt;. Features like &lt;code&gt;@Autowired&lt;/code&gt; and &lt;code&gt;@PostConstruct&lt;/code&gt; are not part of the factory core — each is a plugin called a &lt;strong&gt;&lt;code&gt;BeanPostProcessor&lt;/code&gt;&lt;/strong&gt; that gets a crack at every bean during the lifecycle. The bare &lt;code&gt;BeanFactory&lt;/code&gt; registers none of them, so it silently ignores your annotations. The &lt;code&gt;ApplicationContext&lt;/code&gt; registers them all automatically, and it builds singletons &lt;strong&gt;eagerly&lt;/strong&gt; at startup — so a broken wiring fails at boot, on your machine, not three days later in production.&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;ApplicationContext&lt;/span&gt; &lt;span class="n"&gt;ctx&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;AnnotationConfigApplicationContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.shop"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// every annotation honored, every singleton already built&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same fail-fast instinct constructor injection showed with circular dependencies: surface the problem at the safest possible moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you tell the factory what exists — and how it finds it
&lt;/h2&gt;

&lt;p&gt;The factory needs recipes. Where they come from is the &lt;strong&gt;configuration style&lt;/strong&gt;, and Spring has had three, each fixing the last one's pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XML&lt;/strong&gt; — all wiring in an external file. Central, but verbose and string-typed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotations&lt;/strong&gt; — &lt;code&gt;@Component&lt;/code&gt; and friends mark the class itself. The default for your own code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java config&lt;/strong&gt; — &lt;code&gt;@Bean&lt;/code&gt; methods inside a &lt;code&gt;@Configuration&lt;/code&gt; class. The way to register classes you cannot annotate, like third-party objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three end as the same internal bean definitions, which is why one app mixes them freely. Java config carries one subtlety worth keeping: Spring wraps a &lt;code&gt;@Configuration&lt;/code&gt; class in a &lt;strong&gt;proxy&lt;/strong&gt; so that a call from one &lt;code&gt;@Bean&lt;/code&gt; method to another returns the &lt;em&gt;shared&lt;/em&gt; bean instead of building a second one.&lt;/p&gt;

&lt;p&gt;For the annotation style, &lt;strong&gt;component scanning&lt;/strong&gt; is how the factory actually finds your marked classes. &lt;code&gt;@ComponentScan&lt;/code&gt; walks a &lt;strong&gt;base package&lt;/strong&gt; and everything beneath it — and only ever downward, which is why the main class lives in the root package, and why a class above the scan's start point silently never registers. The &lt;strong&gt;stereotypes&lt;/strong&gt; — &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Repository&lt;/code&gt;, &lt;code&gt;@Controller&lt;/code&gt; — are just &lt;code&gt;@Component&lt;/code&gt; with a role attached, scanned identically, though &lt;code&gt;@Repository&lt;/code&gt; earns exception translation as a bonus.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the factory connects the beans
&lt;/h2&gt;

&lt;p&gt;With every bean found and defined, the last job is wiring them — &lt;strong&gt;autowiring&lt;/strong&gt;. The rule is resolution &lt;strong&gt;by type&lt;/strong&gt;: the container finds the one bean whose type fits a dependency and injects it. One match and the wiring is silent.&lt;/p&gt;

&lt;p&gt;The interesting part is the tie-break, when two beans fit one slot. The container refuses to guess and fails fast; you break the tie three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parameter name&lt;/strong&gt; — a quiet fallback that matches the injection point's name to a bean name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Primary&lt;/code&gt;&lt;/strong&gt; — one global default, declared on the bean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Qualifier&lt;/code&gt;&lt;/strong&gt; — a named choice at the injection point, which overrides &lt;code&gt;@Primary&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when a dependency might legitimately be absent, &lt;code&gt;Optional&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;ObjectProvider&amp;lt;T&amp;gt;&lt;/code&gt; make that explicit instead of crashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two threads that run through everything
&lt;/h2&gt;

&lt;p&gt;Step back and two ideas turn out to connect almost every topic.&lt;/p&gt;

&lt;p&gt;The first is the &lt;strong&gt;proxy&lt;/strong&gt; — a same-shaped stand-in the factory can hand you instead of the bare bean. It is not one feature; it is the same trick reused everywhere. It is how &lt;code&gt;@Transactional&lt;/code&gt; opens a transaction around your method. It is how a request-scoped bean gets injected into a singleton — the proxy resolves to the right per-request instance on each call. It is how a &lt;code&gt;@Configuration&lt;/code&gt; class avoids building a bean twice. And its &lt;em&gt;timing&lt;/em&gt; — created after &lt;code&gt;@PostConstruct&lt;/code&gt; runs — is why init code self-calling a proxied method quietly skips the wrapper. Once you see the proxy, a dozen "how does Spring even do that?" questions share one answer.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;fail-fast&lt;/strong&gt;: surface a mistake at the earliest, safest moment. Eager singleton startup catches broken wiring at boot. Constructor injection turns a circular dependency into a boot-time error instead of a production &lt;code&gt;StackOverflowError&lt;/code&gt;. An ambiguous autowire stops the app rather than picking the wrong bean. The strictness is the safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traps, as one family
&lt;/h2&gt;

&lt;p&gt;Almost every gotcha in this module is the same mistake wearing different clothes — a mismatch between &lt;strong&gt;when injection happens&lt;/strong&gt; and &lt;strong&gt;when you use the result&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A field-injected dependency touched in the constructor is &lt;code&gt;null&lt;/code&gt; — populate runs &lt;em&gt;after&lt;/em&gt; the constructor.&lt;/li&gt;
&lt;li&gt;A mutable field on a singleton races — one instance is wired once and shared across every thread.&lt;/li&gt;
&lt;li&gt;A prototype injected into a singleton freezes — injection is a one-time event, so you get one prototype forever; reach for &lt;code&gt;ObjectProvider&lt;/code&gt; to pull a fresh one each call.&lt;/li&gt;
&lt;li&gt;A proxied method self-called inside &lt;code&gt;@PostConstruct&lt;/code&gt; skips the proxy — the wrapper is not built yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See them together and they stop being four facts to memorize. They are all consequences of one truth: &lt;strong&gt;for a singleton, wiring happens exactly once, at startup.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one picture to keep
&lt;/h2&gt;

&lt;p&gt;If you keep only one thing from this module, keep the factory. The container reads recipes, builds your beans bottom-up in dependency order, can wrap each one in a proxy, caches the results, and fails fast when something does not fit. Inversion of Control is &lt;em&gt;why&lt;/em&gt; you hand the work over; dependency injection is &lt;em&gt;how&lt;/em&gt; the pieces arrive; the lifecycle is &lt;em&gt;how&lt;/em&gt; one bean is assembled; scope is &lt;em&gt;how many and how long&lt;/em&gt;; the &lt;code&gt;ApplicationContext&lt;/code&gt; is the fully-staffed factory that runs it; configuration, scanning, and autowiring are how it learns what to build and connects it all.&lt;/p&gt;

&lt;p&gt;Everything heavier you meet from here — AOP, transactions, web request handling, data access — is a variation on this one startup pass. The factory does not go away. It just keeps finding new work to do while it builds your beans.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>@Autowired resolution, @Qualifier/@Primary, injection types + circular-dependency gotcha</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Mon, 15 Jun 2026 10:44:01 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/autowired-resolution-qualifierprimary-injection-types-circular-dependency-gotcha-249h</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/autowired-resolution-qualifierprimary-injection-types-circular-dependency-gotcha-249h</guid>
      <description>&lt;p&gt;The last article left the container in a tidy state. Scanning has finished, and every class you marked is now a &lt;strong&gt;bean definition&lt;/strong&gt; sitting in the container, names and all. But a definition is just a recipe. The moment the container starts actually building beans, it hits the question this article is about: to build &lt;code&gt;OrderService&lt;/code&gt;, it needs to pass something into the &lt;code&gt;PaymentGateway&lt;/code&gt; slot in its constructor — so &lt;em&gt;which&lt;/em&gt; bean does it reach for?&lt;/p&gt;

&lt;p&gt;When exactly one bean fits, the answer is obvious and you never think about it. The interesting part is everything around that happy path: how the container makes the match, what happens when two beans fit the same slot, and how you steer the choice when it can't decide on its own.&lt;/p&gt;

&lt;p&gt;That whole job — the container finding and supplying the beans a dependency needs, instead of you naming each one by hand — is called &lt;strong&gt;autowiring&lt;/strong&gt;. You meet it for real the first time Spring refuses to start, complaining it found more than one bean of some type and doesn't know which you meant. To fix that on purpose instead of by trial and error, you need to see how resolution actually works.&lt;/p&gt;

&lt;p&gt;This article walks it end to end: the match by type, why &lt;code&gt;@Autowired&lt;/code&gt; is often already implied, the ambiguity that breaks the match, the three tools that break the tie, what to do when a bean might be missing — and finally a deeper look at the circular-dependency trap the dependency-injection article only cracked open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The happy path: resolution by type
&lt;/h2&gt;

&lt;p&gt;Start with the rule from the dependency-injection article, now seen from the container's side. When the container builds a bean and reaches a dependency, it looks through every bean it knows about for one whose &lt;strong&gt;type&lt;/strong&gt; fits, and injects that 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;@Service&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;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// "I need a PaymentGateway"&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;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is exactly one &lt;code&gt;PaymentGateway&lt;/code&gt; bean in the container, the match is unambiguous and the wiring just happens. You declared a need by type; the container found the one bean that satisfies it and handed it over. That is autowiring in its simplest, invisible form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where did &lt;code&gt;@Autowired&lt;/code&gt; go?
&lt;/h2&gt;

&lt;p&gt;You may have noticed there is no &lt;code&gt;@Autowired&lt;/code&gt; on that constructor. That is not an oversight. When a class has a &lt;strong&gt;single constructor&lt;/strong&gt;, Spring treats it as an injection point automatically — it assumes the parameters are dependencies to resolve, no annotation required.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;@Autowired&lt;/code&gt; only needs to appear in the cases Spring cannot infer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a class with &lt;strong&gt;more than one constructor&lt;/strong&gt;, to mark which one the container should call.&lt;/li&gt;
&lt;li&gt;On a &lt;strong&gt;field or setter&lt;/strong&gt;, where there is no sole constructor for Spring to assume.
&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;@Service&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="nd"&gt;@Autowired&lt;/span&gt;                                  &lt;span class="c1"&gt;// needed here only if there are other constructors&lt;/span&gt;
    &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&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 useful way to hold it: &lt;code&gt;@Autowired&lt;/code&gt; marks an &lt;strong&gt;injection point&lt;/strong&gt; — a spot that says "fill this in from the container." A parameter on a sole constructor is an injection point implicitly. Everywhere else, the annotation is how you point at one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When two beans fit one slot
&lt;/h2&gt;

&lt;p&gt;Now the situation that breaks the happy path. Suppose two classes implement the same interface, and both are beans:&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;StripeGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdyenGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;&lt;code&gt;OrderService&lt;/code&gt; still asks for a single &lt;code&gt;PaymentGateway&lt;/code&gt;. The container searches by type and now finds &lt;strong&gt;two&lt;/strong&gt; candidates. It has no basis to prefer one, and it will not guess. It fails at startup:&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;NoUniqueBeanDefinitionException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="nx"&gt;single&lt;/span&gt; &lt;span class="nx"&gt;matching&lt;/span&gt; &lt;span class="nx"&gt;bean&lt;/span&gt;
&lt;span class="nx"&gt;but&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stripeGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adyenGateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fail-fast philosophy again — an ambiguous wiring is caught at boot, on your machine, not silently resolved into the wrong gateway in production. Spring gives you three ways to resolve it, and they are worth knowing as a set, because they layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tie-breaker 1: the parameter name
&lt;/h2&gt;

&lt;p&gt;Before you reach for any annotation, Spring has a quiet fallback already running. When the type matches several beans, it tries to narrow them by &lt;strong&gt;name&lt;/strong&gt; — matching the name of the injection point against the bean names. Recall from the scanning article that a scanned bean is named after its class, decapitalized: &lt;code&gt;StripeGateway&lt;/code&gt; becomes &lt;code&gt;stripeGateway&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;@Service&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="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;stripeGateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// parameter name matches a bean name&lt;/span&gt;
        &lt;span class="c1"&gt;// resolves to the stripeGateway bean&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;Name the parameter &lt;code&gt;stripeGateway&lt;/code&gt; and the ambiguity disappears: the container matches it to the bean of that name. It works, but lean on it carefully. It depends on parameter names surviving compilation (Spring Boot compiles with that turned on, so they do), and it is &lt;strong&gt;subtle&lt;/strong&gt; — renaming a parameter silently rewires the application. It is fine as a fallback; it is a poor way to express intent. For that, be explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tie-breaker 2: &lt;code&gt;@Primary&lt;/code&gt; — declare a default winner
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Primary&lt;/code&gt; marks one bean as the default to use whenever several fit:&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="nd"&gt;@Primary&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdyenGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;Now any unqualified &lt;code&gt;PaymentGateway&lt;/code&gt; injection, anywhere in the app, gets Stripe. The key trait is that &lt;code&gt;@Primary&lt;/code&gt; lives on the &lt;strong&gt;bean&lt;/strong&gt; — one declaration, applied everywhere that type is needed. It is the right tool when there is one obvious default and only rare exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tie-breaker 3: &lt;code&gt;@Qualifier&lt;/code&gt; — name the one you want
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Primary&lt;/code&gt; decides globally. &lt;code&gt;@Qualifier&lt;/code&gt; decides at a single &lt;strong&gt;injection point&lt;/strong&gt;. You give the beans qualifier names and then ask for one by name right where you need 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;@Component&lt;/span&gt;
&lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stripe"&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;StripeGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"adyen"&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;AdyenGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&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;span class="nd"&gt;@Service&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="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stripe"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// this slot wants Stripe&lt;/span&gt;
        &lt;span class="c1"&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;And the two combine cleanly. When both are present, &lt;code&gt;@Qualifier&lt;/code&gt; at the injection point &lt;strong&gt;wins over&lt;/strong&gt; &lt;code&gt;@Primary&lt;/code&gt;. The idiom that falls out: mark the everyday default with &lt;code&gt;@Primary&lt;/code&gt;, and use &lt;code&gt;@Qualifier&lt;/code&gt; only at the few spots that need the exception. Most of the code stays quiet; the deviations are explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the bean might not be there
&lt;/h2&gt;

&lt;p&gt;The mirror image of ambiguity is absence. If a required dependency has &lt;strong&gt;no&lt;/strong&gt; matching bean, the container fails at boot with &lt;code&gt;NoSuchBeanDefinitionException&lt;/code&gt; — the same fail-fast instinct. Usually that is exactly what you want. But occasionally a dependency is genuinely optional: a metrics sink that may or may not be configured, say. Spring has three ways to say "inject it if it exists, otherwise carry on":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Autowired(required = false)&lt;/code&gt; — leaves a field or setter unset if no bean is found.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Optional&amp;lt;PaymentGateway&amp;gt;&lt;/code&gt; — arrives empty when the bean is absent.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ObjectProvider&amp;lt;PaymentGateway&amp;gt;&lt;/code&gt; — a lazy handle you ask for the bean only when you need it (the same type the bean-scopes article used to pull a fresh prototype on demand).
&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;@Service&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;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// empty if no gateway bean exists&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;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer &lt;code&gt;Optional&lt;/code&gt; or &lt;code&gt;ObjectProvider&lt;/code&gt; over &lt;code&gt;required = false&lt;/code&gt;: they keep the field &lt;code&gt;final&lt;/code&gt; and make the "might be absent" part visible in the type, instead of hiding it behind a field that is sometimes &lt;code&gt;null&lt;/code&gt;. (And note the &lt;code&gt;List&amp;lt;PaymentGateway&amp;gt;&lt;/code&gt; form from the DI article is a different case entirely — it asks for &lt;em&gt;all&lt;/em&gt; matching beans on purpose, so several matches are the answer, not an error.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Injection types, seen through resolution
&lt;/h2&gt;

&lt;p&gt;The DI article already made the case for constructor injection over setters and fields, so this is just the same picture from the resolver's angle: the matching algorithm above is identical no matter where the injection point sits. &lt;code&gt;@Qualifier&lt;/code&gt; and &lt;code&gt;@Primary&lt;/code&gt; work the same on a constructor parameter, a setter, or a field.&lt;/p&gt;

&lt;p&gt;What changes is &lt;em&gt;when&lt;/em&gt; resolution runs and how safely. &lt;strong&gt;Constructor&lt;/strong&gt; injection resolves everything at construction, so the object is never half-built and the fields can be &lt;code&gt;final&lt;/code&gt;. &lt;strong&gt;Setter&lt;/strong&gt; injection runs during the populate stage, which suits a truly optional or reconfigurable dependency. &lt;strong&gt;Field&lt;/strong&gt; injection is the most concise and the least testable. The resolution rules don't pick a style for you — but as the next section shows, the style you pick decides how one particular failure behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The circular-dependency gotcha, in full
&lt;/h2&gt;

&lt;p&gt;The DI article showed one slice of this: two beans that depend on each other, wired through constructors, fail at boot. Here is the complete picture, because the outcome depends entirely on the injection style — and that is the whole lesson.&lt;/p&gt;

&lt;p&gt;Say &lt;code&gt;A&lt;/code&gt; needs &lt;code&gt;B&lt;/code&gt;, and &lt;code&gt;B&lt;/code&gt; needs &lt;code&gt;A&lt;/code&gt;. With &lt;strong&gt;constructor injection on both sides&lt;/strong&gt;, neither object can be built first: building &lt;code&gt;A&lt;/code&gt; needs a finished &lt;code&gt;B&lt;/code&gt;, which needs a finished &lt;code&gt;A&lt;/code&gt;, and round it goes. The container detects the loop and stops:&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;BeanCurrentlyInCreationException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nx"&gt;Requested&lt;/span&gt; &lt;span class="nx"&gt;bean&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;currently&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;creation&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;there&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;unresolvable&lt;/span&gt; &lt;span class="nx"&gt;circular&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks like an obstacle. It is actually the good case — the cycle is exposed the instant you start the app.&lt;/p&gt;

&lt;p&gt;Now switch the two beans to &lt;strong&gt;field injection&lt;/strong&gt; and watch the failure quietly vanish:&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;A&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt; &lt;span class="no"&gt;B&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="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt; &lt;span class="n"&gt;a&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 field injection happens &lt;em&gt;after&lt;/em&gt; construction, Spring has a move available that constructors deny it. It builds the raw &lt;code&gt;A&lt;/code&gt; first, then — before &lt;code&gt;A&lt;/code&gt; is fully wired — exposes a reference to that half-built &lt;code&gt;A&lt;/code&gt;. It builds &lt;code&gt;B&lt;/code&gt;, setting &lt;code&gt;B&lt;/code&gt;'s field to the early &lt;code&gt;A&lt;/code&gt; reference. With &lt;code&gt;B&lt;/code&gt; now complete, it finishes wiring &lt;code&gt;A&lt;/code&gt; by setting &lt;code&gt;A&lt;/code&gt;'s field to the finished &lt;code&gt;B&lt;/code&gt;. The cycle resolves, and the application starts as if nothing were wrong.&lt;/p&gt;

&lt;p&gt;That "as if nothing were wrong" is the trap. A circular dependency is a real design flaw — two beans so entangled that neither is whole without the other — and field injection lets it ship undetected. Constructor injection would have failed loudly at boot and forced the conversation. This is one more reason the earlier articles pushed constructors so hard: the strictness is the safety.&lt;/p&gt;

&lt;p&gt;Spring offers an escape hatch, &lt;code&gt;@Lazy&lt;/code&gt;, for when you must break a cycle without redesigning right now. Put &lt;code&gt;@Lazy&lt;/code&gt; on one of the two injection points and the container injects a &lt;strong&gt;proxy&lt;/strong&gt; — the same stand-in trick from the first article — instead of the real bean, deferring the real lookup until the first method call. By then both beans exist, so the loop never has to be resolved at construction time.&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;A&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Lazy&lt;/span&gt; &lt;span class="no"&gt;B&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="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// injects a proxy now, resolves B on first use&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat that as a patch, not a cure. The honest fix is to break the cycle: pull the shared logic into a third bean both can depend on, or let one side publish an &lt;strong&gt;event&lt;/strong&gt; the other listens for, the way the &lt;code&gt;ApplicationContext&lt;/code&gt; article wired &lt;code&gt;OrderService&lt;/code&gt; to email without either knowing the other. A mutual dependency almost always means a responsibility is living in the wrong class.&lt;/p&gt;

&lt;p&gt;It is worth knowing that recent Spring Boot takes your side here: since 2.6 it &lt;strong&gt;forbids circular references by default&lt;/strong&gt;, so even a field-injected cycle fails at startup unless you deliberately set &lt;code&gt;spring.main.allow-circular-references=true&lt;/code&gt;. The framework now nudges everyone toward the fix instead of the papering-over.&lt;/p&gt;

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

&lt;p&gt;Autowiring is the container resolving each dependency &lt;strong&gt;by type&lt;/strong&gt;: one matching bean and the wiring is silent, with &lt;code&gt;@Autowired&lt;/code&gt; implied on a sole constructor and only spelled out for extra constructors, fields, and setters. When two beans fit one slot, the container fails fast rather than guess — and you break the tie three ways: the &lt;strong&gt;parameter name&lt;/strong&gt; (a subtle fallback), &lt;strong&gt;&lt;code&gt;@Primary&lt;/code&gt;&lt;/strong&gt; (one global default on the bean), or &lt;strong&gt;&lt;code&gt;@Qualifier&lt;/code&gt;&lt;/strong&gt; (a named choice at the injection point that overrides &lt;code&gt;@Primary&lt;/code&gt;). When a bean might be missing, &lt;code&gt;Optional&lt;/code&gt; or &lt;code&gt;ObjectProvider&lt;/code&gt; make the absence explicit instead of crashing.&lt;/p&gt;

&lt;p&gt;The deepest point is the one about cycles. Resolution itself doesn't care which injection style you used — but a circular dependency does. Constructor injection turns it into a boot-time error you cannot miss; field injection papers over it and lets a design flaw ship. That asymmetry, now reinforced by Boot's default of refusing cycles outright, is the last and strongest argument for wiring through the constructor.&lt;/p&gt;

&lt;p&gt;With that, the container's whole story is on the table: how it learns what your beans are, builds them, scopes them, and now connects them to each other. The next article steps back from the individual mechanisms and ties the entire Foundations module into one mental model — a recap of how every piece we have built fits together.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Component scanning &amp; stereotypes</title>
      <dc:creator>Ankit Verma</dc:creator>
      <pubDate>Sun, 14 Jun 2026 08:30:48 +0000</pubDate>
      <link>https://dev.to/ankit_verma_e2fa7fb2aa95d/component-scanning-stereotypes-79k</link>
      <guid>https://dev.to/ankit_verma_e2fa7fb2aa95d/component-scanning-stereotypes-79k</guid>
      <description>&lt;p&gt;The last article left a thread dangling. It showed that the annotation style marks a class as a bean by putting &lt;code&gt;@Component&lt;/code&gt; — or one of its cousins — right on the class, and then said the container finds those marked classes by &lt;em&gt;component scanning&lt;/em&gt;. It promised that scanning was "its own topic, the very next article." This is that article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component scanning&lt;/strong&gt; is the step where Spring walks through your code, finds the classes you marked as beans, and registers each one with the container. It is the bridge between "I annotated a class" and "the container knows that class exists." Without it, an &lt;code&gt;@Service&lt;/code&gt; is just an annotation sitting on a class Spring never bothers to look at.&lt;/p&gt;

&lt;p&gt;You meet scanning the first time a class you wrote refuses to show up. You added &lt;code&gt;@Service&lt;/code&gt;, you asked the container for it, and Spring answers that there is no such bean. Almost always the class is sitting in a package the scan never visited. To fix that with intent instead of guesswork, you need to know what scanning actually does and where it actually looks.&lt;/p&gt;

&lt;p&gt;This article walks the whole mechanism: how the scan finds your classes, the &lt;strong&gt;stereotype&lt;/strong&gt; annotations that mark them, the one stereotype that quietly does more than mark, where the search begins, and the traps that come from searching too narrowly or too widely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mark and the search are two halves of one thing
&lt;/h2&gt;

&lt;p&gt;Recall the single job of &lt;code&gt;@Component&lt;/code&gt;: it labels a class as something the container should build and manage — a bean. But a label does nothing on its own. Something has to go looking for it.&lt;/p&gt;

&lt;p&gt;That something is the &lt;strong&gt;component scanner&lt;/strong&gt;. At startup, before a single bean is built, the scanner walks a set of packages, inspects every class it finds, and for each class wearing &lt;code&gt;@Component&lt;/code&gt; it writes down a &lt;strong&gt;bean definition&lt;/strong&gt; — the recipe card from the earlier articles. When the scan finishes, the container holds a definition for every annotated class, exactly as if you had spelled them all out in Java config 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="nd"&gt;@Service&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="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&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;On its own, that class is inert. The annotation says "I am a bean"; the scan is what hears it. Miss either half — forget the annotation, or never scan the package it lives in — and the bean simply does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the scan on
&lt;/h2&gt;

&lt;p&gt;You rarely write the line that starts scanning, but it is worth seeing once, plainly. The annotation that switches it on is &lt;code&gt;@ComponentScan&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;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@ComponentScan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.shop"&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;AppConfig&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 says: walk the &lt;code&gt;com.shop&lt;/code&gt; package and everything beneath it, and register every &lt;code&gt;@Component&lt;/code&gt; you find. The string is a &lt;strong&gt;base package&lt;/strong&gt; — the root where the search begins. Scanning is &lt;strong&gt;recursive&lt;/strong&gt;, so &lt;code&gt;com.shop&lt;/code&gt;, &lt;code&gt;com.shop.orders&lt;/code&gt;, and &lt;code&gt;com.shop.billing.tax&lt;/code&gt; are all swept in by that one line.&lt;/p&gt;

&lt;p&gt;In a Spring Boot app you never actually write &lt;code&gt;@ComponentScan&lt;/code&gt;, because it is hidden inside the annotation on your main class. But it is running all the same, and knowing it is there is what makes the next trap avoidable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the search starts — and the classic trap
&lt;/h2&gt;

&lt;p&gt;The annotation on your main class, &lt;code&gt;@SpringBootApplication&lt;/code&gt;, is a bundle: it folds together &lt;code&gt;@Configuration&lt;/code&gt;, the auto-configuration switch, and &lt;code&gt;@ComponentScan&lt;/code&gt; — the last one with &lt;strong&gt;no package argument&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you give &lt;code&gt;@ComponentScan&lt;/code&gt; no package, it does not scan nothing. It defaults to the package of the class it sits on, and everything below 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="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.shop&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// the root package&lt;/span&gt;

&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;      &lt;span class="c1"&gt;// scans com.shop and downward&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShopApplication&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;ShopApplication&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;This is the reason every Boot project puts its main class in the &lt;strong&gt;root package&lt;/strong&gt;, above all the others. From there, the default scan naturally covers the whole application — &lt;code&gt;com.shop.web&lt;/code&gt;, &lt;code&gt;com.shop.billing&lt;/code&gt;, all of it.&lt;/p&gt;

&lt;p&gt;Now the trap, which catches nearly everyone once. Move that main class down into a sub-package by mistake:&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="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.shop.web&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// oops — not the root&lt;/span&gt;

&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;       &lt;span class="c1"&gt;// scans com.shop.web and downward ONLY&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShopApplication&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;Scanning now starts at &lt;code&gt;com.shop.web&lt;/code&gt;. Your &lt;code&gt;com.shop.billing&lt;/code&gt; package is &lt;em&gt;above&lt;/em&gt; the start point, so it is never visited, and none of its beans get registered. The symptom is a &lt;code&gt;NoSuchBeanDefinitionException&lt;/code&gt; at startup — Spring asking for a bean it was never told about. The fix is to move the main class back up to the root, or to name the packages explicitly with &lt;code&gt;@SpringBootApplication(scanBasePackages = "com.shop")&lt;/code&gt;. Either way, the cause is the same: the scan only ever looks down from where it starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stereotypes: same scan, different meaning
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Component&lt;/code&gt; is the generic mark. On top of it, Spring ships three specialized labels — &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Repository&lt;/code&gt;, and &lt;code&gt;@Controller&lt;/code&gt; — known together as the &lt;strong&gt;stereotype&lt;/strong&gt; annotations, because each names the &lt;em&gt;role&lt;/em&gt; a class plays in the usual layered design: business logic, data access, web entry point.&lt;/p&gt;

&lt;p&gt;Here is the detail that ties them to everything above. Each stereotype is itself &lt;strong&gt;meta-annotated&lt;/strong&gt; with &lt;code&gt;@Component&lt;/code&gt; — that is, &lt;code&gt;@Component&lt;/code&gt; is stamped on the annotation itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;          &lt;span class="c1"&gt;// &amp;lt;-- right here, on the annotation&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;Service&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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 the scanner treats "annotated with &lt;code&gt;@Component&lt;/code&gt;, directly or through another annotation" as the trigger, a &lt;code&gt;@Service&lt;/code&gt; is picked up by exactly the same scan as a bare &lt;code&gt;@Component&lt;/code&gt;. There is no separate machinery for them. A stereotype &lt;em&gt;is&lt;/em&gt; a &lt;code&gt;@Component&lt;/code&gt; with a name on it.&lt;/p&gt;

&lt;p&gt;So why bother with the specialized ones? Two reasons, both real but modest. They &lt;strong&gt;document the layer&lt;/strong&gt; at a glance — &lt;code&gt;@Repository&lt;/code&gt; tells a reader "this talks to the database" in a way &lt;code&gt;@Component&lt;/code&gt; never could. And they give Spring and your tools a hook to treat a layer specially. For most classes the stereotypes are interchangeable with &lt;code&gt;@Component&lt;/code&gt;; the habit is simply to pick the one that names what the class actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  @Repository earns more than its name
&lt;/h2&gt;

&lt;p&gt;One stereotype does more than label, and it is worth knowing exactly what. When the scan registers a &lt;code&gt;@Repository&lt;/code&gt; bean, Spring also wraps it in a &lt;strong&gt;proxy&lt;/strong&gt; — the same stand-in trick from the first article — to perform &lt;strong&gt;exception translation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is the problem it solves. Every persistence technology throws its own exceptions: raw JDBC throws &lt;code&gt;SQLException&lt;/code&gt;, Hibernate throws its own &lt;code&gt;HibernateException&lt;/code&gt;, and so on. If your service layer caught those directly, it would be welded to whichever library sits underneath.&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;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JpaOrderRepository&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;findById&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;id&lt;/span&gt;&lt;span class="o"&gt;)&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;span class="c1"&gt;// may hit a vendor-specific failure&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because this is a &lt;code&gt;@Repository&lt;/code&gt;, the proxy around it catches those vendor-specific exceptions on the way out and rethrows them as Spring's own &lt;code&gt;DataAccessException&lt;/code&gt; family — one consistent hierarchy, the same no matter what runs below. Swap JPA for plain JDBC tomorrow, and the exception types your service code catches do not change. That single benefit is a concrete reason to put &lt;code&gt;@Repository&lt;/code&gt; on data-access classes rather than a generic &lt;code&gt;@Component&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@Controller&lt;/code&gt; stereotype has its own special handling too — the web layer recognizes it and routes HTTP requests to its methods — but that belongs to the web module later. For now it is enough that the scan treats it like any other &lt;code&gt;@Component&lt;/code&gt;, and something downstream gives it extra meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every scanned bean gets a name
&lt;/h2&gt;

&lt;p&gt;When the scanner registers a bean, the container needs a name for it. By default, scanning takes the &lt;strong&gt;simple class name and decapitalizes the first letter&lt;/strong&gt;: &lt;code&gt;OrderService&lt;/code&gt; becomes the bean named &lt;code&gt;orderService&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;@Service&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="o"&gt;}&lt;/span&gt;      &lt;span class="c1"&gt;// registered under the name "orderService"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the time you never touch the name, because injection resolves by &lt;strong&gt;type&lt;/strong&gt;, as the dependency-injection article showed. The name starts to matter only when two beans share a type and you must point at one specifically — that tie-breaking is the next article's subject — or when two names collide.&lt;/p&gt;

&lt;p&gt;The collision is worth flagging because it fails loudly. Put two classes with the &lt;em&gt;same simple name&lt;/em&gt; in different packages, and both want to be &lt;code&gt;orderService&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="c1"&gt;// com.shop.billing.OrderService   -&amp;gt; wants "orderService"&lt;/span&gt;
&lt;span class="c1"&gt;// com.shop.legacy.OrderService    -&amp;gt; also wants "orderService"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scanning refuses the ambiguity and throws &lt;code&gt;ConflictingBeanDefinitionException&lt;/code&gt; at startup. That is the fail-fast philosophy again — a name clash is caught at boot, not silently resolved into the wrong bean. When you genuinely need two such classes, give one an explicit name with &lt;code&gt;@Service("legacyOrderService")&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scanning too wide
&lt;/h2&gt;

&lt;p&gt;By default the scan registers &lt;em&gt;everything&lt;/em&gt; annotated that it finds below the base package. Usually that is what you want. But a base package set too broad will quietly pull in classes you never meant to activate — a stray &lt;code&gt;@Configuration&lt;/code&gt; in some sub-module that switches a feature on, or test-only beans that have no business in production.&lt;/p&gt;

&lt;p&gt;For that, &lt;code&gt;@ComponentScan&lt;/code&gt; accepts &lt;strong&gt;filters&lt;/strong&gt; that include or exclude classes by annotation, type, or pattern:&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;@ComponentScan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;basePackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.shop"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;excludeFilters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@ComponentScan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FilterType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ANNOTATION&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Configuration&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;// skip stray @Configuration classes&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;AppConfig&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;Filters are the precise tool when the default sweep grabs too much. The blunter fix is the better default: keep your base packages &lt;strong&gt;tight&lt;/strong&gt;. A narrow scan registers only what you intend, and as a bonus it costs less at startup — walking a smaller slice of the classpath is faster than walking all of it. Over-broad scanning is one of the quiet reasons a large application boots slowly.&lt;/p&gt;

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

&lt;p&gt;Component scanning is how the annotation style turns marked classes into registered beans. The &lt;strong&gt;scanner&lt;/strong&gt; walks a base package and everything under it, and for every class carrying &lt;code&gt;@Component&lt;/code&gt; — directly or through a stereotype — it writes a bean definition into the container. The annotation declares the intent; the scan is what acts on it, and both halves are required.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;base package&lt;/strong&gt; is where the search starts and only ever goes downward — which is why the main class lives in the root package, and why a class above the scan's start point silently never registers. The &lt;strong&gt;stereotypes&lt;/strong&gt; — &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Repository&lt;/code&gt;, &lt;code&gt;@Controller&lt;/code&gt; — are &lt;code&gt;@Component&lt;/code&gt; with a role attached; they are scanned identically, but &lt;code&gt;@Repository&lt;/code&gt; also earns exception translation, and &lt;code&gt;@Controller&lt;/code&gt; is later picked up by the web layer. Each scanned bean gets a decapitalized class name by default, and a name clash fails fast at boot.&lt;/p&gt;

&lt;p&gt;So the scan ends with the container holding a complete set of bean definitions, names and all. Which raises the question the next article answers: when the container goes to build &lt;code&gt;OrderService&lt;/code&gt; and sees it needs a &lt;code&gt;PaymentGateway&lt;/code&gt; — and &lt;em&gt;two&lt;/em&gt; beans happen to fit that type — how does it decide which one to inject? That is the job of &lt;code&gt;@Autowired&lt;/code&gt;, &lt;code&gt;@Qualifier&lt;/code&gt;, and &lt;code&gt;@Primary&lt;/code&gt;, and it is where we go next.&lt;/p&gt;

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