<?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: Renato Fialho</title>
    <description>The latest articles on DEV Community by Renato Fialho (@fialhorenato).</description>
    <link>https://dev.to/fialhorenato</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%2F675958%2Ff509f3de-7835-4f99-8a21-00afb6ff7181.jpeg</url>
      <title>DEV Community: Renato Fialho</title>
      <link>https://dev.to/fialhorenato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fialhorenato"/>
    <language>en</language>
    <item>
      <title>One Load per Key: Collapsing Duplicate Lookups with Spring Boot's Cache and Caffeine</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Fri, 23 Oct 2026 21:01:53 +0000</pubDate>
      <link>https://dev.to/fialhorenato/one-load-per-key-collapsing-duplicate-lookups-with-spring-boots-cache-and-caffeine-390p</link>
      <guid>https://dev.to/fialhorenato/one-load-per-key-collapsing-duplicate-lookups-with-spring-boots-cache-and-caffeine-390p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fox474j8geowm9czpbjyx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fox474j8geowm9czpbjyx.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rule engines, aggregators and validation pipelines share a hidden cost. Many independent components ask the same downstream question, with the same key, at the same moment. Each component is correct on its own; together they multiply the load on whatever sits behind them.&lt;/p&gt;

&lt;p&gt;This article shows how to collapse those calls into a single load using Spring's cache abstraction backed by &lt;a href="https://github.com/ben-manes/caffeine" rel="noopener noreferrer"&gt;Caffeine&lt;/a&gt;. It also covers what tends to bite afterwards: cached empty results, metrics that change meaning, and tests that pass for the wrong reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Take a promotion eligibility service. To decide whether a product qualifies for a promotion, it evaluates about 20 rules in parallel: in stock, price above a threshold, seller in good standing, category not excluded, and so 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="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each rule depends on a small, focused service:&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;InStockRule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Rule&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;StockService&lt;/span&gt; &lt;span class="n"&gt;stockService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;test&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;productId&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;stockService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isInStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StockService&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;ProductCatalogue&lt;/span&gt; &lt;span class="n"&gt;catalogue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isInStock&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;productId&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;catalogue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&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="na"&gt;orElse&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="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;PricingService&lt;/code&gt;, &lt;code&gt;SellerService&lt;/code&gt; and &lt;code&gt;CategoryService&lt;/code&gt; look the same: each one reads a different field from the same &lt;code&gt;Product&lt;/code&gt;, which comes from one place:&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;ProductCatalogue&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Reads from a shared cache (Redis, DynamoDB, ...), falls back to the catalogue API on a miss.&lt;/span&gt;
    &lt;span class="kd"&gt;public&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;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findProduct&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;productId&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;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;or&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchAndStore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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 design is clean: every rule is small, testable and independent. But one evaluation of one product calls &lt;code&gt;findProduct&lt;/code&gt; about 20 times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Warm path:&lt;/strong&gt; 20 reads from the shared cache, plus 20 deserialisations of the same object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold path:&lt;/strong&gt; all 20 calls miss at the same time, so the catalogue API gets 20 concurrent requests for the same product. That's a cache stampede, and it hits hardest when the downstream system is already struggling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calls don't need 20 answers. They need one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not request scope?
&lt;/h2&gt;

&lt;p&gt;The first idea is usually a request-scoped cache: memoise the response for the lifetime of the HTTP request. That works less well than it sounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thread hopping.&lt;/strong&gt; The rules run on executor threads. Request attributes are bound to the request thread, so request-scoped beans only work if you propagate the context to every executor (for example with a &lt;code&gt;TaskDecorator&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No request at all.&lt;/strong&gt; Queue consumers, Kafka listeners and scheduled jobs have no HTTP request. A &lt;code&gt;@RequestScope&lt;/code&gt; bean there fails with &lt;code&gt;No thread-bound request found&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A short-lived, in-process cache gives the same effect on every entry point. The price is bounded staleness, and with a short enough TTL that price is negligible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;spring-boot-starter-cache&lt;/code&gt; brings Spring's cache abstraction, but not Caffeine. Add both. Spring Boot manages the Caffeine version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;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-cache&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.github.ben-manes.caffeine&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;caffeine&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.springframework.boot:spring-boot-starter-cache"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.github.ben-manes.caffeine:caffeine"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable caching
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableCaching&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure the cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caffeine&lt;/span&gt;
    &lt;span class="na"&gt;cache-names&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;products&lt;/span&gt;
    &lt;span class="na"&gt;caffeine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;maximumSize=10000,expireAfterWrite=30s,recordStats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;type: caffeine&lt;/code&gt;&lt;/strong&gt; makes the application fail at startup if Caffeine is missing. Without it, Spring Boot silently falls back to &lt;code&gt;ConcurrentMapCacheManager&lt;/code&gt;. That cache ignores &lt;code&gt;caffeine.spec&lt;/code&gt;, so it has no size limit and entries never expire: unbounded memory and unbounded staleness, a worse problem than the one you set out to fix. Dependencies move around, so don't rely on Caffeine always being on the classpath.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cache-names&lt;/code&gt;&lt;/strong&gt; creates the listed caches at startup and turns off dynamic creation: a &lt;code&gt;@Cacheable&lt;/code&gt; that uses an unlisted name fails at runtime. It's also what lets Spring Boot register Micrometer metrics for the cache, because only caches that exist at startup are bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;spec&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;maximumSize&lt;/code&gt; caps memory, &lt;code&gt;expireAfterWrite&lt;/code&gt; caps staleness, and &lt;code&gt;recordStats&lt;/code&gt; turns on the hit/miss statistics behind the metrics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The annotation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Cacheable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&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;"#productId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findProduct&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;productId&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;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;or&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchAndStore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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 attributes do most of the work here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sync = true&lt;/code&gt;&lt;/strong&gt; is the difference between caching and collapsing. Without it, Spring checks the cache, misses, calls the method and stores the result. When 20 threads miss at the same moment, all 20 call the method. With &lt;code&gt;sync = true&lt;/code&gt;, Spring hands the load to Caffeine's atomic &lt;code&gt;get(key, loader)&lt;/code&gt;: the first thread runs the method, and the other 19 wait for its result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;key = "#productId"&lt;/code&gt;&lt;/strong&gt; makes the key explicit. The default key generator uses the bare argument for single-argument methods, but the moment someone adds a second parameter, the key changes shape and any matching &lt;code&gt;@CacheEvict&lt;/code&gt; silently stops working. Referring to parameters by name needs the &lt;code&gt;-parameters&lt;/code&gt; compiler flag (Spring Boot's build plugins enable it); otherwise use &lt;code&gt;#p0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Evicting works the same 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;@CacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&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;"#productId"&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;evict&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;productId&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;@CacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allEntries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;evictAll&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;A classic mistake: &lt;code&gt;@CacheEvict("products")&lt;/code&gt; on a method with no arguments evicts the key &lt;code&gt;SimpleKey.EMPTY&lt;/code&gt;, which clears nothing. Use &lt;code&gt;allEntries = true&lt;/code&gt; to clear the whole cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  What will surprise you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Empty results are cached too
&lt;/h3&gt;

&lt;p&gt;Spring unwraps &lt;code&gt;Optional&lt;/code&gt;: present values are stored as the value, and empty ones are stored as &lt;code&gt;null&lt;/code&gt;. &lt;code&gt;CaffeineCacheManager&lt;/code&gt; allows null values by default, so "product not found" is memoised for the full TTL.&lt;/p&gt;

&lt;p&gt;This is often what you want: without it, a missing product is looked up again by every one of the 20 callers. But decide it deliberately. The usual escape hatch, &lt;code&gt;unless = "#result == null"&lt;/code&gt;, isn't allowed together with &lt;code&gt;sync = true&lt;/code&gt;. If you can't accept caching empty results, you'll need a different shape, for example throwing a not-found exception (exceptions are never cached) or using the &lt;code&gt;Cache&lt;/code&gt; API directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Failures are not shared
&lt;/h3&gt;

&lt;p&gt;Exceptions aren't cached. That's good: a transient failure doesn't stick for the next 30 seconds. But it also means that when the load fails, the waiting callers don't get the error. They retry the load one after another. Under a sustained outage, put a circuit breaker or a stale-fallback strategy inside the loaded method. Don't count on the cache to absorb the failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Every caller gets the same instance
&lt;/h3&gt;

&lt;p&gt;Before the cache, each caller got its own freshly deserialised copy. Now all callers within the TTL share one object, across threads. If anything modifies it, you have a data race between components that don't know about each other. Prefer immutable types (records, or immutable generated classes). If you can't, say so in a comment on the cached method.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Self-invocation bypasses the cache
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@Cacheable&lt;/code&gt; works through a Spring proxy. A call from inside the same class (&lt;code&gt;this.findProduct(...)&lt;/code&gt;) skips the proxy and the cache. Check that every hot caller goes through the bean.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The cache is per instance
&lt;/h3&gt;

&lt;p&gt;Each pod or instance has its own cache. An eviction endpoint only clears the instance that received the request; the others keep serving the old value until the TTL expires. That's acceptable when the TTL is short. Write it down for whoever operates the endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Your existing metrics change meaning
&lt;/h3&gt;

&lt;p&gt;Any counter incremented inside the cached method (misses on the shared cache, remote calls, fallbacks) now only fires on in-memory misses. On the day you deploy, dashboards show a sudden drop that looks like an outage or a miracle. It's neither. Warn people before the deploy, and add the cache's own metrics, which &lt;code&gt;recordStats&lt;/code&gt; exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cache.gets{cache="products", result="hit"|"miss"}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cache.puts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cache.evictions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cache.size&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Pick the TTL for the unit of work
&lt;/h3&gt;

&lt;p&gt;The goal is to collapse the calls within one unit of work, not to build a long-lived performance cache. So the TTL needs to be long enough to cover one evaluation including slow paths (retries and their backoff), and short enough that the extra staleness is negligible compared with how fresh the data behind it is anyway. Seconds to tens of seconds is usually right. If you want a longer TTL, that's a different cache, and it needs its own invalidation design.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Virtual threads on JDK 21–23
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;sync = true&lt;/code&gt;, the load runs inside Caffeine's atomic compute, which holds a &lt;code&gt;synchronized&lt;/code&gt; lock. On JDK 21–23, a virtual thread that blocks on I/O inside &lt;code&gt;synchronized&lt;/code&gt; pins its carrier thread. Since carrier threads are few (one per core by default), many concurrent cold loads can starve the scheduler. JDK 24 fixed this (&lt;a href="https://openjdk.org/jeps/491" rel="noopener noreferrer"&gt;JEP 491&lt;/a&gt;). On older JDKs, load-test the cold path with &lt;code&gt;-Djdk.tracePinnedThreads=full&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prove that concurrent misses collapse
&lt;/h3&gt;

&lt;p&gt;A naive test fires 20 calls and checks that the loader ran once. It usually passes even without &lt;code&gt;sync = true&lt;/code&gt;: the stubbed load returns instantly, so the calls run one after another and the first one fills the cache before the second arrives. The test has to keep callers inside the loader until all of them have arrived:&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;@SpringJUnitConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductCatalogueCachingTest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Config&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;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCatalogueCachingTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;CALLERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;ProductCatalogue&lt;/span&gt; &lt;span class="n"&gt;catalogue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@MockitoBean&lt;/span&gt;
    &lt;span class="nc"&gt;ProductStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;concurrentMissesLoadOnce&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allCallersLoading&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;CountDownLatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CALLERS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;release&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;CountDownLatch&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;// Every caller that reaches the loader waits here, so without sync = true all of them would load.&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p-1"&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;thenAnswer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;allCallersLoading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countDown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="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="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p-1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newFixedThreadPool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CALLERS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;range&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="no"&gt;CALLERS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapToObj&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;catalogue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p-1"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;allCallersLoading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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;MILLISECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countDown&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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="na"&gt;isPresent&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="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&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="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p-1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Configuration&lt;/span&gt;
    &lt;span class="nd"&gt;@EnableCaching&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nd"&gt;@Bean&lt;/span&gt;
        &lt;span class="nc"&gt;CacheManager&lt;/span&gt; &lt;span class="nf"&gt;cacheManager&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CaffeineCacheManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nd"&gt;@Bean&lt;/span&gt;
        &lt;span class="nc"&gt;ProductCatalogue&lt;/span&gt; &lt;span class="nf"&gt;catalogue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProductCatalogue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&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;With &lt;code&gt;sync = true&lt;/code&gt;, the first caller enters the loader and the other 19 wait in Caffeine, so &lt;code&gt;get&lt;/code&gt; runs once. Without it, all 20 enter the loader and the test fails with &lt;code&gt;Wanted 1 time, but was 20 times&lt;/code&gt;. Remove &lt;code&gt;sync = true&lt;/code&gt; once to see that failure: a test you've never seen fail hasn't proven anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prove that the shipped configuration is the one you think
&lt;/h3&gt;

&lt;p&gt;The test above builds its own &lt;code&gt;CaffeineCacheManager&lt;/code&gt;, so it never touches &lt;code&gt;application.yml&lt;/code&gt;. A typo in &lt;code&gt;cache-names&lt;/code&gt;, or a missing &lt;code&gt;type&lt;/code&gt;, would only show up in production. &lt;code&gt;ApplicationContextRunner&lt;/code&gt; can load the real configuration file with only the cache auto-configuration:&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;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;applicationConfigurationCreatesBoundedCaffeineCache&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApplicationContextRunner&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withInitializer&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;ConfigDataApplicationContextInitializer&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AutoConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CacheAutoConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withUserConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CachingEnabled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CacheManager&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;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isInstanceOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CaffeineCacheManager&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;var&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;benmanes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;caffeine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?,&lt;/span&gt; &lt;span class="o"&gt;?&amp;gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getNativeCache&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;eviction&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="na"&gt;getMaximum&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;expireAfterWrite&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="na"&gt;getExpiresAfter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableCaching&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CachingEnabled&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;CacheAutoConfiguration&lt;/code&gt; lives in &lt;code&gt;org.springframework.boot.cache.autoconfigure&lt;/code&gt; in Spring Boot 4, and in &lt;code&gt;org.springframework.boot.autoconfigure.cache&lt;/code&gt; in Spring Boot 3.&lt;/p&gt;

&lt;p&gt;Also add one test each for eviction (evict, call again, check the loader ran twice) and for whichever empty-result behaviour you chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;spring-boot-starter-cache&lt;/code&gt; and &lt;code&gt;caffeine&lt;/code&gt; declared, &lt;code&gt;@EnableCaching&lt;/code&gt; on&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;spring.cache.type: caffeine&lt;/code&gt; so a missing Caffeine fails at startup&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;cache-names&lt;/code&gt; declared (and new names added there)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;maximumSize&lt;/code&gt;, &lt;code&gt;expireAfterWrite&lt;/code&gt; and &lt;code&gt;recordStats&lt;/code&gt; in the spec&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;@Cacheable(sync = true)&lt;/code&gt; with an explicit &lt;code&gt;key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;@CacheEvict&lt;/code&gt; uses the same key; clear-all uses &lt;code&gt;allEntries = true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Empty-result caching decided and tested&lt;/li&gt;
&lt;li&gt;[ ] Cached return type immutable, or documented as shared&lt;/li&gt;
&lt;li&gt;[ ] No self-invocation on hot paths&lt;/li&gt;
&lt;li&gt;[ ] Dashboard owners warned about counters inside the cached method&lt;/li&gt;
&lt;li&gt;[ ] Concurrency test that fails without &lt;code&gt;sync = true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Configuration test against the real &lt;code&gt;application.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] On JDK 21–23 with virtual threads: cold path load-tested for pinning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The change itself is a dependency, a few lines of YAML and one annotation. Most of the value is in the rest: deciding what gets cached (including "not found"), making the configuration fail loudly instead of degrading silently, warning the people who read the metrics, and writing tests that actually fail when the behaviour breaks. Get those right, and twenty components asking the same question cost the same as one.&lt;/p&gt;

</description>
      <category>caching</category>
      <category>java</category>
      <category>performance</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Java 19 and the new Virtual Threads</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Tue, 27 Sep 2022 13:59:21 +0000</pubDate>
      <link>https://dev.to/fialhorenato/java-19-and-the-new-virtual-threads-141n</link>
      <guid>https://dev.to/fialhorenato/java-19-and-the-new-virtual-threads-141n</guid>
      <description>&lt;p&gt;This post can be read &lt;a href="https://www.renatofialho.com/blog/working-with-new-virtual-threads-java-19"&gt;here&lt;/a&gt; as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jdk.java.net/19/release-notes"&gt;Java 19&lt;/a&gt; is coming, and with this new version of Java, it brings an amazing new feature, virtual threads.&lt;/p&gt;

&lt;p&gt;This update comes to reduce the effort of writing, maintaining, and observing high-throughput, concurrent applications.&lt;/p&gt;

&lt;p&gt;This is part of the &lt;a href="https://www.infoworld.com/article/3652596/project-loom-understand-the-new-java-concurrency-model.html"&gt;Project Loom&lt;/a&gt;, a project that aims to bring a new concurrency model to java, but preserving the same simple abstraction to the developers.&lt;/p&gt;

&lt;p&gt;Don't worry, you will still be able to use the platform threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;p&gt;Currently, those implementations are on preview mode, so you must provide the &lt;code&gt;--enable-preview&lt;/code&gt; flag to the JVM.&lt;/p&gt;

&lt;p&gt;If you are an IntelliJ user, you can tell IntelliJ to use this flag by going into the &lt;code&gt;Open Module Settings -&amp;gt; Project -&amp;gt; Language Level&lt;/code&gt; and choose the &lt;code&gt;Java 19 (Preview)&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;// Declare a runnable that will print the information about the current thread&lt;/span&gt;
    &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="n"&gt;printThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Declare a virtual thread&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;virtualThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printThread&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Declare a platform thread&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;platformThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofPlatform&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printThread&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will be:&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;VirtualThread&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;runnable&lt;/span&gt;&lt;span class="nd"&gt;@ForkJoinPool&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="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nc"&gt;Thread&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="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you see that we have a virtual thread executing on a worker thread of the Fork-Join pool, and another platform thread running on the main process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seamless integration between platform and virtual threads
&lt;/h2&gt;

&lt;p&gt;Synchronized blocks will work transparent in between virtual and platform threads, as it is shown in this example.&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;VirtualThreads&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&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;VirtualThreads&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&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;start&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;virtualThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;platformThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofPlatform&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;virtualThread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" running command A"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;synchronized&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="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;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wait&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;InterruptedException&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;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="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="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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" running command C"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;

        &lt;span class="n"&gt;platformThread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" running command B"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;synchronized&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="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;notifyAll&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 output will be:&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;VirtualThread&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;runnable&lt;/span&gt;&lt;span class="nd"&gt;@ForkJoinPool&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="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nc"&gt;Thread&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="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;
    &lt;span class="nc"&gt;VirtualThread&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;runnable&lt;/span&gt;&lt;span class="nd"&gt;@ForkJoinPool&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="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loom and Java in general are prominently devoted to building web applications. Obviously, Java is used in many other areas, and the ideas introduced by Loom may well be useful in these applications. &lt;/p&gt;

&lt;p&gt;It’s easy to see how massively increasing thread efficiency, and dramatically reducing the resource requirements for handling multiple competing needs, will result in greater throughput for servers. Better handling of requests and responses is a bottom-line win for a whole universe of existing and to-be-built Java applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jdk.java.net/loom/"&gt;Project Loom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoworld.com/article/3652596/project-loom-understand-the-new-java-concurrency-model.html"&gt;Infoworld&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learn SOLID design principles using Java</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Mon, 26 Sep 2022 15:40:04 +0000</pubDate>
      <link>https://dev.to/fialhorenato/learn-solid-design-principles-using-java-m68</link>
      <guid>https://dev.to/fialhorenato/learn-solid-design-principles-using-java-m68</guid>
      <description>&lt;p&gt;This post can be seen &lt;a href="https://www.renatofialho.com/blog/learning-SOLID-from-coding"&gt;here&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;The main idea from this article is to show the SOLID design principles and provide examples of implementations of those principles using Java as the main language.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SOLID?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;S.O.L.I.D&lt;/strong&gt; stands for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;ingle Responsibility Principle&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O&lt;/strong&gt;pen Closed Principle&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L&lt;/strong&gt;iskov Substitution Principle&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I&lt;/strong&gt;nterface Segregation Principle&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D&lt;/strong&gt;ependency Inversion Principle&lt;/p&gt;

&lt;p&gt;Design principles in general helps us to write better software. And also improves the developer experience of the developers that share the same codebase with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single Responsibility Principle
&lt;/h2&gt;

&lt;p&gt;A class should have only one responsibility.&lt;/p&gt;

&lt;p&gt;It helps into onboarding new members into the code, as well to test, maintain and grow our codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Imagine that you have a &lt;code&gt;UserService&lt;/code&gt; that is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&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;createUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create our user here&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;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&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="c1"&gt;// Return our user&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendAppNotification&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send an app notification&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send an 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;What happens if the requirements of this class change? What if now we must send a tex now we have to send an App Notification via push or SMS?&lt;/p&gt;

&lt;p&gt;Then, we must separate the concerns and responsability by creating a &lt;code&gt;NotificationService&lt;/code&gt; that will handle all of our communications.&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;UserService&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;NotificationService&lt;/span&gt; &lt;span class="n"&gt;notificationService&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;createUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create our user here&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;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&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="c1"&gt;// Return our user&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;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;NotificationService&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;sendSms&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send SMS&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;sendPushNotification&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send push&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;sendEmail&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send 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;Even though it can have better approaches here, the idea is to separate concerns, the other solutions to solve this kind of problem is a different topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Closed Principle
&lt;/h2&gt;

&lt;p&gt;Software entities (classes, modules, functions, etc.) should be opened to extension but closed to modification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Think that we have an CoffeeApp class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeApp&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;brewSimpleCoffee&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Put water&lt;/span&gt;
        &lt;span class="c1"&gt;// Put coffee powder&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;brewPremiumCoffee&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Get the bean&lt;/span&gt;
        &lt;span class="c1"&gt;// Grind the bean&lt;/span&gt;
        &lt;span class="c1"&gt;// Put water&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;Imagine that everytime our system needs to support a new type of coffee, we must change the Machine in order to add a new type.&lt;/p&gt;

&lt;p&gt;Lets use of simple abstraction and polymorphism to improve this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeApp&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;greet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CoffeeMachine&lt;/span&gt; &lt;span class="n"&gt;coffeeMachine&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;coffeeMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;brewCoffee&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ESPRESSO&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;CoffeeMachine&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Coffee&lt;/span&gt; &lt;span class="nf"&gt;brewCoffee&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CoffeeSelection&lt;/span&gt; &lt;span class="n"&gt;selection&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;BasicCoffeeMachine&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CoffeeMachine&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;BrewingUnit&lt;/span&gt; &lt;span class="n"&gt;brewingUnit&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;Coffee&lt;/span&gt; &lt;span class="nf"&gt;brewCoffee&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CoffeeSelection&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;brewFilterCoffee&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;brewFilterCoffee&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;brewingUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;brew&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PremiumCoffeeMachine&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CoffeeMachine&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;Grinder&lt;/span&gt; &lt;span class="n"&gt;grinder&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;BrewingUnit&lt;/span&gt; &lt;span class="n"&gt;brewingUnit&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;Coffee&lt;/span&gt; &lt;span class="nf"&gt;brewCoffee&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CoffeeSelection&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;ESPRESSO:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;brewEspresso&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;FILTER_COFFEE:&lt;/span&gt;
        &lt;span class="k"&gt;default&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;brewFilterCoffee&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;brewEspresso&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;grinder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;grind&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;brewingUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;brew&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;brewFilterCoffee&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;brewingUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;brew&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 that class is opened for extension (ItalianMachine, ColombianMachine, FrenchPressMachine) and closed for modification (won’t have a method with a different logic being added every time a new machine is added to the app).&lt;/p&gt;

&lt;h2&gt;
  
  
  Liskov Substitution Principle
&lt;/h2&gt;

&lt;p&gt;A class can be replaced by its subclass in all practical usage scenarios, meaning that you should use inheritance only for substitutability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Using an Animal example&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;interface&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;fly&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;swim&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;Dog&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A dog can swim&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;swim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Swim&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// But a dog cannot fly&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;fly&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;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hawk&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A hawk cannot swim&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;swim&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;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// But a hawk can fly&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;fly&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Fly&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;Even though, both a &lt;code&gt;Dog&lt;/code&gt; and a &lt;code&gt;Hawk&lt;/code&gt; are animals, we can break up the inheritance to follow up this principle&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;interface&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;swim&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Bird&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;fly&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;Dog&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A dog can swim&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;swim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Swim&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;Hawk&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Bird&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A hawk can fly&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;fly&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Fly&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;h2&gt;
  
  
  Interface Segregation Principle
&lt;/h2&gt;

&lt;p&gt;A client shouldn’t be forced to implement an interface that it doesn’t use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Thinking about the last example, even though a Dog can only swim, some &lt;code&gt;Animal&lt;/code&gt;s can swim and fly.&lt;/p&gt;

&lt;p&gt;Isn't easier if we just implement the interfaces like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Swimmer&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;swim&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Bird&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;fly&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;Dog&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Swimmer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A dog can swim&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;swim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Swim&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;Hawk&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Bird&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A hawk can fly&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;fly&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Fly&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;Duck&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Swimmer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bird&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A duck can fly&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;fly&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Fly&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// A duck can swim&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;swim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Swim&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;h2&gt;
  
  
  Dependency Inversion Principle
&lt;/h2&gt;

&lt;p&gt;We should invert the classic dependency between higher level modules and lower level modules, by abstracting their interaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Let's say we have some implementations over a database&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;PersonService&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;PersonRepository&lt;/span&gt; &lt;span class="n"&gt;personRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PersonService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PersonRepository&lt;/span&gt; &lt;span class="n"&gt;personRepository&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;personRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;personRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PersonRepository&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PersonRepository&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;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Implement the methods&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;DatabaseRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PersonRepository&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Hibernate entity manager to handle the communication towards the Database.&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Implement the methods&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, the high-level PersonService doesn't care if you are using a LocalDatabase for development or a real database for production.&lt;/p&gt;

&lt;p&gt;And for example, you can change your Hibernate implementation to another solution without your high-level service knowing what's going on.&lt;/p&gt;




&lt;p&gt;In case if you have any questions or suggestions, feel free to send me a &lt;a href="https://www.renatofialho.com/contact/"&gt;message&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>How did I move my pet project from Spring MVC to Webflux and Spring Native with GraalVM</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Mon, 27 Jun 2022 23:35:26 +0000</pubDate>
      <link>https://dev.to/fialhorenato/how-did-i-move-my-pet-project-from-spring-mvc-to-webflux-using-spring-native-3lnm</link>
      <guid>https://dev.to/fialhorenato/how-did-i-move-my-pet-project-from-spring-mvc-to-webflux-using-spring-native-3lnm</guid>
      <description>&lt;p&gt;You can read this post as well &lt;a href="https://www.renatofialho.com/blog/challenges-using-spring-native-and-webflux"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my spare time last year, I created a pet project that aggregate news from some sources and delivers them to an Android app that I developed using Flutter, but that pet project is a topic for another post.&lt;/p&gt;

&lt;p&gt;The backend for this pet project was firstly built using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kotlin
Gradle KTS
Spring Boot (Data, MVC, Security, Actuator)
PostgreSQL (Liquibase for migrations)
Deployed on a heroku free tier dyno (Validating the idea)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned above, the stack is currently deployed in a Heroku free tier dyno, and that is a must for me to validate the idea for this project without spending tons of money. &lt;/p&gt;

&lt;p&gt;The MVP was deployed in heroku using one of their buildpacks for Gradle and everything was working fine with some struggles:&lt;/p&gt;

&lt;p&gt;1 - When I was enriching the DB (POSTing more content), the response times increased a little bit.&lt;/p&gt;

&lt;p&gt;2 - Since it is a pet project, the dyno running my app sleep sometimes during the day, and the initialization was very slow (Around 14 seconds)&lt;/p&gt;

&lt;p&gt;Those problems were already bothering me, and I already saw a couple things about the reactive stack for Spring (Webflux / R2DBC) that could help me handle more requests on a peak load without scaling my infrasctructure.&lt;/p&gt;

&lt;p&gt;Usually on my pet projects, I try to implement some new ideas (On this one, I improved my knowledge in Kotlin and learned Dart and Flutter), but I wasn't 100% sure that Webflux and Spring Native were mature enough for being used in this project (Even though is a pet project, there are currently 500+ MAU)&lt;/p&gt;

&lt;p&gt;Skipping some time, I went to the We Are Developers Worldwide Conference in Berlin a couple weeks ago, where a lot of talks were showcasing the new Spring Native and the already "known" Spring Webflux.&lt;/p&gt;

&lt;p&gt;So then, I gave that stack a try and those are my learnings from it:&lt;/p&gt;

&lt;h2&gt;
  
  
  First mistakes
&lt;/h2&gt;

&lt;p&gt;Those 2 technologies are somehow "new" and AFAIK, not being used in a lot of production grade environments.&lt;/p&gt;

&lt;p&gt;Even the Spring project promises a lot of great improvements on Spring 6 and Spring Boot 3.0 for the GraalVM and Spring Native support.&lt;/p&gt;

&lt;p&gt;With that being said, after some time, i realized that my first mistake was jumping into &lt;strong&gt;BOTH&lt;/strong&gt; of the new technologies at the same time.&lt;/p&gt;

&lt;p&gt;That created a feeling of not knowing if the issue was because of Webflux or Spring Native (I lost a couple nights of a lot of Stackoverflow and Github Issues trying to resolve the problems).&lt;/p&gt;

&lt;p&gt;Then, I'll try to showcase the ones that I took notes and remember, please let me know if you faced other problems and we can spread the knowledge. &lt;/p&gt;

&lt;h3&gt;
  
  
  Heroku doesn't support Spring Native out of the box. Yet.
&lt;/h3&gt;

&lt;p&gt;By it's nature of the AOT (Ahead of Time) compiler for the GraalVM, the Spring Native build consumes a lot of resources (It is a huge memory hog) and Heroku doesn't support it yet, so instead, you must build the image yourself or in your CI running &lt;code&gt;./gradlew bootBuildImage&lt;/code&gt; , that will use a buildpack to build and generate a Docker image for you with your application.&lt;/p&gt;

&lt;p&gt;Then, you just need to tag it with the &lt;code&gt;registry.heroku.com/&amp;lt;your_app_name&amp;gt;/&amp;lt;build&amp;gt;&lt;/code&gt; , push and run &lt;code&gt;heroku container:release web -a &amp;lt;your_app_name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Heroku has Docker containers support out of the box, so then it is the same as deploying using one of theirs buildpack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spring Data Webflux and R2DBC drivers don't support a lot of nice features from relational databases
&lt;/h3&gt;

&lt;p&gt;We know that Spring Webflux wasn't meant to be used with relational database since the JDBC drivers are I/O blocking, but the R2DBC initiative does it's job quite nicely.&lt;/p&gt;

&lt;p&gt;Some features, such as the &lt;code&gt;@OneToMany&lt;/code&gt; or &lt;code&gt;@ManyToMany&lt;/code&gt; or &lt;code&gt;@ManyToOne&lt;/code&gt; or &lt;code&gt;@OneToOne&lt;/code&gt; are not supported (yet!) , and in order to make some things to work, you must add some annotations that are a little bit different, such as if you need auditing with the &lt;code&gt;@CreatedAt&lt;/code&gt; and other annotations , you must add the &lt;code&gt;@EnableR2dbcAuditing&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;Well, AFAIK, Quarkus and Micronaut already support some of those features with Native builds, so maybe this is something that Spring Data R2DBC will bring in the near future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Liquibase is NOT supported. Yet.
&lt;/h3&gt;

&lt;p&gt;My first thought when implementing this project was to literally "copy/paste" most of the code from the project that is currently successfully running. &lt;/p&gt;

&lt;p&gt;And the database migration management tool that I was using is Liquibase (Wasn't a must, but instead, a good to have tool).&lt;/p&gt;

&lt;p&gt;I started adding the Liquibase dependency and a lot of issues happened when building the native image, I resolved a lot of them but at the end, struggled with the fact that Liquibase currently doesn't support Spring Native with GraalVM out of the box, this is an issue for the future, hopefully when they chose their &lt;a href="https://github.com/liquibase/liquibase/issues/1552"&gt;issue&lt;/a&gt; on Github, if you are interested, give a +1 on the thumbs up @ the issue!&lt;/p&gt;

&lt;h3&gt;
  
  
  Most of the monitoring tools are not available. Yet.
&lt;/h3&gt;

&lt;p&gt;One issue for production grade applications is observability and monitoring, so far, I couldn't find any way to make the Newrelic java agent to work together with the GraalVM and Spring Native builds (So, no APM yet!). But you can still instrument your application using the Metrics provided by the actuator and Micrometer with the interfaces to the best providers in the market (Datadog, Newrelic, Prometheus and others).&lt;/p&gt;

&lt;h3&gt;
  
  
  You must provide some hints to the GraalVM, and some of them are not 100% clear
&lt;/h3&gt;

&lt;p&gt;I faced an issue when running my application as a native app because the database URL was becoming malformed somehow, then, after a lot of research I found the &lt;a href=""&gt;samples repo&lt;/a&gt; from Spring that had a lot of tips to use Spring Native with some specific technologies.&lt;/p&gt;

&lt;p&gt;The error that i was facing was an &lt;code&gt;&amp;lt;unresolved&amp;gt;&lt;/code&gt; showing up from nowhere in the middle of my database URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caused by: io.r2dbc.postgresql.PostgresqlConnectionFactory$PostgresConnectionException: Cannot connect to localhost/&amp;lt;unresolved&amp;gt;:5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, in order to use the R2DBC PostgreSQL and Spring Native, i had to add this hint to the GraalVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@NativeHint(
        trigger = PostgresqlConnectionFactoryProvider.class,
        types = {
                @TypeHint(types = { Instant[].class, ZonedDateTime[].class, URI[].class }, access = {}),
        }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can have a &lt;code&gt;META-INF/native/reflect-config.json&lt;/code&gt; file as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Well, now, let's come to the results measured so far:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Started MyApplicationKt in 13.374 seconds (JVM running for 16.857)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Started MyApplication in 0.129 seconds (JVM running for 0.132)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll try to update this post later with some insights on CPU and Memory consumption, but so far, nothing to complain!&lt;/p&gt;

</description>
      <category>java</category>
      <category>webdev</category>
      <category>performance</category>
      <category>functional</category>
    </item>
    <item>
      <title>How to create slim docker Java Images using a minimal JRE</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Thu, 14 Apr 2022 13:56:47 +0000</pubDate>
      <link>https://dev.to/fialhorenato/how-to-create-slim-docker-java-images-using-a-minimal-jre-3a20</link>
      <guid>https://dev.to/fialhorenato/how-to-create-slim-docker-java-images-using-a-minimal-jre-3a20</guid>
      <description>&lt;p&gt;This can be read as well &lt;a href="https://www.renatofialho.com/blog/creating-slim-spring-boot-images"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I will explain how you can create slim docker images by creating customized JREs using &lt;code&gt;jlink&lt;/code&gt; and &lt;code&gt;jmods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those commands are present on JDKs since Java 9, but actually being mature enough since Java 11, they bring a new way to create your customized JRE with only the modules that you need from Java, and then, creating smaller Docker images to run your Java applications (Mainly focused in Spring Boot applications), saving some good bucks in your Container Repository application (ECR, Artifactory, Docker Hub).&lt;/p&gt;

&lt;h2&gt;
  
  
  But, how do we start?
&lt;/h2&gt;

&lt;p&gt;Assuming you already have a JDK installed in your machine, if not, i strongly suggest you to use &lt;a href="https://sdkman.io/"&gt;sdkman.io&lt;/a&gt; and install Java 17 version (Which is the latest LTS as of the date of this post)&lt;/p&gt;

&lt;p&gt;First of all, we need to get the list produced from &lt;code&gt;jdeps&lt;/code&gt; based in your jarfile from Spring Boot, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jdeps &lt;span class="nt"&gt;--list-deps&lt;/span&gt; &lt;span class="nt"&gt;--ignore-missing-deps&lt;/span&gt;  your-fat-jar.jar
   java.base
   java.logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, that doesn't seems right, huh?&lt;/p&gt;

&lt;p&gt;Spring Boot has a strategy by default to generate a fat jar with all the needed dependencies inside of it, and &lt;code&gt;jdeps&lt;/code&gt; currently can't get all the dependencies recursively inside the jars from the fat-jar.&lt;/p&gt;

&lt;p&gt;So, what you can do is to extract all the contents from the fat-jar and run jdeps against every jar in the &lt;code&gt;lib&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Or, you can try with try and error, and see which ClassNotFoundException you will get from each JRE version&lt;/p&gt;

&lt;p&gt;But, from some previous experience, we usually need those packages in our minimal JRE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java.compiler
java.logging
java.sql
java.rmi
java.naming
java.management
java.instrument
java.security.jgss
java.net.http
jdk.httpserver
jdk.naming.dns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But my suggestion is to try yourself and see which packages you will really need in your slim JRE&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating my Java slim JRE
&lt;/h2&gt;

&lt;p&gt;You can use &lt;code&gt;jlink&lt;/code&gt; locally to create your customized JRE, the command is somehow like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jlink &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--module-path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;&lt;span class="s2"&gt;/jmods"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--add-modules&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;the java modules your application needs joined by ,] &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--strip-debug&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--compress&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-header-files&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-man-pages&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; /opt/jre-minimal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explaining each flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--module-path &amp;lt;path&amp;gt;              Module path.
                                        If not specified, the JDKs jmods directory
                                        will be used, if it exists. If specified,
                                        but it does not contain the java.base module,
                                        the JDKs jmods directory will be added,
                                        if it exists.

--verbose                         Enable verbose tracing

--strip-debug                     Strip debug information

--compress=&amp;lt;0|1|2&amp;gt;                Enable compression of resources:
                                          Level 0: No compression
                                          Level 1: Constant string sharing
                                          Level 2: ZIP

--no-header-files                 Exclude include header files

--no-man-pages                    Exclude man pages

--output &amp;lt;path&amp;gt;                   Location of output path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get those informations as well by running &lt;code&gt;jlinks --help&lt;/code&gt; in your command line tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  But then, how do I create my slim Spring Boot application docker image
&lt;/h2&gt;

&lt;p&gt;Well, take this as an example, as i mentioned, it's using the OpenJDK 17 version (Latest LTS by the time this post was written)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build our minimal JRE using jlink&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;openjdk:17-oraclelinux8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;jlink &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--module-path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;&lt;span class="s2"&gt;/jmods"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--add-modules&lt;/span&gt; java.compiler,java.sql,java.naming,java.management,java.instrument,java.rmi,java.desktop,jdk.internal.vm.compiler.management,java.xml.crypto,java.scripting,java.security.jgss,jdk.httpserver,java.net.http,jdk.naming.dns,jdk.crypto.cryptoki,jdk.unsupported &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--strip-debug&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--compress&lt;/span&gt; 2 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-header-files&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-man-pages&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--output&lt;/span&gt; /opt/jre-minimal

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="c"&gt;# Now it is time for us to build our real image on top of an slim version of debian&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bitnami/minideb:bullseye&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/jre-minimal /opt/jre-minimal&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_HOME=/opt/jre-minimal&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PATH:$JAVA_HOME/bin"&lt;/span&gt;

&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /tmp&lt;/span&gt;

&lt;span class="c"&gt;# Copy the JRE created in the last step into our $JAVA_HOME&lt;/span&gt;

&lt;span class="c"&gt;# For gradle&lt;/span&gt;
&lt;span class="c"&gt;# COPY build/libs/app-*.jar app.jar&lt;/span&gt;

&lt;span class="c"&gt;# For maven&lt;/span&gt;
&lt;span class="c"&gt;# COPY target/app-*.jar app.jar&lt;/span&gt;


&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java","-jar","/app.jar"] &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, how efficient is this?&lt;/p&gt;

&lt;p&gt;For example, in a regular spring boot application, it saved 200Mb of storage for every image generated, from &lt;code&gt;543.03&lt;/code&gt; to &lt;code&gt;349.98&lt;/code&gt;, assuming that you always have 10 versions of your application in your container registry, you can save &lt;strong&gt;2&lt;/strong&gt; GB of storage space for each application!&lt;/p&gt;

&lt;p&gt;You can check an example in this &lt;a href="https://github.com/fialhorenato/SpringBootstrap"&gt;repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>docker</category>
      <category>spring</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Handling SIGTERM and SIGKILL on Spring Boot web applications</title>
      <dc:creator>Renato Fialho</dc:creator>
      <pubDate>Wed, 08 Dec 2021 00:01:29 +0000</pubDate>
      <link>https://dev.to/fialhorenato/handling-sigterm-and-sigkill-on-spring-boot-web-applications-45i6</link>
      <guid>https://dev.to/fialhorenato/handling-sigterm-and-sigkill-on-spring-boot-web-applications-45i6</guid>
      <description>&lt;p&gt;This post was reproduced &lt;a href="https://www.renatofialho.com/blog/spring-boot-docker-orchestrators" rel="noopener noreferrer"&gt;here&lt;/a&gt; as well&lt;/p&gt;

&lt;p&gt;When debugging one system recently, I found out that every time that a deploy, downscale, container rotation or k8s updates happened I had a lot of connection errors in the ingress car.&lt;/p&gt;

&lt;p&gt;In common on those situations, is that k8s sends a SIGTERM, then have a grace period for the application to handle it gracefully, and after N seconds, it sends a SIGKILL, and that is the end.&lt;/p&gt;

&lt;p&gt;At first, being a little bit reluctant that the issue was with Spring Boot or the web server that I run with (Tomcat), I tried to check every possible misbehave from the load balancer, no success.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fodv3qb3l8tkktyibzuqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fodv3qb3l8tkktyibzuqa.png" alt="This is usually how the service infra works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One example on how this works, but mainly from AWS ECS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdj88paw4ie145rh34x3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdj88paw4ie145rh34x3k.png" alt="AWS ECS ELB Container Draining"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, after some digging into the server properties in Spring Boot, we found out that the common behaviour for the web server is to shutdown immediately after receiving a SIGTERM, not waiting for any persistent&lt;code&gt;keep-alive&lt;/code&gt; connections to be properly finished and closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  And how did I fix this?
&lt;/h2&gt;

&lt;p&gt;So, in order to fix that, you must add a line to your &lt;code&gt;application.properties&lt;/code&gt; or &lt;code&gt;application.yml&lt;/code&gt; file like this:&lt;/p&gt;

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

    server.shutdown=graceful


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

&lt;/div&gt;

&lt;p&gt;OR&lt;/p&gt;

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

&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shutdown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graceful&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Passing &lt;code&gt;graceful&lt;/code&gt; to the server shutdown property allows the web server to support graceful shutdown, allowing active requests time to complete and close any persistent &lt;code&gt;keep-alive&lt;/code&gt; connections towards your service.&lt;/p&gt;

&lt;p&gt;Oh, and if in any case you ended up here but want to know more how Quarkus handles this, i suggest you to take a look &lt;a href="https://quarkus.io/guides/lifecycle#graceful-shutdown" rel="noopener noreferrer"&gt;here&lt;/a&gt;, because it explains in an easy way how to implement this on Quarkus.&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
