<?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>Forem: Piyush Kumar Singh</title>
    <description>The latest articles on Forem by Piyush Kumar Singh (@piyush_kumarsingh_da3833).</description>
    <link>https://forem.com/piyush_kumarsingh_da3833</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909333%2Fe0081e0c-5341-42fe-b711-92dafd3e0fdb.jpg</url>
      <title>Forem: Piyush Kumar Singh</title>
      <link>https://forem.com/piyush_kumarsingh_da3833</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/piyush_kumarsingh_da3833"/>
    <language>en</language>
    <item>
      <title>https://dev.to/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n</title>
      <dc:creator>Piyush Kumar Singh</dc:creator>
      <pubDate>Sun, 03 May 2026 16:48:10 +0000</pubDate>
      <link>https://forem.com/piyush_kumarsingh_da3833/-h21</link>
      <guid>https://forem.com/piyush_kumarsingh_da3833/-h21</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n" class="crayons-story__hidden-navigation-link"&gt;How Redis Actually Works — RAM, Single Thread, and the Expiry Behavior Nobody Explains&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/piyush_kumarsingh_da3833" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909333%2Fe0081e0c-5341-42fe-b711-92dafd3e0fdb.jpg" alt="piyush_kumarsingh_da3833 profile" class="crayons-avatar__image" width="720" height="720"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/piyush_kumarsingh_da3833" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Piyush Kumar Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Piyush Kumar Singh
                
              
              &lt;div id="story-author-preview-content-3604782" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/piyush_kumarsingh_da3833" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909333%2Fe0081e0c-5341-42fe-b711-92dafd3e0fdb.jpg" class="crayons-avatar__image" alt="" width="720" height="720"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Piyush Kumar Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n" id="article-link-3604782"&gt;
          How Redis Actually Works — RAM, Single Thread, and the Expiry Behavior Nobody Explains
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/redis"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;redis&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwareengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwareengineering&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How Redis Actually Works — RAM, Single Thread, and the Expiry Behavior Nobody Explains</title>
      <dc:creator>Piyush Kumar Singh</dc:creator>
      <pubDate>Sun, 03 May 2026 16:45:01 +0000</pubDate>
      <link>https://forem.com/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n</link>
      <guid>https://forem.com/piyush_kumarsingh_da3833/how-redis-actually-works-ram-single-thread-and-the-expiry-behavior-nobody-explains-2j4n</guid>
      <description>&lt;p&gt;A RAM read takes about 100 nanoseconds. A disk read — even on a modern SSD — takes around 100,000 nanoseconds. That single gap explains most of Redis’s speed, before it does a single thing clever. Friend’s Link&lt;/p&gt;

&lt;p&gt;But RAM alone isn’t the full story. The other half is a design decision that looks like a limitation on paper — and turns out to be one of the smartest choices in the codebase. More on that in a moment. Here’s what’s actually happening inside Redis when your app talks to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why is Redis so fast?&lt;/strong&gt;
&lt;/h2&gt;

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

&lt;p&gt;The first reason is obvious once you hear it: Redis keeps everything in RAM. Your PostgreSQL instance, however well-tuned, writes to disk. Redis doesn’t. Every key lives in memory, which is why a GET on a Redis key can return in under a millisecond even under load. There’s no disk seek, no page cache miss, no I/O wait. But here’s where most explanations stop — and they shouldn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Single-threaded — and that’s the point&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Redis processes one command at a time—one thread. No parallelism, no concurrency. That sounds like a bottleneck. It’s actually a feature.&lt;/p&gt;

&lt;p&gt;In a multi-threaded system, shared state requires locks. Locks mean threads waiting on each other. Waiting introduces latency spikes that are hard to reproduce and harder to debug. Redis avoids the entire problem by never having two threads compete for the same data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# These three clients connect simultaneously
Client 1: SET counter 100     ← executes fully first
Client 2: INCR counter        ← executes next, sees 100, returns 101
Client 3: GET counter         ← executes last, returns 101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order is deterministic. Always. You can reason about it. With threads and locks, you can’t—not without careful synchronization, which adds complexity and latency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redis is fast, not just because of RAM, but because it never waits on itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The six data structures — with the tradeoffs that actually matter&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Redis isn’t just strings. Each data structure solves a specific problem, and knowing when to pick one over another is more useful than knowing the commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;String&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET user:1001:name "John"
GET user:1001:name        # "John"
SET page:views 0
INCR page:views           # atomic - safe under concurrent load
GET page:views            # "1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default choice for simple values, flags, and counters. INCR is atomic — a thousand clients calling it simultaneously will never produce a wrong count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HSET user:1001 name "John" email "j@example.com" role "admin"
HGET user:1001 name       # "John"
HGETALL user:1001         # all fields
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Hash is better than a String when you have a structured object with multiple fields you’ll update independently. If you stored this as a JSON string, updating a single field means deserializing the whole blob, changing one value, and reserializing. A Hash lets you update one field with one command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPUSH notifications:1001 "Order shipped"
RPUSH notifications:1001 "Payment received"
LRANGE notifications:1001 0 -1   # all items in order
LPOP notifications:1001           # "Order shipped"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lists maintain insertion order. Use them for notification feeds, activity timelines, and simple job queues where you’re okay with at-most-once delivery. If you need guaranteed delivery, a List isn’t enough — use Kafka or RabbitMQ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sorted Set&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZADD leaderboard 9500 "alice"
ZADD leaderboard 11200 "John"
ZREVRANGE leaderboard 0 2 WITHSCORES
# John    11200
# alice   9500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every member has a score. Redis keeps them sorted automatically. Real-time leaderboards, priority queues, and rate limiting windows — sorted sets handle all three. The reason to reach for this over a List is when rank or score matters, not just insertion order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SADD online:users "user:1001" "user:1002"
SISMEMBER online:users "user:1002"   # 1 (true)
SCARD online:users                   # 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No duplicates, O(1) membership check. Good for tracking online users, visited pages, or anything where “is X in this group” is the question. Use a Set over a List when you need uniqueness and don’t care about order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HyperLogLog&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PFADD page:views:home "ip1" "ip2" "ip3" "ip1"
PFCOUNT page:views:home   # 3 (deduplicated)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HyperLogLog gives you approximate unique counts using a fixed 12KB of memory — regardless of whether you have 1,000 or 100 million unique values. A plain Set would work too, but each unique member consumes memory. For a site with 50 million daily visitors, the Set version could eat gigabytes. HyperLogLog stays at 12KB with a ~0.81% error margin. That tradeoff is almost always worth it for analytics.&lt;/p&gt;

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

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  TTL and the expiry behavior nobody explains
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET session:abc123 "user_data" EX 3600   # expires in 1 hour
TTL session:abc123                        # 3597
# one hour later
GET session:abc123                        # (nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most developers assume Redis runs a background job that scans for expired keys and deletes them at the exact moment of expiry. It doesn’t. That would be expensive — imagine scanning millions of keys every second. Instead, Redis uses two strategies in parallel:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy deletion&lt;/strong&gt;: When you read a key, Redis checks its expiry first. If it’s expired, Redis deletes it right then and returns nil. Memory is reclaimed at access time, not expiry time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active sampling&lt;/strong&gt;: Every 100ms, Redis randomly picks 20 keys that have TTLs set. If more than 25% of them are expired, it runs the loop again immediately. It keeps looping until the expired ratio drops below 25%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The consequence&lt;/strong&gt;: if you have 10 million keys expiring at 3 am and nothing reads them, the active sampler will gradually clean them up over the following minutes. Your memory won’t drop instantly. If you’re sizing Redis memory around key expiry, that lag is real, and you need to account for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistence — what survives a restart
&lt;/h2&gt;

&lt;p&gt;Redis lives in RAM. Restart the process, lose everything — unless you’ve configured persistence.&lt;/p&gt;

&lt;h2&gt;
  
  
  RDB snapshots
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# redis.conf
&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;       &lt;span class="c"&gt;# snapshot if 1+ keys changed in 15 minutes
&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;      &lt;span class="c"&gt;# snapshot if 10+ keys changed in 5 minutes
&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;    &lt;span class="c"&gt;# snapshot if 10000+ keys changed in 1 minute
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis forks a child process and writes everything to dump.rdb. Fast to recover from. The risk: if your server crashes between snapshots, you lose whatever happened in that window. Fine for cache. Not fine for anything where losing recent writes matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  AOF — Append Only File
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# redis.conf
&lt;/span&gt;&lt;span class="n"&gt;appendonly&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;appendfsync&lt;/span&gt; &lt;span class="n"&gt;everysec&lt;/span&gt;   &lt;span class="c"&gt;# flush to disk every second
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every write command gets appended to a log. On restart, Redis replays the log. With every second, you lose at most one second of data. With always, you lose nothing, but your write throughput drops noticeably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production setup — use both
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;appendonly&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;appendfsync&lt;/span&gt; &lt;span class="n"&gt;everysec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RDB handles fast restarts. AOF handles durability. Together, they cover both failure modes without adding much overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Spring Boot integration — with the why behind the config&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dependencies and connection&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- pom.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-data-redis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    timeout: 2000ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RedisTemplate — why you need a custom serializer&lt;/strong&gt;&lt;br&gt;
By default, Spring uses Java serialization for values. That works, but it stores class names alongside data, making keys unreadable and tying you to your class structure. Switch to JSON serialization so your data is readable outside Spring 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;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RedisConnectionFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;template&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;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKeySerializer&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;StringRedisSerializer&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// Jackson JSON instead of Java serialization&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setValueSerializer&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;GenericJackson2JsonRedisSerializer&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;template&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;@Cacheable&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;
&lt;span class="nc"&gt;Spring&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;caching&lt;/span&gt; &lt;span class="n"&gt;abstraction&lt;/span&gt; &lt;span class="n"&gt;lets&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt; &lt;span class="n"&gt;caching&lt;/span&gt; &lt;span class="n"&gt;without&lt;/span&gt; &lt;span class="n"&gt;touching&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;The&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="n"&gt;subsequent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;same&lt;/span&gt; &lt;span class="no"&gt;ID&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nl"&gt;Redis:&lt;/span&gt;

&lt;span class="nd"&gt;@Cacheable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUserById&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;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@CacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#user.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// evicts stale cache on update&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableCaching&lt;/span&gt;   &lt;span class="c1"&gt;// don't forget this&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;Application&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;Rate limiting with sorted sets&lt;/strong&gt;&lt;br&gt;
A sliding window rate limiter is one of Redis’s cleanest use cases. The sorted set score is the timestamp — so you can count requests within a time window with a range query:&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isAllowed&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;userId&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;maxRequests&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;windowSeconds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ratelimit:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&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;now&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="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;windowStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windowSeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;ZSetOperations&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForZSet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeRangeByScore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowStart&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// drop old requests&lt;/span&gt;
    &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;zCard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxRequests&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expire&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowSeconds&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  When your tech lead says “add Redis,” — ask this first
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
There’s a version of this story everyone knows: the tech lead says “add Redis,” you add Redis, and something gets faster. Nobody questions it. But Redis has real constraints, and using it wrong is a common way to create problems that look like infrastructure issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t use it as your primary database&lt;/strong&gt;. Redis has no foreign keys, no joins, no complex queries. If your data has relationships, use a relational database. Redis is the layer on top, not the foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t store large values&lt;/strong&gt;. Redis works well with small, hot data. A 5MB JSON blob in Redis is possible and wasteful — you’re burning expensive RAM, hurting the event loop for every other client, and making serialization your bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t use pub/sub for anything you can’t afford to lose&lt;/strong&gt;. Redis pub/sub has no persistence. If a subscriber goes offline for 30 seconds, those messages are gone. Use Kafka or RabbitMQ when reliability matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set a memory limit and eviction policy&lt;/strong&gt; — always. Without it, Redis will reject writes when it runs out of memory, and that failure mode is jarring in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru   # evict least recently used keys when full
**

## ```


The one line that ties it all together
**
Redis is fast because it stays in RAM and never waits for itself. Use it for caching, sessions, leaderboards, rate limiting, and lightweight pub/sub, where dropped messages are acceptable. Don’t ask it to be your source of truth. Understand those two constraints, and Redis stops being magic — it becomes a predictable tool that does exactly what you’d expect. That’s a good thing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>redis</category>
      <category>softwareengineering</category>
      <category>database</category>
      <category>backend</category>
    </item>
    <item>
      <title>How Spring Security Works Internally (Filters, Authentication &amp; Authorization Explained)</title>
      <dc:creator>Piyush Kumar Singh</dc:creator>
      <pubDate>Sat, 02 May 2026 16:57:49 +0000</pubDate>
      <link>https://forem.com/piyush_kumarsingh_da3833/how-spring-security-works-internally-filters-authentication-authorization-explained-2686</link>
      <guid>https://forem.com/piyush_kumarsingh_da3833/how-spring-security-works-internally-filters-authentication-authorization-explained-2686</guid>
      <description>&lt;p&gt;If you have worked with Spring Boot for a while, you have used Spring Security without fully tracing what happens inside it.&lt;/p&gt;

&lt;p&gt;You add a dependency, configure a SecurityFilterChain, and wire a UserDetailsService, and your APIs are suddenly protected. It works. But under the hood, there is a very disciplined flow that decides who the user is, whether the password is valid, and whether the request should even reach your controller.&lt;/p&gt;

&lt;p&gt;Once that internal flow clicks, Spring Security stops feeling magical and starts feeling predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Big Picture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every incoming HTTP request does not go straight to your controller. Before that request reaches DispatcherServlet, it passes through the servlet filter chain. Spring Security plugs itself into that chain and intercepts the request early.&lt;/p&gt;

&lt;p&gt;That matters because security decisions should happen before business logic runs. The flow looks like this:&lt;/p&gt;

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

&lt;p&gt;That is the full security journey in one line: intercept, authenticate, authorize, then continue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: The Request Enters the Servlet Filter Chain&lt;/strong&gt;&lt;br&gt;
When a client sends a request, Tomcat receives it first. Tomcat then passes it through a chain of servlet filters.&lt;/p&gt;

&lt;p&gt;These filters are not specific to Spring Security. They are part of the servlet infrastructure. Any framework can register filters here. Spring Security registers one important filter called FilterChainProxy.&lt;/p&gt;

&lt;p&gt;You can think of FilterChainProxy as the front desk for all Spring Security logic. It does not do all the security work itself. Instead, it decides which internal security filters should handle the request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: FilterChainProxy Picks the Right SecurityFilterChain&lt;/strong&gt;&lt;br&gt;
This is a key part that many developers miss. Spring Security does not always use one universal chain for every request. It can maintain multiple SecurityFilterChain configurations, each tied to different URL patterns or request matchers.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;/api/** may use JWT authentication&lt;br&gt;
/admin/** may require stricter role checks&lt;br&gt;
/login may use a form login&lt;br&gt;
FilterChainProxy checks the request and selects the correct chain using RequestMatcher. That means Spring Security is not just a collection of filters. It is a smart router for security filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Authentication Filter Extracts Credentials&lt;/strong&gt;&lt;br&gt;
Once the correct security chain is selected, one of the authentication filters takes over. In the classic username-password login flow, that filter is usually UsernamePasswordAuthenticationFilter.&lt;/p&gt;

&lt;p&gt;Its job is simple:&lt;/p&gt;

&lt;p&gt;Read the username and password from the request&lt;br&gt;
Create an unauthenticated Authentication object&lt;br&gt;
Pass that object to AuthenticationManager&lt;br&gt;
At this point, the user is not yet trusted. The filter has only collected credentials. Verification still has to happen. This distinction is important. Extracting credentials and validating credentials are two separate responsibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: AuthenticationManager Coordinates Authentication&lt;/strong&gt;&lt;br&gt;
AuthenticationManager is the entry point for authentication logic. In most applications, the default implementation is ProviderManager.&lt;/p&gt;

&lt;p&gt;ProviderManager does not usually authenticate the user directly. Instead, it delegates to one of the configured AuthenticationProvider implementations. That design makes Spring Security flexible. Different providers can handle different authentication mechanisms:&lt;/p&gt;

&lt;p&gt;username and password&lt;br&gt;
JWT token&lt;br&gt;
OAuth2 login&lt;br&gt;
LDAP&lt;br&gt;
custom authentication rules&lt;br&gt;
When ProviderManager receives an authentication object, it loops through the registered providers and calls supports() on each one.&lt;/p&gt;

&lt;p&gt;The first provider that says, “Yes, I know how to handle this type of authentication,” gets the job. Then authenticate() is called on that provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: AuthenticationProvider Verifies the User&lt;/strong&gt;&lt;br&gt;
This is where the real authentication work happens. For username-password login, the provider is often DaoAuthenticationProvider.&lt;/p&gt;

&lt;p&gt;Its job usually includes two things:&lt;/p&gt;

&lt;p&gt;Load the user from a data source&lt;br&gt;
Validate the submitted password&lt;br&gt;
To load the user, it calls UserDetailsService.&lt;/p&gt;

&lt;p&gt;To validate the password, it uses PasswordEncoder.&lt;/p&gt;

&lt;p&gt;This split is one of the reasons Spring Security is so clean internally. Fetching user data and checking password hashing are handled by dedicated components, not mixed into one giant class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: UserDetailsService Loads the User From the Database&lt;/strong&gt;&lt;br&gt;
UserDetailsService is a very small but important contract.&lt;/p&gt;

&lt;p&gt;Its core method is:&lt;/p&gt;

&lt;p&gt;loadUserByUsername(String username)&lt;br&gt;
This method is responsible for fetching the user from your database, external system, or custom source. It returns a UserDetails object that contains:&lt;/p&gt;

&lt;p&gt;username&lt;br&gt;
password&lt;br&gt;
roles or authorities&lt;br&gt;
account status flags, such as locked or disabled&lt;br&gt;
If the user is not found, Spring Security throws an exception, and authentication fails. At this stage, the system now knows what the stored user record looks like. Next, it needs to compare the password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: PasswordEncoder Validates the Password&lt;/strong&gt;&lt;br&gt;
Spring Security does not compare raw passwords directly. Instead, it uses a PasswordEncoder such as BCryptPasswordEncoder.&lt;/p&gt;

&lt;p&gt;Here is the idea:&lt;/p&gt;

&lt;p&gt;The password submitted by the client is plain text&lt;br&gt;
The stored password in the database is hashed&lt;br&gt;
PasswordEncoder.matches(rawPassword, encodedPassword) checks if they match safely&lt;br&gt;
If the password is wrong, authentication fails.&lt;/p&gt;

&lt;p&gt;If it matches, Spring Security creates a fully authenticated Authentication object containing the user’s identity and authorities. That object is now trusted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8: SecurityContextHolder Stores the Authenticated User&lt;/strong&gt;&lt;br&gt;
Once authentication succeeds, Spring Security stores the authenticated user in SecurityContextHolder.&lt;/p&gt;

&lt;p&gt;This is what makes the user available for the rest of the request lifecycle.&lt;/p&gt;

&lt;p&gt;From here, other parts of the application can access the logged-in user through:&lt;/p&gt;

&lt;p&gt;SecurityContextHolder.getContext().getAuthentication()&lt;br&gt;
@AuthenticationPrincipal&lt;br&gt;
Principal in controllers&lt;br&gt;
In a regular servlet application, this security context is usually stored per request thread. That is why the controller can later know who the current user is without manually passing user details around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Authorization Happens Before the Controller&lt;/strong&gt;&lt;br&gt;
Authentication answers this question: Who are you?&lt;/p&gt;

&lt;p&gt;Authorization answers this one: What are you allowed to do?&lt;/p&gt;

&lt;p&gt;After the user is authenticated, Spring Security moves to authorization filters such as AuthorizationFilter.&lt;/p&gt;

&lt;p&gt;This stage checks whether the current user has the required role, authority, or permission for the requested resource.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;hasRole(“ADMIN”)&lt;br&gt;
hasAuthority(“PAYMENT_READ”)&lt;br&gt;
Request matchers that restrict endpoints&lt;br&gt;
If authorization fails, Spring Security stops the request and returns an error such as 403 Forbidden. If authorization succeeds, the request continues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10: The Request Reaches DispatcherServlet and Then the Controller&lt;/strong&gt;&lt;br&gt;
Only after authentication and authorization are complete does the request proceed to Spring MVC. Now, DispatcherServlet can route the request to the correct controller.&lt;/p&gt;

&lt;p&gt;At this point, your controller can safely assume one of two things:&lt;/p&gt;

&lt;p&gt;The endpoint is public, or&lt;br&gt;
The user has already been authenticated and authorized&lt;br&gt;
That separation is why controllers stay cleaner. Security is handled earlier in the pipeline instead of being scattered across business logic.&lt;/p&gt;

&lt;p&gt;What Happens If Authentication Fails?&lt;br&gt;
If authentication fails anywhere in the chain, Spring Security throws an authentication-related exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common outcomes include:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;401 Unauthorized for unauthenticated access&lt;br&gt;
Redirect to the login page in form-based login&lt;br&gt;
custom error response in REST APIs&lt;br&gt;
The controller is never called.&lt;/p&gt;

&lt;p&gt;This is a useful mental model: failed authentication stops the request before business logic begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Makes Spring Security Feel Complex?&lt;/strong&gt;&lt;br&gt;
Usually, it is not the concepts. It is the number of moving parts.&lt;/p&gt;

&lt;p&gt;There are many classes involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FilterChainProxy&lt;/li&gt;
&lt;li&gt;SecurityFilterChain&lt;/li&gt;
&lt;li&gt;UsernamePasswordAuthenticationFilter&lt;/li&gt;
&lt;li&gt;AuthenticationManager&lt;/li&gt;
&lt;li&gt;ProviderManager&lt;/li&gt;
&lt;li&gt;AuthenticationProvider&lt;/li&gt;
&lt;li&gt;UserDetailsService&lt;/li&gt;
&lt;li&gt;PasswordEncoder&lt;/li&gt;
&lt;li&gt;SecurityContextHolder&lt;/li&gt;
&lt;li&gt;AuthorizationFilter
At first glance, that looks like a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if you group them by responsibility, it becomes manageable:&lt;/p&gt;

&lt;p&gt;_Filters _handle request interception&lt;br&gt;
_Manager _and providers handle authentication delegation&lt;br&gt;
_UserDetailsService _and PasswordEncoder validate identity&lt;br&gt;
_SecurityContextHolder _stores the authenticated user&lt;br&gt;
_Authorization _filters enforce access rules&lt;br&gt;
That is really the whole story.&lt;/p&gt;

&lt;p&gt;A Simple Way to Remember the Flow&lt;/p&gt;

&lt;p&gt;Use this line:&lt;/p&gt;

&lt;p&gt;Request comes in -&amp;gt; filter intercepts -&amp;gt; credentials extracted -&amp;gt; manager delegates -&amp;gt; provider authenticates -&amp;gt; context stores user -&amp;gt; authorization checks access -&amp;gt; controller runs&lt;/p&gt;

&lt;p&gt;If you remember that sentence, you already understand the internals better than many developers who use Spring Security every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Spring Security works like a layered checkpoint system. It intercepts the request before your application code sees it, verifies identity using providers and encoders, stores the authenticated user in a security context, checks permissions, and only then allows the request to hit the controller. Once you understand that flow, the framework feels a lot less intimidating.&lt;/p&gt;

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