<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[s7g.dev - Spring, Kotlin, JPA]]></title><description><![CDATA[s7g.dev - Spring, Kotlin, JPA]]></description><link>https://s7g.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 22 Apr 2026 15:19:46 GMT</lastBuildDate><atom:link href="https://s7g.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[@Scheduled @Async and Custom ThreadPools]]></title><description><![CDATA[In growing larger application you most likely get to the point where you want to introduce @Async methods to leverage Springs threading. You do set a @EnableAsync annotation to a @Configuration Bean and you are done. Spring does the rest for you.
@As...]]></description><link>https://s7g.dev/scheduled-async-and-custom-threadpools</link><guid isPermaLink="true">https://s7g.dev/scheduled-async-and-custom-threadpools</guid><category><![CDATA[Spring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[async]]></category><category><![CDATA[ThreadPools]]></category><dc:creator><![CDATA[Sebastian Schilling]]></dc:creator><pubDate>Wed, 01 Feb 2023 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695035278334/8e367229-2112-43ef-8fe0-94c83b5e3602.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In growing larger application you most likely get to the point where you want to introduce <code>@Async</code> methods to leverage Springs threading. You do set a <code>@EnableAsync</code> annotation to a <code>@Configuration</code> Bean and you are done. Spring does the rest for you.</p>
<h2 id="heading-async-and-custom-threadpool"><code>@Async</code> and Custom ThreadPool</h2>
<p>For larger demands where you don't want to have an uncapped amount of threads or applications where you want to manage e.g. queues, you most likely need a custom <code>ThreadPool</code> and define something equivalent to this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Bean(<span class="hljs-meta-string">"customThreadPool"</span>)</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">customThreadPool</span><span class="hljs-params">()</span></span>: Executor {
  <span class="hljs-keyword">val</span> corePoolSize = <span class="hljs-number">10</span>
  <span class="hljs-keyword">return</span> Executors.newFixedThreadPool(corePoolSize)
}

<span class="hljs-comment">// in some service</span>

<span class="hljs-meta">@Asnyc</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">asyncDefaultPool</span><span class="hljs-params">()</span></span> {
  logger.info { <span class="hljs-string">"Check my thread - I am async with the default ThreadPool"</span> }
}

<span class="hljs-meta">@Async(<span class="hljs-meta-string">"customThreadPool"</span>)</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">asyncCustomPool</span><span class="hljs-params">()</span></span> {
  logger.info { <span class="hljs-string">"Check my thread - I am async with custom ThreadPool"</span> }
}
</code></pre>
<p>Starting an application with this setup and calling these methods will lead to the following log line messages:</p>
<pre><code class="lang-plaintext">// calling asyncCustomPool()
2023-09-18 11:57:29.513  INFO 83349 --- [pool-1-thread-1] c.e.asyncschedulingdemo.SomethingAsyncs  : Check my thread - I am async with custom ThreadPool

// calling asyncDefaultPool()
2023-09-18 12:00:06.293  INFO 83661 --- [nio-8082-exec-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
2023-09-18 12:00:06.322  INFO 83661 --- [cTaskExecutor-1] c.e.asyncschedulingdemo.SomethingAsyncs  : Check my thread - I am async with the default ThreadPool
</code></pre>
<p>The first log line for the <code>asyncCustomPool()</code> looks fine. We see that it uses a thread pool name from the <code>DefaultThreadFactory</code>, but what about the other method? We see two lines, the first one comes from the web context and tells us that there is no bean present for the second thread pool and in the following line we can see that a pool named <code>cTaskExecutor-1</code> is used. The actual name is <code>SimpleAsyncTaskExecutor</code> and the logger did just shorten it. The <code>SimpleAsyncTaskExecutor</code> is probably your <strong>least favorable executor</strong> because it creates a <strong>new thread for every async invocation</strong> which is very expensive.</p>
<p>But why is this the case? Let's have a look at the <code>TaskExecutionAutoConfiguration</code> and its default <code>ThreadPoolTaskExecutor</code></p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Lazy</span>
<span class="hljs-meta">@Bean(
  name = {<span class="hljs-meta-string">"applicationTaskExecutor"</span>, <span class="hljs-meta-string">"taskExecutor"</span>}
)</span>
<span class="hljs-meta">@ConditionalOnMissingBean({Executor.class})</span> <span class="hljs-comment">// &lt;- note this line here!!</span>
<span class="hljs-keyword">public</span> ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
    <span class="hljs-keyword">return</span> builder.build();
}
</code></pre>
<p>This means as long as there is already a Bean of class <code>Executor</code> is present in the context, the default pool will not be instantiated and <a target="_blank" href="https://github.com/spring-projects/spring-framework/blob/2b4c1e265c7fd3d1baa52da137831136d486ce1e/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java#L154-L159">Spring then falls back to the <code>SimpleAsyncTaskExecutor</code>.</a></p>
<pre><code class="lang-java"><span class="hljs-meta">@Override</span>
<span class="hljs-meta">@Nullable</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> Executor <span class="hljs-title">getDefaultExecutor</span><span class="hljs-params">(<span class="hljs-meta">@Nullable</span> BeanFactory beanFactory)</span> </span>{
    Executor defaultExecutor = <span class="hljs-keyword">super</span>.getDefaultExecutor(beanFactory);
    <span class="hljs-keyword">return</span> (defaultExecutor != <span class="hljs-keyword">null</span> ? defaultExecutor : <span class="hljs-keyword">new</span> SimpleAsyncTaskExecutor());
}
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">So to check whether your default pool is affected or not, just check if a Bean named <code>applicationTaskExecutor</code> is present in your context. If it is missing, you might want to change that.</div>
</div>

<h2 id="heading-also-using-scheduled">Also Using <code>@Scheduled</code></h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">In Spring Boot 3 virtual threads are introduced for TaskSchedulingConfigurations. The following snippets and examples might differ, as this article uses Spring Boot 2.X as a basis</div>
</div>

<p>If your application also features scheduled methods e.g. some cleanups, then the behavior slightly differs from the one described above.</p>
<pre><code class="lang-plaintext">2023-09-18 12:21:54.451  INFO 86166 --- [pool-1-thread-1] c.e.asyncschedulingdemo.SomethingAsyncs  : Check my thread - I am async with custom ThreadPool
2023-09-18 12:21:54.452  INFO 86166 --- [   scheduling-1] c.e.asyncschedulingdemo.SomethingAsyncs  : Check my thread - I am async with the default ThreadPool
2023-09-18 12:21:57.563  INFO 86166 --- [   scheduling-1] c.e.a.SomethingSchedules                 : Check my thread - I am scheduled
</code></pre>
<p>The default async task now uses the <code>scheduling-1</code> thread pool. This is also not good, as the default configuration only allows for one execution in parallel. You now have a queue running for your default <code>@Async</code> methods. The scheduling pool is used because the <code>AsyncExecutionAspectSupport.getDefaultExecutor</code> <a target="_blank" href="https://github.com/spring-projects/spring-framework/blob/2b4c1e265c7fd3d1baa52da137831136d486ce1e/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java#L227-L263">method resolves the executor by type</a> and the <code>TaskSchedulingAutoConfiguration</code> <a target="_blank" href="https://github.com/spring-projects/spring-boot/blob/ff2fc95dafad9ebe9751b160c029523b3da9be95/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java#L49-L54">provides a <code>taskScheduler</code></a> which implements the <code>SchedulingTaskExecutor</code> interface (and with this the <code>AsyncTaskExecutor</code> interface and as such finally the <code>TaskExecutor</code> interface)</p>
<h2 id="heading-what-do-we-do-now">What do we do now?</h2>
<p>I would just recommend re-introducing the default thread pool with a Bean like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Bean(name = [<span class="hljs-meta-string">"applicationTaskExecutor"</span>, <span class="hljs-meta-string">"taskExecutor"</span>])</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">defaultTaskExecutor</span><span class="hljs-params">(builder: <span class="hljs-type">TaskExecutorBuilder</span>)</span></span>: Executor{
  <span class="hljs-keyword">return</span> builder.build()
}
</code></pre>
<p>This just adds the standard pool as it is defined by spring back to the context and everything will work out as expected. Overall it is always very helpful to check old and new pools when changing touching configurations regarding <code>@Async</code> and <code>@Scheduled</code>.</p>
<p>Thanks for reading 👨🏼‍💻</p>
]]></content:encoded></item></channel></rss>