<?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: Sanha Ko</title>
    <description>The latest articles on DEV Community by Sanha Ko (@headf1rst).</description>
    <link>https://dev.to/headf1rst</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3112472%2Ffa14e47a-0b29-47e2-b30c-87ccc441daec.png</url>
      <title>DEV Community: Sanha Ko</title>
      <link>https://dev.to/headf1rst</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/headf1rst"/>
    <language>en</language>
    <item>
      <title>Netty 내부 동작 원리로 파헤친 WebClient 초기 지연 이슈</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Fri, 26 Dec 2025 16:25:03 +0000</pubDate>
      <link>https://dev.to/headf1rst/webclient-coldstart-munje-44ln</link>
      <guid>https://dev.to/headf1rst/webclient-coldstart-munje-44ln</guid>
      <description>&lt;p&gt;물류 인프라를 보유하고 있는 회사들은 3PL이라는 서비스를 제공합니다. 3PL이란 물류 인프라를 갖춘 회사가 그렇지 못한 판매처로부터 배송 업무를 위탁받아 제공하는 서비스를 말합니다.&lt;/p&gt;

&lt;p&gt;판매처는 배송이 필요한 주문 목록을 3PL 시스템에 등록하게 되는데, 이 과정에서 입력된 주문이 유효한 주문인지 확인하기 위해서 여러 시스템과 소통하게 됩니다.&lt;/p&gt;

&lt;p&gt;외부 API 호출을 위한 도구로는 WebClient를 사용하고 있었는데요. 아래 지표에서 보듯이, 외부 API 호출까지의 지연 시간이 원인을 알 수 없이 길어지는 현상이 '간헐적'으로 발견되었습니다.&lt;/p&gt;

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

&lt;p&gt;이번 포스트에서는 해당 현상의 원인을 파악하며 알게 된 WebClient의 내부 동작 원리와 Reactor Netty의 아키텍처, 그리고 해결책을 공유하고자 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  원인 파악을 위한 가정
&lt;/h2&gt;

&lt;p&gt;지연이 발생한다는 것은 요청이 어딘가에서 즉시 처리되지 못하고 대기하고 있었을 가능성이 높다는 의미입니다.&lt;/p&gt;

&lt;p&gt;이전 사진에서 빨간 박스로 표시된 메서드는 객체 생성과 WebClient 호출만을 담당하고 있었습니다. WebClient 내부의 어느 처리 단계에서 병목이 발생할 수 있는지 명확히 식별하려면 아키텍처에 대한 이해가 선행되어야 했습니다. 이를 위해 WebClient의 요청 처리 방식과 Reactor Netty의 아키텍처를 분석했습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebClient 실행 메커니즘
&lt;/h2&gt;

&lt;p&gt;먼저 문제가 발생한 코드의 구조를 살펴보겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OrderRegistrationService.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;CONCURRENCY_CALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineResult&lt;/span&gt;&lt;span class="o"&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="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromIterable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registerDtos&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defer&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;omsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refineAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrimaryAddress&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSecondaryAddress&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultIfEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NO_RESPONSE"&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="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RefineResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt; &lt;span class="no"&gt;CONCURRENCY_CALL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collectList&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OmsService.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;refineAddress&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;primaryAddress&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;secondaryAddress&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;RefineAddressInput&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RefineAddressInput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primaryAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primaryAddress&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;secondaryAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secondaryAddress&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;omsClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/refine-address"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyToMono&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressOutput&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;timeout&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;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retryWhen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fixedDelay&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="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"[OMS 주소정제] 재시도 요청: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="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;output&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccess&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"응답 결과에 데이터가 없음"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="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="na"&gt;onErrorResume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ExternalErrorHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handleError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extractOmsErrorMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"OMS 주소정제"&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;
  
  
  Cold Sequence와 구독 시점
&lt;/h2&gt;

&lt;p&gt;위 코드에서 실제 HTTP 요청이 언제 발생하는지 이해하려면 WebClient의 Cold Sequence 특성을 먼저 이해해야 합니다.&lt;/p&gt;

&lt;p&gt;WebClient의 리액티브 체인은 Cold Sequence로 동작합니다. 구독이 발생하기 전까지는 파이프라인만 정의될 뿐, 실제 실행은 일어나지 않습니다. HTTP 요청 발송 시점은 &lt;code&gt;subscribe()&lt;/code&gt;가 호출되는 순간이며, 코드상의 &lt;code&gt;block()&lt;/code&gt;이 내부적으로 이를 트리거합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineResult&lt;/span&gt;&lt;span class="o"&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="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromIterable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registerDtos&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defer&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collectList&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// ← 구독 시작점&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;block()&lt;/code&gt;의 구독 신호는 &lt;strong&gt;역방향(upstream)&lt;/strong&gt;으로 전파됩니다&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;block() → collectList() → flatMap() → Mono.defer() → WebClient 체인
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flatMap(Function, int concurrency)&lt;/code&gt;은 인자로 전달된 concurrency 수 만큼의 Mono를 동시에 구독합니다. &lt;code&gt;Mono.defer()&lt;/code&gt;는 각 구독 시점마다 내부 람다를 실행하여 새로운 Mono를 생성하므로, 각 DTO마다 독립적인 HTTP 요청 파이프라인이 생성됩니다.&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;// 구독될 때마다 새로운 WebClient 체인 생성&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defer&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;omsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refineAddress&lt;/span&gt;&lt;span class="o"&gt;(...))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TaskQueue로의 전달
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;omsService.refineAddress(...)&lt;/code&gt;가 반환하는 Mono가 구독되면 요청 설정을 빌드하고, &lt;code&gt;.retrieve()&lt;/code&gt; 이후 체인이 구독되면서 쓰기 요청이 &lt;strong&gt;TaskQueue&lt;/strong&gt;에 저장됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;omsClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/refine-address"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyToMono&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressOutput&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;POST 요청이 NioEventLoop의 TaskQueue에 저장되면, WebClient를 호출한 스레드의 역할은 여기서 끝납니다. 이후 작업은 EventLoop 스레드가 담당합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Netty EventLoop 스레드의 동작 원리
&lt;/h2&gt;

&lt;p&gt;WebClient의 HTTP 요청이 TaskQueue에 저장되는 이유는 Netty의 &lt;strong&gt;이벤트 루프 기반 비동기 처리 모델&lt;/strong&gt; 때문입니다. 이 모델을 이해하려면 먼저 네트워크 통신의 기본 개념을 짚고 넘어가야 합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Space와 Kernel Space
&lt;/h3&gt;

&lt;p&gt;서로 다른 머신의 애플리케이션이 통신하려면 시스템 콜로 유저 모드와 커널 모드를 오가며 커널 내 소켓 버퍼에 데이터를 읽거나 써야 합니다.&lt;/p&gt;

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

&lt;p&gt;소켓 버퍼에 데이터를 어떻게 읽고 쓰느냐에 따라 Blocking I/O와 Non-blocking I/O로 나뉩니다. 둘의 차이는 스레드가 시스템 콜 후 응답을 기다리는지 여부입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Blocking I/O&lt;/code&gt;: 데이터가 준비될 때까지 스레드가 대기&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Non-blocking I/O&lt;/code&gt;: 데이터가 없으면 즉시 반환, 스레드는 다른 작업 수행 가능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;효율적인 Non-blocking I/O를 구현하려면 특정 이벤트를 등록해 놓고 해당 이벤트가 발생했을 때만 처리하는 방식이 필요합니다. 이렇게 하면 하나의 스레드로 여러 채널을 관리할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiplexing I/O와 Selector
&lt;/h3&gt;

&lt;p&gt;이벤트 기반 소켓 통신에서는 &lt;strong&gt;하나의 Selector가 여러 소켓 채널의 변화를 감지&lt;/strong&gt;하며 이벤트가 발생했을 때만 처리합니다. 이를 &lt;code&gt;Multiplexing I/O&lt;/code&gt;라고 합니다.&lt;/p&gt;

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

&lt;p&gt;Linux에서 이 Multiplexing I/O는 &lt;code&gt;epoll&lt;/code&gt; 시스템 콜로 구현됩니다. Java NIO의 Selector는 내부적으로 이 epoll을 사용합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selector.select()의 실제 동작
&lt;/h2&gt;

&lt;p&gt;OS 커널이 능동적으로 I/O 이벤트를 Selector에 알려주는 것처럼 보이지만, 실제로는 그렇지 않습니다.&lt;/p&gt;

&lt;p&gt;Selector.select()가 호출되면 유저 모드에서 커널 모드로 전환되고, 내부적으로 &lt;code&gt;epoll_wait()&lt;/code&gt; 시스템 콜이 호출되면서 호출 스레드는 커널에서 블로킹 상태로 대기합니다.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;epoll_wait&lt;/code&gt;을 호출하면, OS 커널은 이전에 &lt;code&gt;epoll_ctl&lt;/code&gt;로 등록된 파일 디스크립터(소켓)들을 모니터링하다가, 네트워크 카드에 데이터가 도착하거나 소켓 버퍼에 쓰기가 가능해지는 등의 I/O 이벤트가 발생하면 이를 감지합니다. I/O가 발생한 소켓은 커널 내 Ready Queue에 추가되고, &lt;code&gt;epoll_wait()&lt;/code&gt;이 반환되어 대기 중이던 스레드가 깨어납니다.&lt;/p&gt;

&lt;p&gt;즉, User Space가 커널에 요청하고 시스템 콜로 응답받는 pull 구조입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;select()&lt;/code&gt; 자체는 블로킹 호출이지만, 하나의 스레드가 여러 소켓을 감시하고 이벤트가 발생한 소켓들만 골라서 처리합니다. 따라서 각 소켓 입장에서는 전용 스레드 없이도 비동기적으로 처리되는 것과 같은 효과를 얻게 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  NioEventLoop의 구조
&lt;/h2&gt;

&lt;p&gt;이제 Netty의 EventLoop가 Selector를 어떻게 활용하는지 살펴보겠습니다.&lt;/p&gt;

&lt;p&gt;EventLoop의 구현체인 NioEventLoop는 &lt;code&gt;1 Thread + 1 Selector + 1 TaskQueue&lt;/code&gt;로 구성됩니다.&lt;/p&gt;

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

&lt;p&gt;EventLoop 스레드는 기본적으로 CPU 코어 수만큼 생성됩니다. &lt;code&gt;Math.max(Runtime.getRuntime().availableProcessors(), 4)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;각 EventLoop 스레드는 전용 NioEventLoop 인스턴스를 실행하며, 단일 스레드가 무한 루프를 돌면서 두 가지 작업을 수행합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I/O 이벤트 처리 (네트워크 읽기/쓰기)&lt;/li&gt;
&lt;li&gt;TaskQueue의 작업 처리 (사용자가 등록한 Runnable)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 개념적인 코드&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. 네트워크에서 뭔가 일어났는지 확인&lt;/span&gt;
    &lt;span class="n"&gt;네트워크_이벤트_확인&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. 일어난 일들 처리&lt;/span&gt;
    &lt;span class="n"&gt;이벤트들_처리&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. 누가 시켜놓은 작업들 처리&lt;/span&gt;
    &lt;span class="n"&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;실제 Netty 코드를 보면 (Netty 4.2 기준)&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;// SingleThreadIoEventLoop.java:153-164&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;runIo&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                    &lt;span class="c1"&gt;// ← 1+2: I/O 확인 및 처리&lt;/span&gt;
        &lt;span class="n"&gt;runAllTasks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxTasksPerRun&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← 3: 작업큐 처리&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;confirmShutdown&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;runIo()&lt;/code&gt;는 내부적으로 &lt;code&gt;NioIoHandler.run()&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;// NioIoHandler.java:420-485&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IoExecutionContext&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1단계: select - I/O 이벤트 존재 여부 확인&lt;/span&gt;
    &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wakenUp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAndSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// 2단계: 있으면 처리&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processSelectedKeys&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 각 단계를 자세히 살펴보겠습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. select() - I/O 이벤트 감지
&lt;/h3&gt;

&lt;p&gt;EventLoop는 I/O 이벤트 처리와 TaskQueue에 쌓인 작업 처리, 두 가지 역할을 수행합니다. 이때 &lt;code&gt;Selector.select()&lt;/code&gt;를 사용하여 처리할 I/O 이벤트가 있는지 확인합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;select()&lt;/code&gt; 메서드는 TaskQueue에 작업이 존재하는지 여부에 따라 적절한 select 방식을 결정합니다.&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;// NioIoHandler.java&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;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IoExecutionContext&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;oldWakenUp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Selector&lt;/span&gt; &lt;span class="n"&gt;selector&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;selector&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="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 태스크가 있으면 즉시 확인하고 넘어감&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;canBlock&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;wakenUp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareAndSet&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="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectNow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// 작업 있으면 바로 확인&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 태스크가 없으면 이벤트 올 때까지 대기&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;selectedKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeoutMillis&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&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;canBlock&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;inEventLoop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hasTasks&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hasScheduledTasks&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;h4&gt;
  
  
  TaskQueue가 비었을 때
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;TaskQueue&lt;/code&gt;가 비어있으면 Netty는 &lt;code&gt;select(timeout)&lt;/code&gt;을 호출하여 커널로 부터 I/O 이벤트 신호를 받거나 타임아웃이 될 때까지 블로킹 상태로 대기하여 CPU 사용을 줄입니다.&lt;/p&gt;

&lt;p&gt;만약 대기 중 &lt;code&gt;TaskQueue&lt;/code&gt;에 새 task가 들어오면, &lt;code&gt;wakeup&lt;/code&gt; 메커니즘을 통해 &lt;code&gt;select()&lt;/code&gt;의 블로킹을 깨워서 즉시 반환시키고, 루프를 돌며 TaskQueue를 처리할 수 있게 합니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  TaskQueue가 있을 때
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;TaskQueue&lt;/code&gt;에 작업이 있으면 &lt;code&gt;selectNow()&lt;/code&gt;를 호출하여 I/O 이벤트가 있는지 빠르게 확인하고, 곧바로 테스크 실행으로 넘어가 작업 지연을 줄입니다.&lt;/p&gt;

&lt;p&gt;만약 TaskQueue에 작업이 있는 상황에서 &lt;code&gt;select(timeout)&lt;/code&gt;을 호출해 블로킹되면, EventLoop 스레드가 잠들어 테스크 처리가 지연되고 응답성이 떨어지게 됩니다. 반대로 &lt;code&gt;selectNow()&lt;/code&gt;만 계속 수행하면 준비된 I/O 이벤트가 없어도 계속 확인하므로 불필요한 반복으로 busy-wait(CPU 낭비)이 발생할 수 있습니다.&lt;/p&gt;

&lt;p&gt;즉, Netty의 &lt;code&gt;select()&lt;/code&gt;는 상황에 따라 적절한 방식을 선택하여 CPU를 낭비하지 않고 효율적으로 I/O 이벤트를 대기합니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  select() 호출 이후의 내부 동작
&lt;/h4&gt;

&lt;p&gt;앞서 Netty가 상황에 따라 &lt;code&gt;select(timeout)&lt;/code&gt; 또는 &lt;code&gt;selectNow()&lt;/code&gt;를 선택적으로 호출한다는 것을 살펴보았습니다. 이제 이 호출이 실제로 어떤 과정을 거쳐 커널까지 도달하고, 다시 돌아오는지 살펴보겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Selector.select()&lt;/code&gt;를 호출하면 JDK 내부의 &lt;code&gt;SelectorImpl&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;// SelectorImpl.java&lt;/span&gt;
&lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;lockAndDoSelect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&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="o"&gt;?&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;timeout&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;lockAndDoSelect()&lt;/code&gt;는 동기화를 수행한 뒤 Multiplexing I/O를 담당하는 &lt;code&gt;doSelect()&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;// SelectorImpl.java&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;lockAndDoSelect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="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="n"&gt;ensureOpen&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inSelect&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="s"&gt;"select in progress"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;inSelect&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="k"&gt;try&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="n"&gt;publicSelectedKeys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;doSelect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;inSelect&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 &lt;code&gt;doSelect()&lt;/code&gt;는 추상 메서드입니다. 운영체제마다 효율적인 Multiplexing I/O 메커니즘이 다르기 때문에, JDK는 플랫폼별로 다른 구현체를 제공합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS&lt;/th&gt;
&lt;th&gt;구현 클래스&lt;/th&gt;
&lt;th&gt;시스템 콜&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;EPollSelectorImpl&lt;/td&gt;
&lt;td&gt;epoll_wait()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;KQueueSelectorImpl&lt;/td&gt;
&lt;td&gt;kevent()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;WindowsSelectorImpl&lt;/td&gt;
&lt;td&gt;IOCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;이 글에서는 서버 환경에서 가장 많이 사용되는 &lt;strong&gt;Linux의 epoll 기반 구현&lt;/strong&gt;을 중심으로 살펴보겠습니다. (JDK 21 기준)&lt;/p&gt;

&lt;h3&gt;
  
  
  EPollSelectorImpl 인스턴스는 언제 생성되는가?
&lt;/h3&gt;

&lt;p&gt;EPollSelectorImpl 인스턴스는 &lt;code&gt;Selector.open()&lt;/code&gt; 호출 시점에 초기화됩니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;애플리케이션에서 new NioEventLoopGroup(n) 호출&lt;/li&gt;
&lt;li&gt;내부적으로 n개의 NioIoHandler 생성&lt;/li&gt;
&lt;li&gt;각 NioIoHandler 생성자에서 provider.openSelector() 호출&lt;/li&gt;
&lt;li&gt;Linux 환경에서는 EPollSelectorImpl 인스턴스 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;EPollSelectorImpl 생성 시 다음과 같은 초기화가 이루어집니다.&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;EPollSelectorImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SelectorProvider&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="c1"&gt;// 1. epoll 인스턴스 생성 (epoll_create 시스템 콜)&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;epfd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EPoll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

   &lt;span class="c1"&gt;// 2. epoll_wait 결과를 저장할 네이티브 메모리 할당&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;pollArrayAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EPoll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allocatePollArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NUM_EPOLLEVENTS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="c1"&gt;// 3. wakeup용 EventFD 생성&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;eventfd&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;EventFD&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="nc"&gt;IOUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configureBlocking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newFD&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventfd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;efd&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="c1"&gt;// 4. EventFD를 epoll에 EPOLLIN으로 등록&lt;/span&gt;
   &lt;span class="nc"&gt;EPoll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ctl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eventfd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;efd&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;EPOLLIN&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;즉, 하나의 EventLoop마다 하나의 epoll 인스턴스가 매핑됩니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  epoll의 세 가지 시스템 콜
&lt;/h4&gt;

&lt;p&gt;epoll은 세 가지 시스템 콜을 제공합니다&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;epoll_create&lt;/code&gt;: epoll 인스턴스(채널 감시 저장소) 생성&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;epoll_ctl&lt;/code&gt;: 감시할 FD 추가/수정/삭제&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;epoll_wait&lt;/code&gt;: 이벤트(read/write)가 발생할 때까지 대기하고, 이벤트가 발생한 FD 목록을 반환&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JDK의 &lt;code&gt;EPoll.wait()&lt;/code&gt;는 JNI를 통해 커널의 &lt;code&gt;epoll_wait()&lt;/code&gt; 시스템 콜을 직접 호출합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;epoll_wait()&lt;/code&gt;는 미리 할당된 네이티브 메모리의 &lt;code&gt;epoll_event&lt;/code&gt; 구조체 배열에 준비된 이벤트 정보를 채우고, 준비된 이벤트 개수를 반환합니다. 이 배열에는 각 FD와 발생한  이벤트 타입(EPOLLIN/EPOLLOUT/EPOLLERR 등)이 담겨 있습니다.&lt;/p&gt;

&lt;p&gt;EPollSelectorImpl.doSelect()에서 이 메서드들이 실제로 호출되는 흐름을 보면:&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;// EpollSelectorImpl.java&lt;/span&gt;
&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;doSelect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;numEntries&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;processUpdateQueue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// epoll_ctl로 관심 이벤트 변경 반영&lt;/span&gt;
    &lt;span class="n"&gt;processDeregisterQueue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocking&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// epoll_wait 시스템 콜 호출&lt;/span&gt;
        &lt;span class="n"&gt;numEntries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EPoll&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="n"&gt;epfd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pollArrayAddress&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;NUM_EPOLLEVENTS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocking&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 반환된 이벤트 처리&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numEntries&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&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;
  
  
  2. processSelectedKeys() - I/O 이벤트 처리
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EPoll.wait()&lt;/code&gt;가 이벤트 개수를 반환하면, &lt;code&gt;EPollSelectorImpl.processEvents()&lt;/code&gt;가 해당 개수만큼 이벤트 배열을 순회하며 각 FD에 연결된 SelectionKey를 찾아 selectedKeys에 추가합니다. 이후 Netty의 &lt;code&gt;NioIoHandler.processSelectedKeys()&lt;/code&gt;가 &lt;code&gt;seletedKeys&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;// NioIoHandler.java&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;processSelectedKeysOptimized&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;handled&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="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;selectedKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;;&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;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="nc"&gt;SelectionKey&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selectedKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;keys&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;];&lt;/span&gt;
          &lt;span class="n"&gt;selectedKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;keys&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;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// GC를 위해 null 처리&lt;/span&gt;

          &lt;span class="n"&gt;processSelectedKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 각 이벤트 처리&lt;/span&gt;
          &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;handled&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;handled&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;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processSelectedKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SelectionKey&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DefaultNioRegistration&lt;/span&gt; &lt;span class="n"&gt;registration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DefaultNioRegistration&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// 준비된 이벤트를 핸들러에 전달&lt;/span&gt;
      &lt;span class="c1"&gt;// OP_READ  → 데이터 수신&lt;/span&gt;
      &lt;span class="c1"&gt;// OP_WRITE → 데이터 송신&lt;/span&gt;
      &lt;span class="c1"&gt;// OP_CONNECT → 연결 완료&lt;/span&gt;
      &lt;span class="c1"&gt;// OP_ACCEPT → 새 연결 요청&lt;/span&gt;
      &lt;span class="n"&gt;registration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readyOps&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;registration.handle()&lt;/code&gt;은 내부적으로 &lt;code&gt;AbstractNioChannel.AbstractNioUnsafe.handle()&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;// AbstractNioChannel.java:420-450&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;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IoRegistration&lt;/span&gt; &lt;span class="n"&gt;registration&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IoEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;NioIoOps&lt;/span&gt; &lt;span class="n"&gt;nioReadyOps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;NioIoEvent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;ops&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. OP_CONNECT: 연결 완료 처리 (가장 먼저 처리)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nioReadyOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NioIoOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONNECT&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;removeAndSubmit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NioIoOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONNECT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;finishConnect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. OP_WRITE: 쓰기 가능 상태 - 대기 중인 버퍼 전송&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nioReadyOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NioIoOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WRITE&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;forceFlush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. OP_READ / OP_ACCEPT: 데이터 수신 또는 새 연결 수락&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nioReadyOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NioIoOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;READ_AND_ACCEPT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;nioReadyOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NioIoOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NONE&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;read&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;h3&gt;
  
  
  3. runAllTasks() - Non-I/O Task 처리
&lt;/h3&gt;

&lt;p&gt;I/O 이벤트 처리가 끝나면 runAllTasks()가 호출되어 TaskQueue에 쌓인 작업들을 처리합니다. WebClient의 HTTP 요청도 바로 이 단계에서 실제로 전송됩니다.&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;// SingleThreadEventExecutor.java&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;runAllTasks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timeoutNanos&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// 스케줄 큐에서 실행 가능한 태스크를 TaskQueue로 이동&lt;/span&gt;
     &lt;span class="n"&gt;fetchFromScheduledTaskQueue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pollTask&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

     &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeoutNanos&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="n"&gt;getCurrentTimeNanos&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timeoutNanos&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="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;runTasks&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="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(;;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="n"&gt;safeExecute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 테스크 실행&lt;/span&gt;

         &lt;span class="n"&gt;runTasks&lt;/span&gt; &lt;span class="o"&gt;++;&lt;/span&gt;

         &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pollTask&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
             &lt;span class="n"&gt;lastExecutionTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCurrentTimeNanos&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
             &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;

     &lt;span class="n"&gt;afterRunningAllTasks&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  전체 흐름 요약
&lt;/h3&gt;

&lt;p&gt;지금까지 살펴본 내용을 하나의 다이어그램으로 정리하면 다음과 같습니다.&lt;/p&gt;

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

&lt;p&gt;병목이 발생한 인스턴스의 vCPU 수는 2개였습니다. Netty의 EventLoop 스레드 수는 기본적으로 &lt;code&gt;Math.max(availableProcessors(), 4)&lt;/code&gt;로 결정되므로, 이 환경에서는 EventLoop 스레드가 &lt;strong&gt;총 4개&lt;/strong&gt; 존재합니다.&lt;/p&gt;

&lt;p&gt;지금까지 살펴본 바와 같이 Netty의 EventLoop는 Multiplexing I/O 방식으로 동작하기 때문에 적은 수의 스레드로도 많은 동시 요청을 처리할 수 있습니다. &lt;code&gt;epoll_wait()&lt;/code&gt;은 수천 개의 채널이 등록되어 있어도 실제로 I/O 이벤트가 발생한 채널만 반환하므로, 동시 요청 수가 EventLoop 스레드 수보다 많다고 해서 병목이 발생하지는 않습니다.&lt;/p&gt;

&lt;p&gt;따라서 &lt;strong&gt;"동시 요청 10개 &amp;gt; EventLoop 스레드 4개"는 병목의 원인이 아닙니다.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  또 다른 가설: Parallel Scheduler 스레드 경합
&lt;/h3&gt;

&lt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;omsClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/refine-address"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyToMono&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressOutput&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;timeout&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;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// ← 여기&lt;/span&gt;
   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retryWhen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fixedDelay&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="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// ← 여기&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;timeout()&lt;/code&gt;과 &lt;code&gt;retryWhen()&lt;/code&gt;의 fixedDelay()는 내부적으로 &lt;strong&gt;Schedulers.parallel()&lt;/strong&gt;을 사용하여 타이머를 스케줄링합니다. 그리고 Parallel Scheduler의 스레드 수 역시 CPU 코어 수에 비례합니다.&lt;/p&gt;

&lt;p&gt;vCPU 2개 환경에서 동시 요청 수가 10개인 환경에서 할당된 Parallel Scheduler 스레드 수보다 많은 요청을 처리하면서 병목이 발생했을 가능성이 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  검증
&lt;/h2&gt;

&lt;p&gt;실제로 CPU 코어 수 제한이 WebClient 동시 호출 성능에 미치는 영향을 측정하기 위해 &lt;code&gt;JMH(Java Microbenchmark Harness)&lt;/code&gt;를 사용하여 벤치마크를 수행했습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  테스트 환경
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;공통 설정&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;동시 호출 수 (Concurrency): 10&lt;/li&gt;
&lt;li&gt;총 요청 수 (totalRequests): 50&lt;/li&gt;
&lt;li&gt;서버 응답 지연 (serverLatencyMs): 200m&lt;/li&gt;
&lt;li&gt;측정 방식: 10회 반복 측정 후 평균값 산출&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Case 1: CPU 코어 10개 (refineAddress_concurrency10_cpu10)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;JVM 옵션: &lt;code&gt;-XX:ActiveProcessorCount=10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Available Processors: 10&lt;/li&gt;
&lt;li&gt;Reactor Schedulers DefaultPoolSize: 10&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;h3&gt;
  
  
  Case 2: CPU 코어 2개 (refineAddress_concurrency10_cpu2)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;JVM 옵션: &lt;code&gt;-XX:ActiveProcessorCount=2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Available Processors: 2&lt;/li&gt;
&lt;li&gt;Reactor Schedulers DefaultPoolSize: 2&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;h3&gt;
  
  
  결과
&lt;/h3&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;CPU 10 코어&lt;/th&gt;
&lt;th&gt;CPU 2 코어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;평균 처리 시간&lt;/td&gt;
&lt;td&gt;1035.498 ms&lt;/td&gt;
&lt;td&gt;1049.652 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안정성 (Stdev)&lt;/td&gt;
&lt;td&gt;7.496&lt;/td&gt;
&lt;td&gt;18.554&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;신뢰구간 폭&lt;/td&gt;
&lt;td&gt;22.667 ms&lt;/td&gt;
&lt;td&gt;56.103 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactor 워커 수&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;예상과 달리, CPU 코어 수(Parallel Scheduler Pool Size)에 따른 처리 시간 차이는 약 14ms(1.4%) 로 거의 없었습니다.&lt;/p&gt;

&lt;p&gt;스레드 수가 2개에서 10개로 증가해도 성능 향상이 미미합니다. timeout/retry의 스케줄링 자체는 매우 가벼운 작업이므로, 스레드 수가 적어도 큰 영향을 주지 않는것을 확인할 수 있었습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;벤치 마크 코드&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RunBenchmark&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="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;RunnerException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;Options&lt;/span&gt; &lt;span class="n"&gt;options&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;OptionsBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OmsWebClientConcurrencyBenchmark&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="na"&gt;getSimpleName&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forks&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warmupIterations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 각 벤치 마크 실행 전 최적화를 위한 웜업&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warmupTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeValue&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="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurementIterations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 각 벤치마크 측정. 10회 반복&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurementTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeValue&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="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timeUnit&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addProfiler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JavaFlightRecorderProfiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// JFR 프로파일러 추가&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resultFormat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultFormatType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jmh-results.json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Runner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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="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="nd"&gt;@BenchmarkMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AverageTime&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;OmsWebClientConcurrencyBenchmark&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;CONCURRENCY_CALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@State&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Benchmark&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BenchState&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 한 번의 벤치마크 호출당 요청 총 개수&lt;/span&gt;

        &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;serverLatencyMs&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 목 서버가 응답 전 인위적으로 대기할 지연(ms)&lt;/span&gt;

        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;DisposableServer&lt;/span&gt; &lt;span class="n"&gt;server&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;OmsService&lt;/span&gt; &lt;span class="n"&gt;omsService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nd"&gt;@Setup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Trial&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;setup&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;availableProcessors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;availableProcessors&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;reactorPoolSize&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;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reactor.schedulers.defaultPoolSize"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;availableProcessors&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n========================================"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Benchmark started: OMS WebClient concurrency(10) under different CPU core caps"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Params: totalRequests="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;totalRequests&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", serverLatencyMs="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;serverLatencyMs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Available processors (current JVM): "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;availableProcessors&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reactor Schedulers DefaultPoolSize: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reactorPoolSize&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"========================================"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// 목 응답 JSON&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"\"jibeonAddress\":{\"primaryAddress\":\"서울시 강남구\",\"secondaryAddress\":\"테스트로 123\",\"zipCode\":\"06200\"},"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"\"roadAddress\":{\"primaryAddress\":\"서울시 강남구\",\"secondaryAddress\":\"테스트로 123\",\"zipCode\":\"06200\"},"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"\"sigungu\":\"강남구\",\"dong\":\"역삼동\",\"provider\":\"OMS\",\"hcode\":\"11110\",\"bcode\":\"1111010100\",\"buildingNumber\":\"123\""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.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;port&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/refine-address"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delay&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;ofMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverLatencyMs&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;just&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;)))))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bindNow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;WebClient&lt;/span&gt; &lt;span class="n"&gt;omsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://127.0.0.1:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;WebClient&lt;/span&gt; &lt;span class="n"&gt;fbkClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://127.0.0.1:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;omsService&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;OmsServiceImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;omsClient&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fbkClient&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nd"&gt;@TearDown&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Trial&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;tearDown&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disposeNow&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;invokeRefineAddressBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OmsService&lt;/span&gt; &lt;span class="n"&gt;omsService&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Flux&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="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&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="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defer&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;omsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refineAddressIgnore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"서울시 강남구"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"테스트로 123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultIfEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NO_RESPONSE"&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="no"&gt;CONCURRENCY_CALL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collectList&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 코어 수 2로 실행&lt;/span&gt;
    &lt;span class="nd"&gt;@Benchmark&lt;/span&gt;
    &lt;span class="nd"&gt;@Fork&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jvmArgsAppend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:ActiveProcessorCount=2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:FlightRecorderOptions=threadbuffersize=16k,globalbuffersize=10m,memorysize=50m"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:StartFlightRecording=settings=profile"&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;refineAddress_concurrency10_cpu2&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BenchState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Blackhole&lt;/span&gt; &lt;span class="n"&gt;blackhole&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invokeRefineAddressBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;omsService&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 코어 수 10으로 실행&lt;/span&gt;
    &lt;span class="nd"&gt;@Benchmark&lt;/span&gt;
    &lt;span class="nd"&gt;@Fork&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jvmArgsAppend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:ActiveProcessorCount=10"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:FlightRecorderOptions=threadbuffersize=16k,globalbuffersize=10m,memorysize=50m"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-XX:StartFlightRecording=settings=profile"&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefineAddressDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;refineAddress_concurrency10_cpu10&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BenchState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Blackhole&lt;/span&gt; &lt;span class="n"&gt;blackhole&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invokeRefineAddressBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;omsService&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  지연의 원인: Cold Start
&lt;/h2&gt;

&lt;p&gt;원인을 분석하는 동안 유사한 지표를 가진 Trace를 추가로 수집할 수 있었는데요. 지표에서 한 가지 공통된 패턴을 발견할 수 있었습니다.&lt;/p&gt;

&lt;p&gt;지연이 발생한 모든 케이스가 애플리케이션 배포 직후 첫 번째 외부 API 호출 시점에 발생한다는 사실을 발견했습니다.&lt;/p&gt;

&lt;p&gt;Netty의 리소스는 &lt;strong&gt;Lazy Initialization&lt;/strong&gt; 방식으로 동작합니다. 즉, &lt;code&gt;WebClient&lt;/code&gt;를 생성하는 시점이 아니라 &lt;strong&gt;실제로 첫 번째 HTTP 요청을 보내는 시점&lt;/strong&gt;에 초기화가 이루어집니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cold Start 해결책
&lt;/h2&gt;

&lt;p&gt;두 가지 해결책을 검토했습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Connection Pool에 사전 커넥션 맺기&lt;/code&gt;: 미리 연결을 생성해 대기&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Warmup 옵션&lt;/code&gt;: 리소스를 사전 로드하되 실제 연결은 필요 시점에 생성&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Connection Pool에 사전 커넥션 생성
&lt;/h3&gt;

&lt;p&gt;처음에는 애플리케이션 시작 시점에 미리 커넥션을 생성하여 풀에 대기시키는 방법을 고려하였습니다. 커넥션은 &lt;code&gt;maxIdleTime&lt;/code&gt;, &lt;code&gt;maxLifeTime&lt;/code&gt; 만큼 살아있다가 종료되는데, 기본값은 &lt;code&gt;-1&lt;/code&gt;로 무제한입니다. 즉, 별도로 설정하지 않으면 커넥션이 시간 제한 없이 풀에 유지됩니다.&lt;/p&gt;

&lt;p&gt;단, 두 설정값을 기본값(-1)으로 두면 서버 측의 &lt;code&gt;keepAliveTimeout&lt;/code&gt; 설정과 충돌할 수 있습니다. &lt;/p&gt;

&lt;p&gt;서버에서 커넥션을 먼저 끊으면 클라이언트는 이미 닫힌 커넥션으로 요청을 보내게 되어 &lt;code&gt;"Connection reset by peer"&lt;/code&gt; 오류가 발생할 수 있습니다. 따라서 실무에서는 &lt;strong&gt;서버의 keepAliveTimeout보다 작은 값으로 maxIdleTime을 설정&lt;/strong&gt;하는 것이 권장됩니다.&lt;/p&gt;

&lt;p&gt;결국 애플리케이션 시작 시점에 미리 커넥션을 생성해 놓더라도 일정 시간 요청이 없으면 유휴 커넥션이 자동 해제되므로 트래픽이 간헐적인 '주문 등록'과 같은 케이스에는 적합하지 않습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Netty 이슈에서도 ConnectionPool을 웜업하는건 해결책이 아니며 고려사항이 아니라는것을 확인할 수 있습니다. (&lt;a href="https://github.com/reactor/reactor-netty/pull/1455" rel="noopener noreferrer"&gt;Add warmup functionality for the servers/clients #1455&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  Warmup 옵션
&lt;/h3&gt;

&lt;p&gt;반면 warmup은 실제 TCP 커넥션을 맺지 않고, 이후 요청에서 재사용되는 네트워크 리소스들을 애플리케이션 시작 시점에 미리 초기화 합니다. 따라서 커넥션이 해제되더라도 EventLoop, DNS 리졸버 등은 이미 로드되어 있어, 후속 요청에서 초기화 비용이 발생하지 않습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebClientConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

   &lt;span class="nd"&gt;@Bean&lt;/span&gt;
   &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt; &lt;span class="nf"&gt;omsWebClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://oms-api.example.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

       &lt;span class="c1"&gt;// 애플리케이션 시작 시 warmup 수행&lt;/span&gt;
       &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warmup&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;block&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;WebClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientConnector&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;ReactorClientHttpConnector&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Warmup으로 미리 준비되는 리소스&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Warmup을 호출하면, HttpClient/TcpClient 내부에서 다음 리소스들이 구성에 따라 사전에 초기화됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EventLoopGroup&lt;/strong&gt;: EventLoop 스레드 풀 생성&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS Resolver&lt;/strong&gt;: 비동기 DNS 리졸버 초기화&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native transport 라이브러리&lt;/strong&gt;: epoll 등 네이티브 루프 및 관련 라이브러리 로드&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL Context&lt;/strong&gt;: TLS 핸드셰이크용 SSL 엔진 (HTTPS인 경우)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;지금까지 WebClient의 간헐적 지연 문제를 추적하며 Netty EventLoop가 Selector와 TaskQueue를 관리하는 방식, Linux epoll의 Multiplexing I/O 메커니즘, 그리고 실제 원인이었던 Netty의 Lazy Initialization과 warmup 해결책까지 살펴보았습니다.&lt;/p&gt;

&lt;p&gt;이 과정을 통해 단순히 라이브러리를 사용하는 것을 넘어, 내부 동작 원리를 이해하는 것이 얼마나 중요한지 다시 한번 깨달았습니다. 표면적인 증상만 보고 '코어 수를 느리자'거나 '타임아웃을 조정하자'는 식의 접근 대신, 각 레이어를 단계별로 파고들면서 병목이 발생할 수 있는 지점을 하나씩 가시화하고 범위를 좁혀나갈 수 있었습니다.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI 브라우저를 활용한 PR 메세지 자동화</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Sun, 14 Dec 2025 07:07:05 +0000</pubDate>
      <link>https://dev.to/headf1rst/ai-beuraujeoreul-hwalyonghan-pr-meseji-jadonghwa-51op</link>
      <guid>https://dev.to/headf1rst/ai-beuraujeoreul-hwalyonghan-pr-meseji-jadonghwa-51op</guid>
      <description>&lt;p&gt;개발자에게 "코드 작성"과 "PR(Pull Request) 작성" 중 무엇이 더 고통스러운지 묻는다면, 의외로 많은 분이 후자를 택합니다. 구현은 즐겁지만, 그것을 남에게 글로 설명하는데는 생각보다 상당한 시간이 소모되기 때문입니다.&lt;/p&gt;

&lt;p&gt;최근 Copilot이나 IntelliJ AI 등 다양한 도구가 등장하며 PR 작성을 돕고 있지만, 여전히 '2%' 부족함을 느낍니다. 오늘은 그 부족한 2%를 채우고, 리뷰어와 작성자 모두가 행복해지는 PR 자동화 워크플로우를 공유하고자 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 좋은 Pull Request 란?
&lt;/h2&gt;

&lt;p&gt;기술적인 자동화를 논하기 전에, 우리가 작성해야 할 '좋은 PR'의 정의부터 명확히 해야 합니다.&lt;/p&gt;

&lt;p&gt;좋은 PR의 핵심은 &lt;strong&gt;'리뷰어에 대한 공감'&lt;/strong&gt;입니다. 리뷰어는 바쁜 시간을 쪼개서 코드를 봅니다. 리뷰어의 시간을 아껴주고, 코드의 의도를 명확하게 전달하는것이 PR의 가장 중요한 목표입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  좋은 PR의 구성요소
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;명확한 맥락:&lt;/strong&gt; 단순히 코드가 '무엇'이 변했는지는 diff만 봐도 알 수 있습니다. 중요한 것은 &lt;strong&gt;'왜 이 변경이 필요했는가'&lt;/strong&gt;입니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;직관적 시각 자료:&lt;/strong&gt; 백 마디 말보다 한 장의 스크린샷이나 다이어그램이 훨씬 빠른 이해를 돕습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;작고 집중된 단위:&lt;/strong&gt; 하나의 PR은 하나의 이슈만 다루어야 리뷰와 병합의 리스크가 줄어듭니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;코드 리뷰를 하는 이유 중 하나는 팀원 간의 작업 방향 정렬과 개발 과정에서의 실수 방지입니다.&lt;/p&gt;

&lt;p&gt;작업 배경을 상세히 기술하면 리뷰어는 단순히 코드 변경사항만 보는 것이 아니라, 왜 이런 변경이 필요했는지 맥락을 이해할 수 있습니다. 이러한 컨텍스트 공유는 리뷰어가 더 넓은 시야에서 코드를 바라볼 수 있게 하여, 단순 문법적 오류나 컨벤션 위반을 넘어 아키텍처적 개선점, 잠재적 사이드 이펙트, 대안적 접근 방식 등 다양한 관점의 피드백을 제공할 수 있게 됩니다.&lt;/p&gt;

&lt;p&gt;결과적으로 배경 설명이 잘 된 PR은 리뷰의 질을 높이고, 팀 전체의 도메인 지식과 시스템 이해도를 향상시키는 효과적인 지식 공유의 수단이 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 현재 AI Assistant의 한계: '맥락'의 부재
&lt;/h2&gt;

&lt;p&gt;최근의 생성형 AI 도구들(GitHub Copilot, IntelliJ AI 등)은 코드 변경 사항(Diff)을 요약하는 데 탁월한 능력을 보여줍니다. 버튼 하나만 누르면 아래와 같은 요약을 순식간에 만들어냅니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Copilot Agent가 생성한 PR&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;그러나 AI가 만든 PR 메시지는 초안으로는 충분하지만, 작업의 배경까지는 담아내지 못해 코드 수정의 근본적인 이유를 파악하기 어렵다는 한계가 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;배경 정보 누락:&lt;/strong&gt; AI는 코드 자체만 보고 요약하므로, "사용자가 특정 상황에서 겪은 불편함"이나 "기획 의도" 같은 외부 맥락을 알지 못합니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;근본적 이유 부재:&lt;/strong&gt; 코드를 '수정했다'는 사실은 알지만, '왜 수정해야만 했는지'에 대한 비즈니스적/기술적 배경은 설명하지 못합니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결국, 리뷰어가 가장 궁금해하는 &lt;strong&gt;'작업 배경'&lt;/strong&gt;을 채우는 일은 여전히 사람의 몫으로 남게 됩니다. 그리고 배경을 간결하고 핵심적으로 정리하는 것은 PR 작성에서 가장 많은 시간을 잡아먹습니다.&lt;/p&gt;

&lt;p&gt;이 과정을 AI가 대신할 수 있다면, 리뷰어에게 코드의 의도를 명확하게 전달하면서 PR 작성에 드는 시간을 크게 줄일 수 있을 것입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Jira 티켓에서 맥락 가져오기
&lt;/h2&gt;

&lt;p&gt;그렇다면 어디서 맥락을 가져올 수 있을까요? 저는 개발을 시작하기 전에 티켓에 해결하고자 하는 문제와 목표를 명확히 정의합니다.&lt;/p&gt;

&lt;p&gt;티켓에는 작업을 한눈에 파악할 수 있도록 "무엇을", "왜" 하는지를 간결하게 담습니다. 예를 들어 "주문 취소 시 재고 복구 실패 이슈 수정"처럼 핵심 문제와 해결 방향을 제목에서부터 드러냅니다.&lt;/p&gt;

&lt;p&gt;그 다음 작업의 배경과 목적을 상세히 기술합니다. 현재 어떤 문제가 발생하고 있는지(AS-IS), 왜 이 작업이 필요한지, 어떤 비즈니스 영향이 있는지를 구체적으로 작성하고, 작업을 통해 달성하고자 하는 상태(TO-BE)와 명확한 완료 기준을 함께 정의합니다.&lt;/p&gt;

&lt;p&gt;"PR 쓰기도 귀찮은데 Jira까지 자세히 쓰라고요?"라고 반문하실 수 있습니다. 하지만 &lt;strong&gt;문제 정의는 AI가 아닌 사람이 해야 할 영역&lt;/strong&gt;입니다. 티켓을 충실히 작성하면 다음과 같은 이점이 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  작업 전 Jira 티켓을 풍부하게 작성하면 좋은점
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;명확한 목표 설정과 작업 집중&lt;/strong&gt;: "로그인 버그 수정"이라는 한 줄짜리 티켓은 '어떤 상황에서', '어떤 사용자가', '어떻게' 버그를 겪는지 알려주지 않습니다. 상세한 설명, 재현 방법, 기대 결과가 담긴 티켓은 내가 무엇을 해야 하는지 명확히 알려주어 불필요한 고민 없이 작업에만 집중하게 해줍니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;정확한 계획 수립과 예측 가능성 확보&lt;/strong&gt;: 티켓이 상세할수록 작업의 규모와 복잡도를 더 정확하게 예측할 수 있습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;미래의 나를 위한 기록&lt;/strong&gt;: 몇 달 뒤 내가 작성한 코드를 다시 보게 될 때, Jira 티켓은 "내가 왜 이 코드를 이렇게 작성했더라?"에 대한 완벽한 답변이 되어 줍니다. 유지보수와 기능 확장이 훨씬 쉬워집니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. AI 브라우저로 워크플로우 완성하기
&lt;/h2&gt;

&lt;p&gt;우리의 목표는 &lt;strong&gt;"PR 작성에 드는 시간과 노력을 최소화하면서, 퀄리티는 극대화하는 것"&lt;/strong&gt;입니다. 이를 위해 AI 브라우저인 Commet(혹은 Dia)의 기능을 활용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Commet은 단순한 브라우징을 넘어, 사용자가 자주 사용하는 프롬프트를 &lt;strong&gt;'Shotcuts'&lt;/strong&gt; 형태로 저장하고 &lt;code&gt;/Shotcuts&lt;/code&gt;로 호출할 수 있는 기능을 제공합니다. 이를 통해 매번 긴 프롬프트를 작성할 필요 없이, Jira의 문맥과 코드 변경 사항을 결합할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  자동화 워크플로우
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jira 티켓 작성:&lt;/strong&gt; 작업 전, 이슈의 배경(Why)과 해결 방안(What)을 Jira에 명확히 기록합니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commet Shortcuts 등록:&lt;/strong&gt; Commet 브라우저에 PR 자동 생성을 위한 Shortcuts를 등록해줍니다.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2mgw687c31cvmbf0t44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2mgw687c31cvmbf0t44.png" alt="shortcuts" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PR 수정 화면에서 Commet의 사이드 패널을 열고 미리 등록해 둔 &lt;strong&gt;PR 작성 Shortcuts&lt;/strong&gt;을 호출&lt;br&gt;
합니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이렇게 하면 AI는 Jira에서 &lt;strong&gt;'작업의 의도(Why)'&lt;/strong&gt;를 가져오고, Git Diff에서 &lt;strong&gt;'구현 내용(What)'&lt;/strong&gt;을 가져와 PR 메시지를 생성해 줍니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR 자동 생성에 사용한 프롬프트
&lt;/h2&gt;

&lt;p&gt;마지막으로 PR 자동 생성에 사용한 프롬프트를 공유드리며 글을 마무리하도록 하겠습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;당신은 명확하고 규칙적인 Pull Request 설명을 잘 작성하는 숙련된 소프트웨어 엔지니어입니다.  
당신의 주요 목표는 주어진 Jira 티켓과 PR 세부 정보를 기반으로, 우리 팀의 컨벤션을 **엄격히 준수하는 PR 메시지**를 생성하는 것입니다.

이 페이지의 브랜치 정보를 참고하고, “ABCD”로 시작하는 관련 Jira 티켓을 열어 그 안에 작성된 맥락(context)을 활용해 Pull Request 설명을 작성하세요.  
아래 링크를 통해 Jira 보드에 접속한 뒤, 해당 DPDD 티켓을 검색하여 열 수 있습니다.

Jira 보드 링크:  
`https://jiraboard.atlassian.net/jira/software/c/projects/DPDD/boards/5218?assignee=712020%3A8ef336bd-458e-4630-a79f-d0ea76930601`

PR 메시지는 **한국어로 작성하세요.**

---

### 따라야 할 PR 메시지 템플릿

## 요약 ✍️  
*Jira 티켓과 PR 정보를 기반으로 간결한 요약을 작성하세요.*  
먼저 이번 변경의 **배경과 목적(Why)** 을 설명하고,  
그 다음 **구체적인 변경 내용(What, How)** 을 불릿 포인트로 정리하세요.

이 문서에 포함된 클래스나 주요 구성요소 간의 관계, 역할, 상호작용을 설명하기 위해  
`mermaid` 다이어그램을 코드 블록 안에 렌더링하세요.  
다이어그램은 문서의 변경점과 새롭게 추가된 부분에 초점을 맞추어야 합니다.

**형식(Format):**
- 익숙한 ASCII 문자만 사용합니다. 결과는 고정폭(monospaced) 폰트로 렌더링됩니다.  
- 문서의 **첫 줄과 마지막 줄에는 60개의 “_” 문자로 된 수평선**을 추가합니다.  
- 각 줄은 **최대 60자 이내**로 제한합니다.  
- 파일 관계를 설명할 필요가 있다면 디렉터리/계층 구조를 사용하고,  
  그렇지 않다면 흐름도(Flowchart) 또는 시각적인 형태로 표현합니다.  
- 문서나 내용이 제공되지 않았다면, 내용을 요청하세요.

---

## 리뷰어가 참고하면 좋은 링크 🔗  
- Jira url: [여기에 Jira URL 삽입]

---

## 먼저 읽기 좋은 진입점 🚘️  
*PR 세부 정보를 기반으로, 리뷰 시 가장 먼저 보면 좋은 주요 진입 파일(Controller, Consumer 등)을 나열하세요.*

---

## 리뷰어에게 🙏  
*PR의 복잡한 로직, 트레이드오프, 또는 집중해서 봐주었으면 하는 부분을 짧게 설명하세요.*  
너무 세부적이지 않게, 상위 레벨에서 작성합니다.

---

## 체크 리스트 ✅  
- [x] 테스트 코드를 작성했습니다. (코드 변경이 있는 경우)  
- [x] 커밋 전 SonarLint 분석을 수행했습니다.  
- [x] 셀프 리뷰를 진행했습니다.  
- [x] Jira 티켓을 업데이트했습니다.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Swagger의 사실과 오해: API-First Development</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Mon, 01 Dec 2025 14:07:04 +0000</pubDate>
      <link>https://dev.to/headf1rst/swaggeryi-sasilgwa-ohae-api-first-development-3kcc</link>
      <guid>https://dev.to/headf1rst/swaggeryi-sasilgwa-ohae-api-first-development-3kcc</guid>
      <description>&lt;p&gt;개발에서 가장 중요하게 생각하는것 중 하나는 '인터페이스'입니다.&lt;br&gt;
인터페이스를 잘 정의하는 것은 시스템의 일관성과 확장성을 보장하고 변화에 유연하게 대응할 수 있는 기반이 되어줍니다.&lt;/p&gt;

&lt;p&gt;새로운 API를 개발할때 가장 먼저 마주하게 되는 인터페이스는 바로 API 명세인데요. API 명세를 잘 정의하기 위해서는 프론트엔드 개발자와 백엔드 개발자간의 효율적인 소통이 필수적입니다.&lt;/p&gt;

&lt;p&gt;그렇다면 어떻게 해야 효율적으로 API 명세에 대해 소통할 수 있을까요?&lt;br&gt;
저의 경우, 컨플루언스나 지라 티켓에 API 명세를 작성하거나 임의의 Controller를 구현한 Mock API를 개발환경에 배포한 뒤 Swagger 문서를 공유하는 방식으로 프론트와 소통하였는데요.&lt;/p&gt;

&lt;p&gt;이러한 소통 방식은 평소에는 문제가 없지만 짧은 개발 일정 속에 제공해야할 API 수가 많을 경우, 문서와 실제 API가 불일치하거나 다른 작업의 영향으로 인해 빠르게 Mock API를 개발 환경에 제공하지 못하는 상황이 발생했습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  무엇이 문제인가
&lt;/h2&gt;

&lt;p&gt;문제의 근본적인 원인은 Swagger를 단순히 코드 작성 후 자동으로 문서를 생성해주는 도구로만 생각하고 사용했기 때문입니다. 즉, 코드를 작성해야 Swagger 문서가 만들어지다 보니 API 디자인과 소통이 &lt;strong&gt;Code-First&lt;/strong&gt; 방식으로 진행되었습니다.&lt;/p&gt;

&lt;p&gt;코드와 동기화된 Swagger 문서는 서버가 배포된 이후에 갱신되기 때문에 API 논의 시점에 딜레이가 발생합니다. 이러한 딜레이를 해결하기 위해 별도의 문서를 만들어 공유해야 했습니다. 하지만 작성자마다 문서 포맷과 표현 방식이 달라 일관성을 유지하기 어려웠습니다. 뿐만 아니라 Swagger 문서가 생성되고 나서는 제대로 관리되지 않았기 때문에 신뢰할 수 없는 문서가 되어갔습니다.&lt;/p&gt;

&lt;p&gt;결국 API 소통을 위해서는 코드보다 먼저 문서가 선행되어야 합니다. 이러한 접근 방식을 실현하기 위해 등장한 것이 바로 OpenAPI Specification을 활용한 &lt;strong&gt;API-First&lt;/strong&gt; 개발 방식입니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  OpenAPI Specification이란?
&lt;/h2&gt;

&lt;p&gt;OpenAPI Specification(OAS)에 대해 알아보기 전에 OAS가 어떠한 문제를 해결하기 위해 등장한 기술인지 먼저 알아보도록 하겠습니다.&lt;/p&gt;

&lt;p&gt;사실 OAS의 기원은 Swagger에서 시작되었습니다. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Swagger API project was created in 2011 by Tony Tam, technical co-founder of the dictionary site Wordnik. During the development of Wordnik's products, the need for automation of API documentation and client SDK generation became a major source of frustration. Tam designed a simple JSON representation of the API...&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Swagger_(software)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Swagger_(software)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swagger의 시작점은 단순한 문서 자동화 툴이 아니었습니다. 2010년대 초, 온라인 사전 서비스 Wordnik를 개발하던 Tony Tam은 API 문서화와 클라이언트 SDK 반복 생성에 점점 지쳐가던 중, “API의 동작을 사람이 아닌 명세(specification)로 정의할 수 있다면 서버와 클라이언트가 동일한 계약을 공유할 수 있지 않을까?”라는 질문에서 Swagger를 고안하게 되었습니다.&lt;/p&gt;

&lt;p&gt;Tony Tam은 API를 간결하게 JSON(YAML)으로 기술하는 방식을 설계했고, "코드보다 먼저 계약을 정의하자"라는 단순하지만 강력한 철학을 내세웠습니다. Swagger 명세는 단순한 문서 형식을 넘어, API의 요청-응답 구조와 스키마, 보안, 상태 코드 등 동작의 규약을 포괄하는 '계약(contract)' 그 자체를 기술하는 수단이었습니다. 즉, Swagger의 본래 목적은 API-First(Contract-First) 개발 문화의 정착에 있었던 셈입니다. &lt;/p&gt;

&lt;p&gt;실제로 많은 개발자가 말하는 Swagger는 Swagger 그 자체가 아니라 Spring 진영에서 구현된 Springdoc 혹은 Springfox 라이브러리 입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'io.springfox:springfox-boot-starter:3.0.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Springdoc은 코드에 &lt;code&gt;@Tag&lt;/code&gt;, &lt;code&gt;@Operation&lt;/code&gt; 같은 어노테이션을 붙이면 자동으로 Swagger 형식의 JSON 혹은 YAML 명세를 생성해주는 도구입니다. 명세를 작성하는 게 아니라 코드로부터 명세를 추출하는 Code-First 접근인 것입니다.&lt;/p&gt;

&lt;p&gt;Code-First 접근의 가장 큰 한계는 명세와 실제 구현 사이의 신뢰가 무너진다는 점입니다. 명세는 항상 이미 작성된 코드를 전제로 생성되기 때문에, 변화가 생길 때마다 명세가 뒤처지게 마련입니다.&lt;/p&gt;

&lt;p&gt;반면 API-First 방식은 명세가 먼저 존재하고, 구현은 그 계약을 이행하는 수단일 뿐입니다. API의 요청 구조, 응답 형식, 각종 제약 조건과 오류 모델까지 우선적으로 합의한 뒤, 서버와 클라이언트가 명세를 기준으로 각자 코드를 작성하거나 검증합니다. 이 때 명세서는 단순한 문서가 아니라, 팀 간 신뢰를 담은 실행 가능한 계약이 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAS를 통한 API-First 실천
&lt;/h2&gt;

&lt;p&gt;이제 API-First 방식을 적용해서 간단한 게시판 서비스를 만들어보겠습니다.&lt;/p&gt;

&lt;p&gt;먼저 API 명세를 관리할 별도의 GitHub 저장소를 생성합니다. 클라이언트와 서버 개발자는 각자의 프로젝트에 이 저장소를 서브모듈로 추가해 동일한 명세를 공유하고 효율적으로 관리할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git 서브모듈 추가 방법
&lt;/h3&gt;

&lt;p&gt;각자의 레포지토리에서 아래 명령어로 서브모듈을 추가합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule add https://github.com/headF1rst/simple-dash-board-oas.git contract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;여기서 &lt;code&gt;contract&lt;/code&gt;는 서브모듈이 생성될 디렉터리 이름입니다. 명령어 실행 후 프로젝트 안에 서브모듈이 정상적으로 추가된 것을 확인할 수 있습니다.&lt;/p&gt;

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

&lt;p&gt;API 명세 작성은 서비스의 전역 정보를 담는 메인 진입점 파일인 &lt;code&gt;openapi.yaml&lt;/code&gt; 부터 시작합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;openapi.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Simple Dashboard API&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;게시판 서비스 API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.simpledashboard.com'&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Production server&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080'&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Local development server&lt;/span&gt;

&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# 게시글 목록 조회 및 생성 엔드포인트&lt;/span&gt;
  &lt;span class="na"&gt;/v1/articles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./paths/articles.yaml'&lt;/span&gt;
    &lt;span class="c1"&gt;# 특정 게시글 조회, 수정, 삭제 엔드포인트&lt;/span&gt;
  &lt;span class="s"&gt;/v1/articles/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./paths/articles-by-id.yaml'&lt;/span&gt;

&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;securitySchemes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bearerAuth&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;http&lt;/span&gt;
      &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bearer&lt;/span&gt;
      &lt;span class="na"&gt;bearerFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JWT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$ref&lt;/code&gt; 키워드를 사용해 다른 위치의 스키마 파일을 참조할 수 있습니다. 이를 통해서 진입점 역할을 하는 &lt;code&gt;openapi.yaml&lt;/code&gt; 파일이 지나치게 비대해지는 것을 막고, 하나의 정의를 여러 곳에서 재사용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./paths&lt;/code&gt; 디렉터리 하위에 게시글 목록 API 명세를 별도의 YAML 파일로 분리해 관리해 주었습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;articles.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GET/POST /v1/articles&lt;/span&gt;
&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;게시글 목록 조회&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;게시글 목록을 조회합니다. 페이징을 지원합니다.&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listArticles&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Article&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/parameters/BoardIdQuery.yaml'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/parameters/WriterIdQuery.yaml'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/parameters/PageQuery.yaml'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/parameters/SizeQuery.yaml'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/parameters/SortQuery.yaml'&lt;/span&gt;
  &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;조회 성공&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/schemas/ArticleListResponse.yaml'&lt;/span&gt;
  &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;400'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/responses/400BadRequest.yaml'&lt;/span&gt;
  &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;500'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/responses/500ServerError.yaml'&lt;/span&gt;

&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;새로운 게시글 생성&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;createArticle&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Article&lt;/span&gt;
  &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/schemas/ArticleCreateRequest.yaml'&lt;/span&gt;
  &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;201'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;생성 성공&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/schemas/ArticleResponse.yaml'&lt;/span&gt;
  &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;400'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../components/responses/400BadRequest.yaml'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;전체 디랙토리 구조는 다음과 같습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract/
├── openapi.yaml                      # 메인 진입점 (다른 파일들을 $ref로 참조)
├── paths/                            # API 엔드포인트 정의
│   ├── articles.yaml                 # GET/POST /v1/articles
│   └── articles-by-id.yaml           # GET/PUT/DELETE /v1/articles/{id}
├── components/
│   ├── schemas/                      # 데이터 모델 (DTO)
│   ├── parameters/                   # 공통 파라미터
│   ├── responses/                    # 공통 응답
│   └── examples/                     # 예제 데이터
├── openapi-templates/spring/         # 커스텀 Mustache 템플릿
│   ├── api.mustache                  # API 인터페이스 템플릿
│   ├── generatedAnnotation.mustache  # @Generated 제거용
│   └── licenseInfo.mustache          # 라이선스 헤더
└── build.gradle                      # OpenAPI Generator 설정
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;yaml 파일을 작성할 때는 IntelliJ Plugin인 &lt;a href="https://plugins.jetbrains.com/plugin/14394-openapi-specifications" rel="noopener noreferrer"&gt;OpenAPI Specifications&lt;/a&gt;이나 &lt;a href="https://editor.swagger.io/" rel="noopener noreferrer"&gt;Swagger Editor&lt;/a&gt;를 활용하면 즉각적인 피드백을 받을 수 있어, 명세 작성과 검증을 훨씬 수월하게 진행할 수 있습니다.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  OpenAPI Generator 인터페이스 자동 생성
&lt;/h3&gt;

&lt;p&gt;OpenAPI Generator를 사용하면 애플리케이션 빌드 시점에, yaml로 정의한 API 명세를 기반으로 인터페이스를 자동 생성할 수 있습니다. 이렇게 생성된 인터페이스를 기반으로 Controller 구현체를 작성하면, “코드로부터 명세를 생성하는 방식”이 아니라 “명세로부터 코드를 생성하는 방식”으로 개발할 수 있습니다.&lt;/p&gt;

&lt;p&gt;먼저 OpenAPI Generator를 사용하기 위한 Gradle 설정을 추가합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;build.gradle&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    id 'org.openapi.generator' version '7.17.0' apply false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;build.gradle (:contract)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="nl"&gt;plugin:&lt;/span&gt; &lt;span class="s1"&gt;'org.openapi.generator'&lt;/span&gt;

&lt;span class="n"&gt;openApiGenerate&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;generatorName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'spring'&lt;/span&gt;

    &lt;span class="c1"&gt;// 입력 스펙 경로 (번들링된 파일 사용)&lt;/span&gt;
    &lt;span class="n"&gt;inputSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"openapi.bundle.yaml"&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="na"&gt;asFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;absolutePath&lt;/span&gt;

    &lt;span class="c1"&gt;// 출력 경로&lt;/span&gt;
    &lt;span class="n"&gt;outputDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"generated/openapi"&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="na"&gt;asFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;absolutePath&lt;/span&gt;

    &lt;span class="c1"&gt;// 커스텀 Mustache 템플릿 디렉터리&lt;/span&gt;
    &lt;span class="n"&gt;templateDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"$projectDir/openapi-templates/spring"&lt;/span&gt;

    &lt;span class="c1"&gt;// 패키지 설정&lt;/span&gt;
    &lt;span class="n"&gt;apiPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'simple.board.contract.api'&lt;/span&gt;
    &lt;span class="n"&gt;modelPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'simple.board.contract.model'&lt;/span&gt;

    &lt;span class="c1"&gt;// API/Model 파일만 생성 (supporting 파일 제외)&lt;/span&gt;
    &lt;span class="n"&gt;globalProperties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;apis&lt;/span&gt;           &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;models&lt;/span&gt;         &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;supportingFiles:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;// 코드 생성 옵션&lt;/span&gt;
    &lt;span class="n"&gt;configOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;interfaceOnly&lt;/span&gt;          &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 인터페이스만 생성 (구현체 X)&lt;/span&gt;
        &lt;span class="n"&gt;useTags&lt;/span&gt;                &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 태그 기반 API 분리&lt;/span&gt;
        &lt;span class="n"&gt;addGeneratedAnnotation&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// @Generated 어노테이션 제거&lt;/span&gt;
        &lt;span class="n"&gt;dateLibrary&lt;/span&gt;            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'java8'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// java.time.* 사용&lt;/span&gt;
        &lt;span class="n"&gt;serializableModel&lt;/span&gt;      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 모델 직렬화 가능&lt;/span&gt;
        &lt;span class="n"&gt;documentationProvider&lt;/span&gt;  &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Swagger/SpringDoc 어노테이션 제거&lt;/span&gt;
        &lt;span class="n"&gt;useBeanValidation&lt;/span&gt;      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Bean Validation 사용&lt;/span&gt;
        &lt;span class="n"&gt;useJakartaEe&lt;/span&gt;           &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Jakarta EE 사용 (javax → jakarta)&lt;/span&gt;
        &lt;span class="n"&gt;skipDefaultInterface&lt;/span&gt;   &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// default 메서드 본문 제거&lt;/span&gt;
        &lt;span class="n"&gt;useSpringBoot3&lt;/span&gt;         &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 이 모듈은 라이브러리이므로 bootJar 비활성화&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bootJar'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;enabled&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="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jar'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 생성된 소스를 컴파일에 포함&lt;/span&gt;
&lt;span class="k"&gt;sourceSets&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;java&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;srcDir&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"generated/openapi/src/main/java"&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="na"&gt;asFile&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 컴파일 전에 코드 생성 실행&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'compileJava'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dependsOn&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'openApiGenerate'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// OpenAPI 스펙 번들링 태스크&lt;/span&gt;
&lt;span class="c1"&gt;// $ref로 분리된 YAML 파일들을 build 디렉터리로 복사하여 참조 해소&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bundleOpenApi'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'openapi'&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Prepare OpenAPI spec files for code generation'&lt;/span&gt;

    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$projectDir/openapi.yaml"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$projectDir/paths"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$projectDir/components"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"openapi.bundle.yaml"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"paths"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"components"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;doLast&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"$projectDir/openapi.yaml"&lt;/span&gt;
            &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&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="na"&gt;asFile&lt;/span&gt;
            &lt;span class="n"&gt;rename&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'openapi.bundle.yaml'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$projectDir/paths"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"$projectDir/paths"&lt;/span&gt;
                &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"paths"&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="na"&gt;asFile&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$projectDir/components"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"$projectDir/components"&lt;/span&gt;
                &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"components"&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="na"&gt;asFile&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 코드 생성 태스크 설정&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'openApiGenerate'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dependsOn&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;named&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bundleOpenApi'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 불필요한 생성 파일 삭제&lt;/span&gt;
    &lt;span class="n"&gt;doLast&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"generated/openapi/README.md"&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="na"&gt;asFile&lt;/span&gt;
        &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"generated/openapi/.openapi-generator"&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="na"&gt;asFile&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 생성된 코드의 컴파일 의존성 (런타임은 소비 모듈에서 제공)&lt;/span&gt;
    &lt;span class="n"&gt;compileOnly&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework:spring-web'&lt;/span&gt;
    &lt;span class="n"&gt;compileOnly&lt;/span&gt; &lt;span class="s1"&gt;'jakarta.validation:jakarta.validation-api'&lt;/span&gt;
    &lt;span class="n"&gt;compileOnly&lt;/span&gt; &lt;span class="s1"&gt;'com.fasterxml.jackson.core:jackson-annotations'&lt;/span&gt;
    &lt;span class="n"&gt;compileOnly&lt;/span&gt; &lt;span class="s1"&gt;'org.openapitools:jackson-databind-nullable:0.2.6'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;빌드를 실행하면 아래와 같이 API 인터페이스와 스펙 기반의 모델 객체가 자동으로 생성된 것을 확인할 수 있습니다.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  커스텀 Mustache 템플릿
&lt;/h2&gt;

&lt;p&gt;OpenAPI Generator가 생성한 API 인터페이스를 확인해보면, 저희의 관심사와는 거리가 먼 다양한 보일러플레이트 코드가 함께 생성되어 다소 장황하게 느껴질 수 있습니다.&lt;/p&gt;

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

&lt;p&gt;Mustache 템플릿을 직접 커스터마이징하면, 프로젝트 상황에 맞춰 생성 코드를 한층 더 최적화할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Mustache는 다양한 프로그래밍 언어를 지원하는 Logic-less 템플릿 엔진으로, 복잡한 로직을 최소화하고 간단한 조건문과 반복문만을 지원합니다. 이러한 특성 덕분에 데이터 표현에만 집중할 수 있으며, View와 서버 로직이 명확하게 분리되어 코드가 단순하고 가독성이 높아집니다. OpenAPI Generator는 기본적으로 Mustache 템플릿을 사용해 코드를 생성하기 때문에, 이 템플릿을 직접 수정하여 생성 결과물을 원하는 형태로 제어할 수 있습니다.&lt;/p&gt;

&lt;p&gt;다음과 같이 mustache로 불필요한 보일러플레이트를 제거하거나 프로젝트에 필요한 커스텀 어노테이션과 메서드를 추가할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;package &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;useBeanValidation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
import jakarta.validation.Valid;
&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;useBeanValidation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
import &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;modelPackage&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;.*;

/**
 * &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;classname&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; - API interface
 */
public interface &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;classname&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; {
&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;operations&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;operation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;

    @RequestMapping(method = RequestMethod.&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;httpMethod&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;, value = "&lt;span class="k"&gt;{{{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}}}&lt;/span&gt;")
    ResponseEntity&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;returnType&lt;/span&gt;&lt;span class="k"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;returnType&lt;/span&gt;&lt;span class="k"&gt;}}{{/&lt;/span&gt;&lt;span class="nn"&gt;returnType&lt;/span&gt;&lt;span class="k"&gt;}}{{^&lt;/span&gt;&lt;span class="nv"&gt;returnType&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;Object&lt;/span&gt;&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;returnType&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;operationId&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;(&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;allParams&lt;/span&gt;&lt;span class="k"&gt;}}{{^&lt;/span&gt;&lt;span class="nv"&gt;-first&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
            &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;-first&lt;/span&gt;&lt;span class="k"&gt;}}{{#&lt;/span&gt;&lt;span class="nn"&gt;isPathParam&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;@PathVariable("&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;baseName&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;") &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;isPathParam&lt;/span&gt;&lt;span class="k"&gt;}}{{#&lt;/span&gt;&lt;span class="nn"&gt;isQueryParam&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;@RequestParam(value = "&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;baseName&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;", required = &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;required&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;) &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;isQueryParam&lt;/span&gt;&lt;span class="k"&gt;}}{{#&lt;/span&gt;&lt;span class="nn"&gt;isHeaderParam&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;@RequestHeader(value = "&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;baseName&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;", required = &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;required&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;) &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;isHeaderParam&lt;/span&gt;&lt;span class="k"&gt;}}{{#&lt;/span&gt;&lt;span class="nn"&gt;isBodyParam&lt;/span&gt;&lt;span class="k"&gt;}}{{#&lt;/span&gt;&lt;span class="nn"&gt;useBeanValidation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;@Valid &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;useBeanValidation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;@RequestBody &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;isBodyParam&lt;/span&gt;&lt;span class="k"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;dataType&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;paramName&lt;/span&gt;&lt;span class="k"&gt;}}{{^&lt;/span&gt;&lt;span class="nv"&gt;-last&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;,&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;-last&lt;/span&gt;&lt;span class="k"&gt;}}{{/&lt;/span&gt;&lt;span class="nn"&gt;allParams&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;);
&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;operation&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;operations&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;장황했던 &lt;code&gt;ArticleApi&lt;/code&gt; 인터페이스가 간결하게 개선된 것을 볼 수 있습니다.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;지금까지 API-First 방식으로 OpenAPI Specification을 활용한 효율적인 API 개발 프로세스에 대해 알아보았습니다. Git 서브 모듈을 통해 명세를 공유하고, OpenAPI Generator로 백엔드 인터페이스를 자동 생성하며, Mustache 템플릿을 커스터마이징하여 프로젝트에 최적화하는 방법까지 살펴보았습니다.&lt;/p&gt;

&lt;p&gt;이러한 접근 방식의 진정한 가치는 프론트엔드 개발자와의 협업에서 더욱 빛을 발합니다. OpenAPI 명세를 공유하면 프론트엔드에서도 &lt;code&gt;typescript-axios&lt;/code&gt;나 &lt;code&gt;typescript-fetch&lt;/code&gt; 같은 Generator를 사용해 API 호출 코드와 타입을 자동으로 생성할 수 있습니다.&lt;/p&gt;

&lt;p&gt;더 나아가 Prism 같은 Mock 서버를 활용하면 백엔드 API 구현이 완료되기 전에도 프론트엔드에서 명세 기반의 Mock 테스트를 진행할 수 있어, 진정한 의미의 병렬 개발이 가능해집니다.&lt;/p&gt;

&lt;p&gt;다만, API-First 방식이 도입 초기부터 즉각적인 생산성 향상을 가져다주진 않을 것으로 생각됩니다. OpenAPI Specification 문법에 익숙해지는 과정, YAML 파일 구조화 방법, 그리고 Mustache 템플릿 커스터마이징까지 생각보다 학습 곡선이 가팔랐습니다. &lt;/p&gt;

&lt;p&gt;무엇보다 아직 OpenAPI Specification 관련 라이브러리들이 전반적으로 미성숙하다는 느낌을 받았습니다. 자잘한 버그들이 존재했고, 이를 해결하는 과정에서 생각보다 많은 시간이 소비되었습니다.&lt;/p&gt;

&lt;p&gt;하지만 이러한 초기 투자는 장기적으로 충분한 가치가 있다고 생각합니다. OpenAPI Specification이 단일 진실 공급원이 되면 "문서와 실제 동작이 다르다", "이 필드가 필수인지 선택인지 모르겠다"라는 식의 불필요한 오해와 소통 비용이 사라지고, 팀 전체가 동일한 명세를 기준으로 대화할 수 있게 될 것이라 생각합니다.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
    </item>
    <item>
      <title>Resilience4j Bulkhead 패턴: 대용량 데이터 처리 안정성 높이기</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Fri, 24 Oct 2025 15:28:20 +0000</pubDate>
      <link>https://dev.to/headf1rst/resilience4j-bulkhead-paeteon-daeyongryang-deiteo-ceori-anjeongseong-nopigi-3jdn</link>
      <guid>https://dev.to/headf1rst/resilience4j-bulkhead-paeteon-daeyongryang-deiteo-ceori-anjeongseong-nopigi-3jdn</guid>
      <description>&lt;p&gt;물류 시스템을 개발하다 보면, 대량의 데이터를 엑셀 파일로 내보내야 하는 요구사항을 자주 마주합니다. 사용자가 지정한 기간의 데이터를 엑셀로 제공하려면, 먼저 데이터를 조회한 뒤 Apache POI로 엑셀 형식으로 변환하는 과정이 필요합니다.&lt;/p&gt;

&lt;p&gt;다만 이 과정을 한 번에 처리하면 메모리 사용량이 급증해 ‎&lt;code&gt;OutOfMemoryException&lt;/code&gt;이 발생할 수 있습니다. 따라서 데이터를 N개 단위로 나누어 조회하고 변환하는 작업을 반복하는 것이 안전합니다. 특히 POI의 SXSSF는 슬라이딩 윈도우 방식으로 설정된 행 수만 메모리에 유지하고, 초과분은 임시 파일로 디스크에 플러시 하여 메모리 상한을 넘지 않도록 합니다.&lt;/p&gt;

&lt;p&gt;하지만 요청이 동시에 몰리는 상황에서는 얘기가 달라집니다. 동시 다운로드 건수와 윈도우 크기가 곱해지면서 ‎&lt;code&gt;요청 수 × 윈도우 크기&lt;/code&gt;에 해당하는 데이터가 동시에 메모리에 올라가 OOM 위험이 다시 커집니다.&lt;/p&gt;

&lt;p&gt;이를 방지하는 방법으로는&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;서비스 레벨에서 동시 요청 수를 제한하는 방법,&lt;/li&gt;
&lt;li&gt;요청마다 독립 프로세스를 띄워 비동기로 처리해 격리하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 있습니다.&lt;/p&gt;

&lt;p&gt;이번 포스트에서는 &lt;code&gt;Bulkhead 패턴&lt;/code&gt;을 활용해 동시 요청을 효과적으로 제한하는 방법을 중심으로 정리해 보도록 하겠습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulkhead 패턴이란
&lt;/h2&gt;

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

&lt;p&gt;Bulkhead는 본래 선박 구조에서 온 용어로, 선체 내부를 여러 개의 격벽으로 나누어 한 구획에 물이 차도 다른 구획으로 침수 피해가 번지지 않게 하는 설계를 뜻합니다. 이러한 선박 설계에서 착안한 Bulkhead 패턴은 시스템 자원을 영역별로 분리하여, 한 영역에 과부하나 장애가 발생해도 전체로 확산하지 않도록 합니다.&lt;/p&gt;

&lt;p&gt;구체적으로는 특정 기능이나 엔드포인트를 논리/물리적으로 구분하고, 각 구획에 허용할 동시 처리 수와 대기열 상한을 고정합니다. 이렇게 하면 한 구획의 트래픽이 급증하거나 해당 구획이 리소스를 모두 소진하더라도, 다른 API 처리를 위한 자원은 보존되어 한 작업 때문에 다른 작업이 지연되는 상황을 예방할 수 있습니다.&lt;/p&gt;

&lt;p&gt;엑셀 파일 생성은 메모리 소모가 크고 일반적인 API보다 처리 시간이 길기 때문에, 동일 서버가 다른 중요한 요청까지 함께 처리하는 환경에서는 엑셀 다운로드가 몰릴 때 톰캣 스레드나 메모리 같은 공용 리소스를 잠식하여 다른 API까지 느려지는 등 문제가 발생할 수 있습니다. Bulkhead 패턴은 작업별로 리소스를 분리하고 상한을 설정해 이러한 장애 전파를 차단합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience4j를 사용한 Bulkhead 패턴 적용
&lt;/h2&gt;

&lt;p&gt;이러한 Bulkhead 패턴은 직접 구현할 필요 없이 라이브러리를 사용하여 간단하게 구현할 수 있습니다.&lt;/p&gt;

&lt;p&gt;대표적인 라이브러리에는 &lt;code&gt;Resilience4j&lt;/code&gt;, &lt;code&gt;Netflix Hystrix&lt;/code&gt;, &lt;code&gt;Alibaba Sentinel&lt;/code&gt; 등이 있는데 가장 활발하게 개발되고 있고, Spring 생태계와의 통합도 잘 되어있는 &lt;a href="https://resilience4j.readme.io/" rel="noopener noreferrer"&gt;Resilience4j&lt;/a&gt;를 사용하도록 하겠습니다.&lt;br&gt;
(Resilience4j는 이 외에도 서킷 브레이커나 재시도 등 시스템의 회복 탄력성을 위한 다양한 기능들을 제공합니다)&lt;/p&gt;

&lt;p&gt;Resilience4j를 사용하여 Bulkhead 패턴을 적용하는 방법은 간단합니다.&lt;/p&gt;
&lt;h3&gt;
  
  
  의존성 추가
&lt;/h3&gt;

&lt;p&gt;아래와 같이 의존성을 추가해 줍니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Resilience4j&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'io.github.resilience4j:resilience4j-spring-boot3:2.3.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;참고: &lt;a href="https://mvnrepository.com/artifact/io.github.resilience4j" rel="noopener noreferrer"&gt;https://mvnrepository.com/artifact/io.github.resilience4j&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;그리고 &lt;code&gt;application.yml&lt;/code&gt;에 아래와 같이 추가합니다&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resilience4j&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bulkhead&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;excelStream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 벌크헤드의 이름&lt;/span&gt;
        &lt;span class="na"&gt;maxConcurrentCalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;   &lt;span class="c1"&gt;# 동시에 5건까지만 스트리밍 생성&lt;/span&gt;
        &lt;span class="na"&gt;maxWaitDuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100ms&lt;/span&gt;    &lt;span class="c1"&gt;# 100ms 초과 시 거절&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;maxConcurrentCalls&lt;/code&gt;는 허용할 최대 동시 호출 수(기본값: 25), &lt;code&gt;maxWaitDuration&lt;/code&gt;은 최대 동시 호출 수에 도달했을 때 추가 요청이 들어온 경우 얼마나 대기할 것인지를 나타냅니다. 기본값인 0s로 설정할 경우 대기 없이 즉시 거절하게 됩니다.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;application.yml&lt;/code&gt;에 옵션을 추가하지 않고 Bulkhead 인스턴스를 만들고 주입해서 사용하는 방법도 가능합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BulkheadSemaphoreConfig.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BulkheadSemaphoreConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;BulkheadConfigCustomizer&lt;/span&gt; &lt;span class="nf"&gt;excelStreamSemaphore&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BulkheadConfigCustomizer&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="s"&gt;"excelStream"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxConcurrentCalls&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxWaitDuration&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;ofMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;BulkheadThreadPoolConfig.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BulkheadThreadPoolConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolBulkheadConfigCustomizer&lt;/span&gt; &lt;span class="nf"&gt;excelStreamThreadPool&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolBulkheadConfigCustomizer&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="s"&gt;"excelStream"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;coreThreadPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxThreadPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queueCapacity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&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;h3&gt;
  
  
  구현 예시
&lt;/h3&gt;

&lt;p&gt;Bulkhead를 적용한 간단한 Controller와 검증을 위한 테스트 코드를 만들어보겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ExcelDownloadController.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;ExcelDownloadController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bulkhead&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"excelStream"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEMAPHORE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallbackMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"excelStreamFallback"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/orders/download"&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;download&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nc"&gt;OrderDownloadRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&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="c1"&gt;// 엑셀 다운로드 처리 시뮬레이션 (500ms 소요)&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;sleep&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="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="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="na"&gt;interrupt&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;excelStreamFallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;OrderDownloadRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BulkheadFullException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TooManyRequestsException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too many concurrent requests"&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;@Bulkhead&lt;/code&gt;의 ‎&lt;code&gt;type&lt;/code&gt;에는 ‎&lt;code&gt;Bulkhead.Type.SEMAPHORE&lt;/code&gt;와 ‎&lt;code&gt;Bulkhead.Type.THREADPOOL&lt;/code&gt; 두 가지가 있으며, 기본값은 ‎&lt;code&gt;SEMAPHORE&lt;/code&gt;입니다. 두 방식의 차이는 아래에서 자세히 설명하겠습니다.&lt;/p&gt;

&lt;p&gt;실패 시 호출할 ‎&lt;code&gt;fallbackMethod&lt;/code&gt; 또한 지정할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;주의할 점:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;• ‎&lt;code&gt;fallback&lt;/code&gt; 메서드의 시그니처는 원본 컨트롤러 메서드와 호환되어야 합니다. 즉, 원본 메서드의 파라미터를 동일한 순서로 모두 받아야 하며, 마지막 인자로 예외 타입을 추가할 수 있습니다.&lt;br&gt;
 • 반환 타입도 일치해야 합니다. 원본이 ‎&lt;code&gt;void&lt;/code&gt;면 ‎&lt;code&gt;fallback&lt;/code&gt;도 ‎&lt;code&gt;void&lt;/code&gt;여야 하고, ‎&lt;code&gt;ResponseEntity&amp;lt;T&amp;gt;&lt;/code&gt;를 반환하면 ‎&lt;code&gt;fallback&lt;/code&gt; 역시 같은 타입을 반환해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;ExcelDownloadControllerBulkheadTest.java&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SampleApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfigureMockMvc&lt;/span&gt;
&lt;span class="nd"&gt;@ActiveProfiles&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExcelDownloadControllerBulkheadTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;MockMvc&lt;/span&gt; &lt;span class="n"&gt;mockMvc&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="n"&gt;bulkhead가_최대_동시_호출_수를_제한한다&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;InterruptedException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// given&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxConcurrentCalls&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;CountDownLatch&lt;/span&gt; &lt;span class="n"&gt;startLatch&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="nc"&gt;CountDownLatch&lt;/span&gt; &lt;span class="n"&gt;doneLatch&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="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executorService&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="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt; &lt;span class="n"&gt;successCount&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;AtomicInteger&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="nc"&gt;AtomicInteger&lt;/span&gt; &lt;span class="n"&gt;rejectedCount&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;AtomicInteger&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="c1"&gt;// when&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;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;totalRequests&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;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;executorService&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="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;startLatch&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="c1"&gt;// 모든 스레드가 동시에 시작하도록 대기&lt;/span&gt;

                    &lt;span class="n"&gt;mockMvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;perform&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/download"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;andExpect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isOk&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

                    &lt;span class="n"&gt;successCount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// TooManyRequestsException이 발생하면 거절된 것으로 카운트&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCause&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;TooManyRequestsException&lt;/span&gt; &lt;span class="o"&gt;||&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="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too many concurrent requests"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;rejectedCount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// 다른 예외도 거절로 간주 (Bulkhead 관련)&lt;/span&gt;
                        &lt;span class="n"&gt;rejectedCount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;doneLatch&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="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;startLatch&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="c1"&gt;// 모든 요청 동시 시작&lt;/span&gt;
        &lt;span class="n"&gt;doneLatch&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="c1"&gt;// 모든 요청 완료 대기&lt;/span&gt;
        &lt;span class="n"&gt;executorService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// then&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[DEBUG_LOG] Success count: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;successCount&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="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[DEBUG_LOG] Rejected count: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rejectedCount&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="c1"&gt;// maxConcurrentCalls(5)를 초과하는 요청은 거절되어야 함&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;successCount&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="na"&gt;isLessThanOrEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxConcurrentCalls&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 일부 요청은 거절되어야 함 (10개 요청 중 5개 초과분)&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;rejectedCount&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="na"&gt;isGreaterThan&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="c1"&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;successCount&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="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rejectedCount&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="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalRequests&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;application-test.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resilience4j&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bulkhead&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;excelStream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 벌크헤드의 이름&lt;/span&gt;
        &lt;span class="na"&gt;maxConcurrentCalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;   &lt;span class="c1"&gt;# 동시에 5건까지만 스트리밍 생성&lt;/span&gt;
        &lt;span class="na"&gt;maxWaitDuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100ms&lt;/span&gt;    &lt;span class="c1"&gt;# 100ms 초과시 거절&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;테스트를 실행해 보면 다음과 같이 테스트가 성공하고, 10건의 동시 요청 중 5건만 수행되고 나머지 5건은 거절된 걸 확인할 수 있습니다.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  SEMAPHORE vs THREADPOOL
&lt;/h2&gt;

&lt;p&gt;Resilience4j의 &lt;code&gt;Bulkhead&lt;/code&gt;는 내부적으로 두 가지 실행 모델을 제공합니다. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;SEMAPHORE&lt;/code&gt;와 &lt;code&gt;THREADPOOL&lt;/code&gt; 두 방식 모두 동시 처리량을 제한하고 격리하는 목적은 같지만, 격리 수준과 적용 시점에서 차이가 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  SemaphoreBulkhead
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SemaphoreBulkhead&lt;/code&gt;는 &lt;code&gt;java.util.concurrent.Semaphore&lt;/code&gt;를 내부적으로 사용하여 동시 호출 수를 제어하며, 현재 스레드에서 동기적으로 코드를 실행합니다.&lt;/p&gt;

&lt;p&gt;자바의 세마포어는 &lt;code&gt;permit&lt;/code&gt;이라는 내부 카운터를 기반으로 동시 접근을 제어하는 동기화 메커니즘입니다. Resilience4j의 Bulkhead는 &lt;code&gt;maxConcurrentCalls&lt;/code&gt;에 설정된 최대 동시 호출 수만큼 permit이 초기화됩니다. &lt;/p&gt;

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

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

&lt;p&gt;스레드가 호출을 시도할 때, 설정된 동시 호출 수 한도 내라면 즉시 실행되고 한도를 초과했다면 &lt;code&gt;maxWaitDuration&lt;/code&gt; 동안 대기하게 됩니다. 이 시간 내에 permit을 얻지 못하면 &lt;code&gt;BulkheadFullException&lt;/code&gt;이 발생하게 됩니다.&lt;/p&gt;

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

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

&lt;p&gt;세마포어 방식은 다양한 스레딩 모델과 I/O 모델에서 잘 동작하며, 호출 응답이 빠를고 예측 가능한 동기 작업에 효과적입니다. 별도의 스레드 풀을 관리하지 않으므로 오버헤드가 적고, 단순한 동시성 제어가 필요한 경우에 유용합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  ThreadPoolBulkhead
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ThreadPoolBulkhead&lt;/code&gt;는 내부적으로 &lt;code&gt;ArrayBlockingQueue&lt;/code&gt;와 &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;를 사용하여 작업을 비동기적으로 실행하며, &lt;br&gt;
특정 작업을 전용 스레드 풀로 격리하여 동시 호출을 관리합니다.&lt;/p&gt;

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

&lt;p&gt;스레드 풀에 사용 가능한 스레드가 있다면 작업이 즉시 실행되고, 모든 스레드가 사용 중이면 작업은 큐에 추가되어 스레드가 사용 가능해질 때까지 대기합니다. 만약 큐까지 가득 차면 &lt;code&gt;BulkheadFullException&lt;/code&gt;이 발생하여 요청이 거부됩니다.&lt;/p&gt;

&lt;p&gt;이러한 스레드 풀 방식은 응답 시간이 길거나 예측 불가능한 I/O 바운드 작업에 적합합니다. 비동기 처리가 필요하거나, 느린 외부 서비스 호출로부터 메인 스레드 풀을 보호해야 하는 경우에 적합합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  언제 뭘 사용해야 할까
&lt;/h3&gt;

&lt;p&gt;세마포어는 호출 스레드에서 직접 동기적으로 실행되기 때문에 별도의 스레드 생성이나 컨텍스트 스위칭 등의 오버헤드가 발생하지 않습니다. 만약 수행시간이 적게 걸리는 작업이라면 스레드 풀로 작업을 위임하고 컨텍스트 스위칭하는 데 드는 비용이 오히려 더 클 수 있습니다.&lt;/p&gt;

&lt;p&gt;반면 느린 작업에 세마포어를 사용할 경우 호출&lt;/p&gt;

&lt;p&gt;단순히 동시 호출 수만 제한하는 것이 목적이라면 세마포어 방식이 효율적입니다. 하지만 외부 API 호출처럼 작업 시간이 길고 완전한 리소스 격리가 필요하다면 전용 스레드 풀을 할당하는 스레드 풀 방식이 더 적합합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;지금까지 Bulkhead 패턴과 Resilience4j를 활용하여 시스템 전체의 안정성을 확보하는 방법을 알아보았습니다.&lt;/p&gt;

&lt;p&gt;촉박한 개발 일정 속에서 장애 방어 로직을 추가하는 것이 때로는 오버엔지니어링처럼 느껴질 수 있습니다. 하지만 리소스 소모가 많고 장애 전파 위험이 큰 기능에서 발생하는 한 번의 장애는, 시스템 전체를 마비시키며 훨씬 더 큰 비용을 초래할 수 있습니다.&lt;/p&gt;

&lt;p&gt;특히 Resilience4j는 Bulkhead 뿐만 아니라 서킷 브레이커와 같은 강력한 회복탄력성 패턴들을 제공합니다. 복잡한 구현 없이 어노테이션과 간단한 설정만으로 시스템의 안정성을 크게 높일 수 있으니, 이 글이 도입에 도움이 되었으면 합니다.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>springboot</category>
      <category>java</category>
      <category>backend</category>
    </item>
    <item>
      <title>Solving the Billion-Dollar Mistake: Modern Java Null Safety with JSpecify and NullAway</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Wed, 24 Sep 2025 23:39:15 +0000</pubDate>
      <link>https://dev.to/headf1rst/solving-the-billion-dollar-mistake-modern-java-null-safety-with-jspecify-and-nullaway-2ie7</link>
      <guid>https://dev.to/headf1rst/solving-the-billion-dollar-mistake-modern-java-null-safety-with-jspecify-and-nullaway-2ie7</guid>
      <description>&lt;p&gt;Whether you're a developer just starting with Java or a senior engineer with two decades of experience, the most frequently encountered error is undoubtedly the &lt;strong&gt;NullPointerException&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;In fact, statistics show that the NullPointerException is the second most common software defect, highlighting just how many developers struggle with NPEs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=12427069" rel="noopener noreferrer"&gt;Tony Hoare&lt;/a&gt; famously called it his "billion-dollar mistake."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I call it my billion-dollar mistake. It was the invention of the null reference in 1965. … This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To improve null safety, various attempts have been made in the Java ecosystem. Following &lt;code&gt;Optional&lt;/code&gt; and the &lt;code&gt;@NonNull&lt;/code&gt; annotation from JSR 305, &lt;strong&gt;JSpecify&lt;/strong&gt; has emerged as the modern standard.&lt;/p&gt;

&lt;p&gt;This article will demonstrate how to use JSpecify to enhance your project's stability and why it's a low-overhead choice for new and existing systems alike.&lt;/p&gt;

&lt;h2&gt;
  
  
  NullPointerException: Why Is It Still a Problem?
&lt;/h2&gt;

&lt;p&gt;Consider this code, which calls an API to extract a token and uses the result without any null checks.&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;// TokenExtractor.java&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractToken&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;authorization&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Main.java&lt;/span&gt;
&lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="n"&gt;tokenExtractor&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;DefaultTokenExtractor&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenExtractor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extractToken&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-auth-header"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token length: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- NullPointerException!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will throw a &lt;code&gt;NullPointerException&lt;/code&gt; if the &lt;code&gt;extractToken&lt;/code&gt; method returns &lt;code&gt;null&lt;/code&gt;. When it does, the token variable becomes null, and any attempt to access its members, like &lt;code&gt;token.length()&lt;/code&gt;, triggers the exception.&lt;/p&gt;

&lt;p&gt;The fundamental problem in Java is that nullability is &lt;strong&gt;implicit&lt;/strong&gt;. Unless explicitly stated in API documentation, developers can't be sure whether a return value can be null, leading to misunderstandings and bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSpecify: A Standard for Explicit Null Safety
&lt;/h2&gt;

&lt;p&gt;To solve this problem, teams from Google, JetBrains, Spring, and others collaborated to create the JSpecify standard.&lt;/p&gt;

&lt;p&gt;JSpecify is more than just a set of annotations; it provides a clear specification for null safety, ensuring consistent behavior across tools like IDEs and static analyzers.&lt;/p&gt;

&lt;p&gt;JSpecify defines nullability in three states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unspecified&lt;/strong&gt;: The default state in Java, where a value may or may not be null.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nullable (@Nullable)&lt;/strong&gt;: Explicitly indicates that a value can be null.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-null (@NonNull)&lt;/strong&gt;: Guarantees that a value will never be null.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Adding the JSpecify Dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jspecify:jspecify:1.0.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can annotate the &lt;code&gt;TokenExtractor&lt;/code&gt; interface to make its nullability explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jspecify.annotations.Nullable&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;interface&lt;/span&gt; &lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Nullable&lt;/span&gt; &lt;span class="c1"&gt;// Specifies that the return value can be null&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractToken&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;authorization&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 this change, an IDE like IntelliJ IDEA will warn you of a potential &lt;code&gt;NullPointerException&lt;/code&gt; at the &lt;code&gt;token.length()&lt;/code&gt; call, helping you fix the issue before it becomes a runtime error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Readability: Setting Defaults with &lt;code&gt;@NullMarked&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In most APIs (around 90% of the time), values are expected to be non-null. Adding &lt;code&gt;@NonNull&lt;/code&gt; to every parameter and return type is tedious, clutters the code, and harms readability.&lt;/p&gt;

&lt;p&gt;To address this, JSpecify provides the &lt;code&gt;@NullMarked&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;By applying &lt;code&gt;@NullMarked&lt;/code&gt; at the package level (in a &lt;code&gt;package-info.java&lt;/code&gt; file), all types within that package are considered non-null by default.&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;// src/main/java/com/example/package-info.java&lt;/span&gt;
&lt;span class="nd"&gt;@NullMarked&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jspecify.annotations.NullMarked&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, everything is treated as non-null unless explicitly marked with &lt;code&gt;@Nullable&lt;/code&gt;, resulting in much cleaner and more manageable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build-Time Verification: Strengthening Null Safety with NullAway
&lt;/h2&gt;

&lt;p&gt;IDE warnings are helpful, but they can't prevent developers from committing code that ignores them.&lt;/p&gt;

&lt;p&gt;To enforce null safety, you can use a static analysis tool like &lt;a href="https://github.com/uber/NullAway" rel="noopener noreferrer"&gt;NullAway&lt;/a&gt;. NullAway is an Error Prone plugin that analyzes JSpecify annotations during the build process and will cause the build to fail if it finds any null-safety violations.&lt;/p&gt;

&lt;p&gt;You can configure it in Gradle as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'net.ltgt.errorprone'&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s1"&gt;'4.1.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jspecify:jspecify:1.0.0'&lt;/span&gt;

    &lt;span class="n"&gt;errorprone&lt;/span&gt; &lt;span class="s2"&gt;"com.google.errorprone:error_prone_core:2.37.0"&lt;/span&gt;
    &lt;span class="n"&gt;errorprone&lt;/span&gt; &lt;span class="s2"&gt;"com.uber.nullaway:nullaway:0.12.6"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JavaCompile&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;configureEach&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorprone&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;disableAllChecks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"NullAway:OnlyNullMarked"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"NullAway"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you ignore the IDE warning and run the build, it will fail with a compile-time error, preventing code that violates null safety from being deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSpecify in the Spring Ecosystem
&lt;/h2&gt;

&lt;p&gt;Starting with Spring Framework 7 (included in Spring Boot 4), the entire codebase has been migrated to use JSpecify annotations. This means Spring developers can leverage null-safety information from Spring APIs directly in their IDEs and build tools without any additional setup.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;RestClient.body()&lt;/code&gt; method returns a &lt;code&gt;@Nullable String&lt;/code&gt;, reminding developers to handle the possibility of a &lt;code&gt;null&lt;/code&gt; response appropriately.&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;// Spring's RestClient API&lt;/span&gt;
&lt;span class="nd"&gt;@Nullable&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;restClient&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="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/user"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The IDE warns that 'body' can be null, prompting a null check.&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Future of Null Safety in Java
&lt;/h2&gt;

&lt;p&gt;In the long term, null-safety features are planned for the Java language itself as part of Project Valhalla.&lt;/p&gt;

&lt;p&gt;New syntax like String&lt;code&gt;?&lt;/code&gt; (nullable) and String&lt;code&gt;!&lt;/code&gt; (non-null) has been proposed. However, due to Java's commitment to backward compatibility, the default for unannotated types will likely remain "unspecified". &lt;a href="https://openjdk.org/jeps/8303099" rel="noopener noreferrer"&gt;(https://openjdk.org/jeps/8303099)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this feature is still in the proposal stage and will likely take several years to materialize, JSpecify and NullAway currently represent the most practical and powerful way to improve the stability of Java applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;JSpecify and NullAway are a powerful combination for addressing Java's "billion-dollar mistake." By using explicit annotations, you clarify your code's intent and can eliminate potential NullPointerExceptions at compile time through IDE and build tool integration.&lt;/p&gt;

&lt;p&gt;Based on my experience introducing JSpecify to my team, I've seen firsthand how a simple configuration can significantly improve application stability and code quality. A major advantage is the ability to adopt it gradually on a package-by-package basis, which makes applying it to existing projects far less daunting.&lt;/p&gt;

&lt;p&gt;I encourage you to try JSpecify in your projects and start writing safer, more robust Java code today.&lt;/p&gt;

</description>
      <category>java</category>
      <category>webdev</category>
      <category>springboot</category>
    </item>
    <item>
      <title>10억 달러짜리 실수 해결하기: JSpecify와 NullAway를 사용한 최신 Java Null 안전성</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Wed, 24 Sep 2025 15:39:56 +0000</pubDate>
      <link>https://dev.to/headf1rst/10eog-dalreojjari-silsu-haegyeolhagi-jspecifywa-nullawayreul-sayonghan-coesin-java-null-anjeonseong-4jal</link>
      <guid>https://dev.to/headf1rst/10eog-dalreojjari-silsu-haegyeolhagi-jspecifywa-nullawayreul-sayonghan-coesin-java-null-anjeonseong-4jal</guid>
      <description>&lt;p&gt;자바 프로그래밍을 처음 시작한 개발자부터 20년 경력의 시니어 개발자까지, 경력을 불문하고 개발자들이 가장 자주 마주치는 에러는 &lt;strong&gt;NullPointerException&lt;/strong&gt; 일 것입니다.&lt;/p&gt;

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

&lt;p&gt;실제로 한 통계 자료에 따르면 NullPointerException이 소프트웨어 결함 통계 2위에 해당할 정도로 많은 개발자가 NPE로 고통받고 있다고 하는데요.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;'Null 참조를 만든건 나의 10억 달러짜리 실수다'&lt;/em&gt; 라는 &lt;a href="https://news.ycombinator.com/item?id=12427069" rel="noopener noreferrer"&gt;토니 호어(Tony Hoare)&lt;/a&gt;의 말도 너무 유명하죠.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I call it my billion-dollar mistake. It was the invention of the null reference in 1965. … This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이러한 null 안전성을 높이기 위해서 Java에서는 다양한 시도들이 있었고, &lt;code&gt;Optional&lt;/code&gt;, JSR 305의 &lt;code&gt;@NonNull&lt;/code&gt; 어노테이션을 거쳐, 마침내 &lt;strong&gt;JSpecify&lt;/strong&gt;가 표준 어노테이션으로 제정되었습니다.&lt;/p&gt;

&lt;p&gt;지금부터 JSpecify를 사용하여 어떻게 프로젝트에 안정성을 높일 수 있는지, 신규 프로젝트 뿐만 아니라 기존 시스템에 도입하기에도 부담이 없을지에 대해 소개해 드리고자 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  NullPointerException: 왜 여전히 문제인가?
&lt;/h2&gt;

&lt;p&gt;토큰을 추출하는 API를 호출하고, 그 결과를 아무런 확인 없이 사용하는 코드가 있습니다.&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;// TokenExtractor.java&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractToken&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;authorization&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Main.java&lt;/span&gt;
&lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="n"&gt;tokenExtractor&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;DefaultTokenExtractor&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenExtractor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extractToken&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-auth-header"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token length: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- NullPointerException 발생!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 코드는 &lt;code&gt;extractToken&lt;/code&gt; 메서드가 &lt;code&gt;null&lt;/code&gt;을 반환할 가능성이 있을 때 &lt;code&gt;NullPointerException&lt;/code&gt;을 발생시킵니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;extractToken&lt;/code&gt; 메서드가 &lt;code&gt;null&lt;/code&gt;을 반환하게되면 &lt;code&gt;token&lt;/code&gt;에 &lt;code&gt;null&lt;/code&gt;값이 들어가고 &lt;code&gt;token.length()&lt;/code&gt;로 &lt;code&gt;token&lt;/code&gt; 값에 접근할 때 &lt;code&gt;NullPointerException&lt;/code&gt;이 발생하게 됩니다.&lt;/p&gt;

&lt;p&gt;이처럼 Java의 근본적인 문제는 Null 가능성(Nullability)이 암시적(implicit)이라는 점입니다.&lt;/p&gt;

&lt;p&gt;API 문서에 명시되어 있지 않으면 개발자는 반환 값이 null일 수 있는지 아닌지 알기 어렵고, 이는 오해와 버그로 이어지게 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSpecify: 명시적인 Null 안전성을 향한 표준
&lt;/h2&gt;

&lt;p&gt;이 문제를 해결하기 위해 Google, JetBrains, Spring 등 여러 팀이 협력하여 JSpecify 표준을 만들었습니다.&lt;/p&gt;

&lt;p&gt;JSpecify는 단순한 어노테이션 집합이 아니라, Null 안전성에 대한 명확한 명세를 제공하여 다양한 도구(IDE, 정적 분석기)가 일관되게 동작하도록 하는 것을 목표로 합니다.&lt;/p&gt;

&lt;p&gt;JSpecify는 Null 가능성을 세 가지 상태로 정의합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;미지정(Unspecified)&lt;/strong&gt;: Java의 기본 상태로, &lt;code&gt;null&lt;/code&gt;일 수도 아닐 수도 있습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nullable (@Nullable)&lt;/strong&gt;: 명시적으로 &lt;code&gt;null&lt;/code&gt; 값을 가질 수 있음을 나타냅니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-null (@NonNull)&lt;/strong&gt;: 절대 &lt;code&gt;null&lt;/code&gt; 값을 가지지 않음을 보장합니다.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  JSpecify 의존성 추가
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jspecify:jspecify:1.0.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 &lt;code&gt;TokenExtractor&lt;/code&gt; 인터페이스에 어노테이션을 추가하여 Null 가능성을 명시할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jspecify.annotations.Nullable&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;interface&lt;/span&gt; &lt;span class="nc"&gt;TokenExtractor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Nullable&lt;/span&gt; &lt;span class="c1"&gt;// 반환 값이 null일 수 있음을 명시&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractToken&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;authorization&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;이렇게 하면 IntelliJ IDEA와 같은 IDE는 &lt;code&gt;token.length()&lt;/code&gt;를 호출하는 부분에서 잠재적인 &lt;code&gt;NullPointerException&lt;/code&gt;을 경고해 주어, 개발자가 런타임 오류가 발생하기 전에 문제를 해결할 수 있게 도와줍니다.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  가독성 향상: &lt;code&gt;@NullMarked&lt;/code&gt;로 기본값 설정하기
&lt;/h2&gt;

&lt;p&gt;대부분의 경우(약 90%) API는 &lt;code&gt;null&lt;/code&gt;이 아닌 값을 다룹니다. 모든 매개변수와 반환 값에 &lt;code&gt;@NonNull&lt;/code&gt;을 붙이는 것은 귀찮은 일이며 코드를 지저분하게 만들고 자칫 가독성을 해칠 수 있습니다. &lt;/p&gt;

&lt;p&gt;이를 해결하기 위해 JSpecify는 &lt;code&gt;@NullMarked&lt;/code&gt; 어노테이션을 제공합니다. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;@NullMarked&lt;/code&gt; 어노테이션을 패키지 수준(&lt;code&gt;package-info.java&lt;/code&gt; 파일)에 적용하면 해당 패키지 내의 모든 타입은 기본적으로 Non-null로 간주됩니다.&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;// src/main/java/com/example/package-info.java&lt;/span&gt;
&lt;span class="nd"&gt;@NullMarked&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jspecify.annotations.NullMarked&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;@Nullable&lt;/code&gt;을 붙인 경우를 제외하고는 모두 Non-null로 처리되므로 코드가 훨씬 깔끔하게 관리할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  빌드 시간 검증: NullAway로 Null 안전성 강화하기
&lt;/h2&gt;

&lt;p&gt;IDE의 경고는 유용하지만, 이를 무시하고 코드를 커밋하는 것을 막을 수는 없는데요.&lt;/p&gt;

&lt;p&gt;Null 안전성을 강제하기 위해 &lt;a href="https://github.com/uber/NullAway" rel="noopener noreferrer"&gt;NullAway&lt;/a&gt;와 같은 정적 분석 도구를 사용할 수 있습니다. &lt;code&gt;NullAway&lt;/code&gt;는 Error Prone의 플러그인으로, 빌드 과정에서 JSpecify 어노테이션을 분석하여 Null 안전성 위반 시 빌드를 실패시킵니다.&lt;/p&gt;

&lt;p&gt;Gradle에 다음과 같이 설정할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'net.ltgt.errorprone'&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s1"&gt;'4.1.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jspecify:jspecify:1.0.0'&lt;/span&gt;

    &lt;span class="n"&gt;errorprone&lt;/span&gt; &lt;span class="s2"&gt;"com.google.errorprone:error_prone_core:2.37.0"&lt;/span&gt;
    &lt;span class="n"&gt;errorprone&lt;/span&gt; &lt;span class="s2"&gt;"com.uber.nullaway:nullaway:0.12.6"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JavaCompile&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;configureEach&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorprone&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;disableAllChecks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"NullAway:OnlyNullMarked"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"NullAway"&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;IDE의 경고를 무시하고 빌드를 하면 다음과 같이 컴파일 시점에 에러가 발생하여 Null 안전성을 위반하는 코드가 배포되는 것을 막을 수 있습니다.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Spring 생태계에서의 JSpecify
&lt;/h2&gt;

&lt;p&gt;Spring Framework 7(Spring Boot 4에 포함)부터는 코드베이스 전체가 JSpecify 어노테이션으로 마이그레이션되었습니다. 이는 Spring 개발자들이 별도의 설정 없이도 Spring API의 Null 안전성 정보를 IDE와 빌드 도구에서 바로 활용할 수 있음을 의미합니다.&lt;/p&gt;

&lt;p&gt;예를 들어, &lt;code&gt;RestClient&lt;/code&gt;의 &lt;code&gt;.body()&lt;/code&gt; 메서드는 &lt;code&gt;@Nullable String&lt;/code&gt;을 반환하므로, 개발자는 반환 값이 &lt;code&gt;null&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;// Spring의 RestClient API&lt;/span&gt;
&lt;span class="nd"&gt;@Nullable&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;restClient&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="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/user"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// IDE는 body가 null일 수 있음을 경고&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  앞으로의 Java의 Null 안전성
&lt;/h2&gt;

&lt;p&gt;장기적으로 Java 언어 자체에 Null 안전성 기능이 도입될 예정이라고 하는데요(Project Valhalla의 일부).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;?&lt;/code&gt; (nullable)와 &lt;code&gt;!&lt;/code&gt; (non-null) 같은 새로운 구문이 제안되었지만, Java의 하위 호환성 원칙 때문에 기본값은 여전히 "미지정(unspecified)"으로 남을 것이라고 합니다.&lt;a href="https://openjdk.org/jeps/8303099" rel="noopener noreferrer"&gt;(https://openjdk.org/jeps/8303099)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;하지만 어디까지나 제안 단계이고 이 기능이 현실화되기까지는 수년이 걸릴 것이므로, 현재로서는 JSpecify와 NullAway가 Java 애플리케이션의 안정성을 높이는 가장 현실적이고 강력한 방법입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;JSpecify와 NullAway는 Java의 "10억 달러짜리 실수"를 해결하기 위한 강력한 조합입니다. 명시적인 어노테이션을 통해 코드의 의도를 명확히 하고, IDE와 빌드 도구를 연동하여 컴파일 시간에 잠재적인 NullPointerException을 제거할 수 있습니다.&lt;/p&gt;

&lt;p&gt;실제로 팀에 JSpecify를 공유하고 프로젝트에 적용하면서 느낀 경험에 비추어 볼때, 간단한 설정으로 어플리케이션 안정성과 코드 품질이 향상 되는것을 경험하였으며, 패키지 단위의 점진적 도입이 가능하기 때문에 기존 프로젝트에 적용하는 데 부담이 적다는 것 역시 큰 장점으로 느껴졌습니다.&lt;/p&gt;

&lt;p&gt;이 글을 읽어주시는 분들께서도 JSpecify를 프로젝트에 적용하여 더 안전하고 견고한 코드를 작성해 보시기 바랍니다.&lt;/p&gt;

</description>
      <category>java</category>
      <category>webdev</category>
      <category>springboot</category>
    </item>
    <item>
      <title>PostgreSQL MVCC Internals: From xmin/xmax to Isolation Levels</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Sun, 06 Jul 2025 16:10:18 +0000</pubDate>
      <link>https://dev.to/headf1rst/postgresql-mvcc-internals-from-xminxmax-to-isolation-levels-2g6h</link>
      <guid>https://dev.to/headf1rst/postgresql-mvcc-internals-from-xminxmax-to-isolation-levels-2g6h</guid>
      <description>&lt;p&gt;When multiple users access a database concurrently, how can we guarantee Isolation, one of the core &lt;code&gt;ACID&lt;/code&gt; properties? While we could apply locks when reading or writing data, this approach is inefficient because other users must wait for the lock to be released.&lt;/p&gt;

&lt;p&gt;To address this challenge, modern RDBMSs like PostgreSQL and MySQL (InnoDB) employ a technique called &lt;strong&gt;MVCC (Multi-Version Concurrency Control)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The core idea of MVCC is to create a new version of a row each time it's modified, instead of overwriting the existing data. This method controls concurrency by storing version information for each row, tracking which transaction created or deleted it.&lt;/p&gt;

&lt;p&gt;While various database vendors share this core concept of MVCC, their implementations differ. Let's explore these differences by examining the MVCC methods of two of the most popular databases: PostgreSQL and MySQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  How PostgreSQL Implements MVCC
&lt;/h2&gt;

&lt;p&gt;In PostgreSQL, each row in a table (internally called a 'tuple') contains several system columns that are not directly visible to the user. The key columns for transaction control are xmin and xmax.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;xmin&lt;/code&gt;: The transaction ID that created this row version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xmax&lt;/code&gt;: The transaction ID that deleted this row version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using these two columns, PostgreSQL operates as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;INSERT&lt;/strong&gt;: A new row is created, and the current transaction's ID is recorded in the xmin column. The xmax column remains null.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DELETE&lt;/strong&gt;: Instead of being physically removed immediately, the row's xmax column is updated with the current transaction's ID. This row is now considered a 'dead tuple'.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: This operates as a combination of a DELETE and an INSERT. First, the xmax of the current row is marked with the transaction ID. Then, a new row with the updated values is inserted, and its xmin is set to the current transaction ID.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a specific row to be visible to a transaction, it must satisfy both of the following visibility rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creation Rule (xmin)&lt;/strong&gt;: The transaction that created the row version (xmin) must have been committed before the current transaction's snapshot was taken.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deletion Rule (xmax)&lt;/strong&gt;: If the row version was deleted, the deleting transaction (xmax) must not have been committed before the current transaction's snapshot was taken.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How MySQL (InnoDB) Implements MVCC
&lt;/h2&gt;

&lt;p&gt;InnoDB, MySQL's default storage engine, also employs MVCC, but it manages old versions in a distinct way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core Metadata&lt;/strong&gt;: Each row contains important hidden columns, including &lt;code&gt;DB_TRX_ID&lt;/code&gt; (the transaction ID that created the version, similar to xmin) and &lt;code&gt;DB_ROLL_PTR&lt;/code&gt; (&lt;strong&gt;the rollback pointer&lt;/strong&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Previous Version Storage&lt;/strong&gt;: Unlike PostgreSQL, which keeps old versions within the table itself, InnoDB stores the before-images of data in a separate area called the &lt;strong&gt;Undo Log&lt;/strong&gt;. The &lt;code&gt;DB_ROLL_PTR&lt;/code&gt; is an address that points to the previous version of the data in this Undo Log.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Operation&lt;/strong&gt;: When a transaction encounters a data version that is newer than its snapshot, it follows the &lt;code&gt;DB_ROLL_PTR&lt;/code&gt; to traverse the Undo Log, &lt;strong&gt;reconstructing&lt;/strong&gt; a version of the data that is visible to it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Visibility in PostgreSQL by Isolation Level
&lt;/h2&gt;

&lt;p&gt;Ultimately, visibility in PostgreSQL is determined by &lt;code&gt;xmin&lt;/code&gt;, &lt;code&gt;xmax&lt;/code&gt;, and a &lt;strong&gt;snapshot&lt;/strong&gt;. The differences between isolation levels arise from when this snapshot is created.&lt;/p&gt;

&lt;p&gt;Let's explain the visibility rules from the perspective of a transaction, &lt;code&gt;Tx A&lt;/code&gt;, using a &lt;code&gt;users&lt;/code&gt; table that contains two rows.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  READ COMMITTED (Default)
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;READ COMMITTED&lt;/code&gt; level, a &lt;strong&gt;new snapshot is created for every SQL statement&lt;/strong&gt;. This means changes committed by other transactions become visible immediately. (&lt;code&gt;Non-Repeatable Read&lt;/code&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;Tx A&lt;/code&gt; starts (TXID: 100) and executes its first &lt;code&gt;SELECT&lt;/code&gt;.
&lt;/h4&gt;

&lt;p&gt;A new snapshot, Snapshot_1, is created for this statement, capturing the current state of the database.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Snapshot_1 Contents: {'Committed TXs': {90, 91}, 'In-Progress TXs': {100}}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Visibility Check (based on &lt;code&gt;Snapshot_1&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;'Alice' row (&lt;code&gt;xmin=90&lt;/code&gt;): xmin is in the committed list. -&amp;gt; &lt;strong&gt;Visible&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'Bob' row (&lt;code&gt;xmin=91&lt;/code&gt;): xmin is in the committed list. -&amp;gt; &lt;strong&gt;Visible&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result for Tx A: It sees two records: 'Alice' and 'Bob'.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Tx B modifies data and commits.
&lt;/h4&gt;

&lt;p&gt;The original 'Alice' row is updated with &lt;code&gt;xmax=101&lt;/code&gt;, and a new 'Alicia' row is created with &lt;code&gt;xmin=101&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The database's global list of committed transactions is updated to &lt;code&gt;{90, 91, 101}&lt;/code&gt;.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  3. Tx A executes its second SELECT within the same transaction.
&lt;/h4&gt;

&lt;p&gt;According to the &lt;code&gt;READ COMMITTED&lt;/code&gt; rule, a &lt;strong&gt;completely new&lt;/strong&gt; Snapshot_2 is created for this &lt;code&gt;SELECT&lt;/code&gt; statement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Snapshot_2 Contents: {'Committed TXs': {90, 91, 101}, 'In-Progress TXs': {100}}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Visibility Check (based on &lt;code&gt;Snapshot_2&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Original 'Alice' row (&lt;code&gt;xmax=101&lt;/code&gt;): &lt;code&gt;xmax&lt;/code&gt; is in the committed list. -&amp;gt; &lt;strong&gt;Not visible&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New 'Alicia' row (&lt;code&gt;xmin=101&lt;/code&gt;): &lt;code&gt;xmin&lt;/code&gt; is in the committed list. -&amp;gt; &lt;strong&gt;Visible&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'Bob' row (&lt;code&gt;xmin=91&lt;/code&gt;): &lt;code&gt;xmin&lt;/code&gt; is in the committed list. -&amp;gt; &lt;strong&gt;Visible&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final Result for &lt;code&gt;Tx A&lt;/code&gt;: It now sees two records: 'Alicia' and 'Bob'.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;Tx A&lt;/code&gt; executed the same query twice within the same transaction but received different results. This phenomenon, known as a &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;, is the expected behavior for the &lt;code&gt;READ COMMITTED&lt;/code&gt; isolation level.&lt;/p&gt;

&lt;h3&gt;
  
  
  REPEATABLE READ / SERIALIZABLE
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;REPEATABLE READ&lt;/code&gt;, a &lt;strong&gt;snapshot is created only once&lt;/strong&gt;, when the first SQL statement in the transaction is executed. This same snapshot is then reused until the transaction ends.&lt;/p&gt;

&lt;p&gt;Because of this, even if &lt;code&gt;Tx B&lt;/code&gt; changes data and commits, &lt;code&gt;Tx A&lt;/code&gt; continues to judge visibility based on its old, fixed snapshot and therefore does not see the changes. This provides a consistent view of the data throughout the transaction, preventing Non-Repeatable Reads. The &lt;code&gt;SERIALIZABLE&lt;/code&gt; level uses the same snapshot policy but adds more sophisticated dependency tracking to prevent all concurrency anomalies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Different Methods? (Design Philosophy Trade-offs)
&lt;/h2&gt;

&lt;p&gt;The different MVCC implementations in PostgreSQL and MySQL reflect trade-offs in their design philosophies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;: This approach is optimized for &lt;strong&gt;read performance&lt;/strong&gt;. Because previous versions are stored in the table itself, there is no overhead from reconstructing rows, which makes reads very fast. However, since 'dead tuples' accumulate within the table, a periodic cleanup process called &lt;code&gt;VACUUM&lt;/code&gt; is essential. Without it, the table can suffer from 'bloat', becoming unnecessarily large.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MySQL (InnoDB)&lt;/strong&gt;: This approach prioritizes &lt;strong&gt;table space efficiency&lt;/strong&gt;. Change history is managed in a separate Undo Log, so the table itself remains clean and contains only the latest data. However, reading past data may incur the additional cost of traversing the Undo Log to reconstruct a row version, and the Undo Log space itself requires management.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches have their pros and cons. Neither is absolutely superior; rather, they represent different choices made to achieve the distinct goals of each system.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgressql</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Prompt Engineering Tips from Anthropic Engineers</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Tue, 24 Jun 2025 13:20:18 +0000</pubDate>
      <link>https://dev.to/headf1rst/prompt-engineering-tips-from-anthropic-engineers-4j4n</link>
      <guid>https://dev.to/headf1rst/prompt-engineering-tips-from-anthropic-engineers-4j4n</guid>
      <description>&lt;p&gt;Here are a few impressive points from &lt;a href="https://www.youtube.com/watch?v=T9aRN5JkmL8&amp;amp;t=2463s" rel="noopener noreferrer"&gt;youtube (AI prompt engineering: A deep dive)&lt;/a&gt; where Anthropic engineers shared their prompt writing tips and experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impressive Prompt Writing Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When the model makes a mistake, it can be helpful to ask why it was wrong and directly ask the model how to instruct it next time to avoid the mistake.&lt;/li&gt;
&lt;li&gt;When the model faces unexpected input or ambiguous situations, you should provide clear instructions on what to do (e.g., output an "UNCERTAIN" tag) to prevent it from giving a wrong answer.&lt;/li&gt;
&lt;li&gt;Inducing the model to explain its reasoning process before providing an answer (Chain of Thought) improves the model's accuracy.&lt;/li&gt;
&lt;li&gt;You can ask the model to "interview" you. When it's difficult to clearly grasp what you want, you can get help constructing the prompt by asking the model to ask you questions and elicit the necessary information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips for Improving Prompt Engineering Skills
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read many other people's prompts and model outputs:&lt;/strong&gt; You can learn a lot by looking at great prompts, analyzing their structure and intent, and by carefully observing the model's outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attempt many conversations with the model:&lt;/strong&gt; You need to practice improving prompts by directly and repeatedly communicating with the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show your prompts to others:&lt;/strong&gt; Showing a prompt you've written to someone without prior knowledge of the task can help you discover parts that are not clear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the model's limits:&lt;/strong&gt; Trying the most difficult tasks you think the model cannot do and exploring its limits leads to the greatest learning.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  인상 깊었던 프롬프트 작성 팁
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;모델이 실수를 했을 때, 왜 틀렸는지 묻고, 다음에는 어떻게 지시해야 틀리지 않을지 모델에게 직접 물어보는것이 도움이 될 수 있다.&lt;/li&gt;
&lt;li&gt;모델이 예상치 못한 입력이나 모호한 상황에 직면했을 때 어떻게 해야 할지 (예: "불확실" 태그 출력) 명확한 지시를 제공하여 잘못된 답변을 내놓는 것을 방지해야 한다.&lt;/li&gt;
&lt;li&gt;모델이 답변을 제공하기 전에 자신의 추론 과정을 설명하도록 유도하면(Chain of Thought) 모델의 정확도가 향상된다.&lt;/li&gt;
&lt;li&gt;모델에게 자신을 "인터뷰"하도록 요청할 수 있다. 자신이 원하는 바를 명확히 파악하기 어려울 때, 모델한테 나에게 필요한 정보를 질문하고 이끌어내도록 요청하여 프롬프트를 구성하는 데 도움을 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  프롬프트 엔지니어링 기술 향상 팁
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;다른 사람의 프롬프트와 모델 출력 많이 읽기&lt;/strong&gt;: 훌륭한 프롬프트를 보고 그 구조와 의도를 분석하며, 모델의 출력을 면밀히 관찰하면 많은 것을 배울 수 있다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;모델과 많은 대화 시도&lt;/strong&gt;: 직접 모델과 반복적으로 소통하면서 프롬프트를 개선하는 연습을 해야 한다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;타인에게 프롬프트 보여주기&lt;/strong&gt;: 자신이 작성한 프롬프트를 그 작업에 대한 사전 지식이 없는 다른 사람에게 보여주면, 명확하지 않은 부분을 발견하는 데 도움이 된다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;모델의 한계 시험&lt;/strong&gt;: 모델이 할 수 없을 것이라고 생각하는 가장 어려운 작업을 시도하며 모델의 한계를 탐색하는 것이 가장 큰 학습으로 이어진다.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>promptengineering</category>
      <category>ai</category>
    </item>
    <item>
      <title>From Theory to Practice: A JMH Showdown Between Sequential and Parallel Streams</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Sun, 15 Jun 2025 14:33:54 +0000</pubDate>
      <link>https://dev.to/headf1rst/from-theory-to-practice-a-jmh-showdown-between-sequential-and-parallel-streams-1m5e</link>
      <guid>https://dev.to/headf1rst/from-theory-to-practice-a-jmh-showdown-between-sequential-and-parallel-streams-1m5e</guid>
      <description>&lt;p&gt;In our &lt;a href="https://dev.to/headf1rst/discover-how-forkjoinpool-powers-javas-high-performance-parallel-processing-3pn7"&gt;previous post&lt;/a&gt;, we delved into the mechanics of ForkJoinPool, the powerful engine behind Java's parallel processing capabilities. Theory is essential, but seeing the real-world impact is what truly matters. Now, it's time to put that theory to the test.&lt;/p&gt;

&lt;p&gt;This post presents a practical performance comparison between traditional sequential processing and parallel processing using &lt;code&gt;parallelStream&lt;/code&gt;. We'll use the &lt;strong&gt;Java Microbenchmark Harness (JMH)&lt;/strong&gt; to precisely measure the performance gains achieved when validating a large set of data (transportation plans in our example).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmark: Setting the Stage
&lt;/h2&gt;

&lt;p&gt;This section covers all the details required to understand how the test was designed, configured, and implemented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Environment and JMH Configuration
&lt;/h3&gt;

&lt;p&gt;To ensure a fair and reliable comparison, we established a controlled test environment using JMH. The objective is to measure the average execution time of the &lt;code&gt;TransportPlanExcelUploadValidator&lt;/code&gt;'s validation logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Data&lt;/strong&gt;: A list containing 1,000 to 10,000 transport plan entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark Tool&lt;/strong&gt;: JMH (Java Microbenchmark Harness).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warmup Iterations&lt;/strong&gt;: 5 rounds to allow the JVM to perform initial optimizations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurement Iterations&lt;/strong&gt;: 10 rounds to capture a stable average execution time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forks&lt;/strong&gt;: 1 fork to run the test in a separate process, ensuring isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s important to briefly discuss why the &lt;strong&gt;warmup iterations&lt;/strong&gt; are critical for accurate results on the JVM. When Java code is first run, the JVM may interpret it or use a baseline compiler. Only after code has been executed multiple times (making it "hot"), does the Just-In-Time (JIT) compiler step in to perform significant optimizations, translating bytecode into highly efficient native code. The warmup phase ensures that this JIT compilation and other optimizations have completed, so that our measurements reflect the true performance of the optimized code, not the initial, slower startup phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Benchmark Code
&lt;/h3&gt;

&lt;p&gt;The core of our benchmark lies in the &lt;code&gt;TransportPlanExcelUploadValidatorBenchmark&lt;/code&gt; class. We've defined two separate methods, each annotated with &lt;code&gt;@Benchmark&lt;/code&gt;, to measure the performance of the sequential and parallel approaches.&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;@BenchmarkMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AverageTime&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@OutputTimeUnit&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="nd"&gt;@State&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Benchmark&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;TransportPlanExcelUploadValidatorBenchmark&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;ParallelValidator&lt;/span&gt; &lt;span class="n"&gt;parallelValidator&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;SequentialValidator&lt;/span&gt; &lt;span class="n"&gt;sequentialValidator&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;LocalDate&lt;/span&gt; &lt;span class="n"&gt;transportDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;uploadPlans&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;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TransportPlan&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;registeredPlans&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="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Setup&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;setup&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Prepare test data&lt;/span&gt;
        &lt;span class="nc"&gt;ManagerService&lt;/span&gt; &lt;span class="n"&gt;managerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManagerService&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;parallelValidator&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;ParallelValidator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managerService&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sequentialValidator&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;SequentialValidator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managerService&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Setup Fixtures...&lt;/span&gt;

        &lt;span class="c1"&gt;// Mock manager service methods&lt;/span&gt;
        &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAllByFullCarNumberAndUsableIsTrue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anySet&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;willReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;emptyList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Generate test data&lt;/span&gt;
        &lt;span class="n"&gt;uploadPlans&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="mi"&gt;1_000&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="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;DawnMiddleMileTransportPlanInfo&lt;/span&gt; &lt;span class="n"&gt;info&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;DawnMiddleMileTransportPlanInfo&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTransportDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportDate&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDeparture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test Departure"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDestination&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test Destination"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setExpectedEntryTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%02d:00"&lt;/span&gt;&lt;span class="o"&gt;,&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;%&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCarNumber&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"12가"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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;));&lt;/span&gt;
                    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDeliveryRound&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;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRowNumber&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;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;info&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="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Benchmark&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;sequentialValidation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Blackhole&lt;/span&gt; &lt;span class="n"&gt;blackhole&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sequentialValidator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validateExcelUpload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadPlans&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registeredPlans&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Benchmark&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;parallelValidation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Blackhole&lt;/span&gt; &lt;span class="n"&gt;blackhole&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parallelValidator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validateExcelUpload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadPlans&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registeredPlans&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="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;
  
  
  Implementation: Sequential vs. Parallel Validators
&lt;/h3&gt;

&lt;p&gt;Here is a brief look at the two different validator implementations being tested. The core difference lies in how they iterate over the data and collect results.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SequentialValidator&lt;/code&gt; uses a standard &lt;code&gt;for&lt;/code&gt; loop. It iterates through the list of plans one by one, validates each item in a single thread, and adds the results directly to standard &lt;code&gt;ArrayLists&lt;/code&gt;. This represents a traditional, single-threaded approach.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ParallelValidator&lt;/code&gt;, in contrast, leverages &lt;code&gt;parallelStream().forEach()&lt;/code&gt; to process the plans concurrently. To safely collect results from multiple threads without causing race conditions, it uses thread-safe &lt;code&gt;ConcurrentLinkedQueue&lt;/code&gt; collections. Once the parallel processing is complete, these queues are drained into &lt;code&gt;ArrayLists&lt;/code&gt; to form the final result.&lt;/p&gt;

&lt;h4&gt;
  
  
  ParallelValidator.java
&lt;/h4&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="nd"&gt;@RequiredArgsConstructor&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;ParallelValidator&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;ManagerService&lt;/span&gt; &lt;span class="n"&gt;managerService&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;TransportPlanUploadValidationResult&lt;/span&gt; &lt;span class="nf"&gt;validateExcelUpload&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="nc"&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validInfosQueue&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;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidationFailure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failuresQueue&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;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;sortedUploadPlans&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parallelStream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPlan&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failedReasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validateExcelUploadRow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validationContext&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failedReasons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;validInfosQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;failuresQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidationFailure&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="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRowNumber&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;failedReasons&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validInfos&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;validInfosQueue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidationFailure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failures&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;failuresQueue&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;TransportPlanUploadValidationResult&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="n"&gt;validInfos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SequentialValidator.java
&lt;/h4&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="nd"&gt;@RequiredArgsConstructor&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;SequentialValidator&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;ManagerService&lt;/span&gt; &lt;span class="n"&gt;managerService&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;TransportPlanUploadValidationResult&lt;/span&gt; &lt;span class="nf"&gt;validateExcelUpload&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validInfos&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidationFailure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failures&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadInfo&lt;/span&gt; &lt;span class="n"&gt;uploadPlan&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sortedUploadPlans&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failedReasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validateExcelUploadRow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validationContext&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;failedReasons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidationFailure&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="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRowNumber&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;failedReasons&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;validInfos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPlan&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TransportPlanUploadValidationResult&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="n"&gt;validInfos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the Benchmark
&lt;/h2&gt;

&lt;p&gt;The benchmark was executed using the following main class, which configures and runs the JMH Runner.&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;RunBenchmark&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="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;RunnerException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;Options&lt;/span&gt; &lt;span class="n"&gt;accurateOptions&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;OptionsBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransportPlanExcelUploadValidatorBenchmark&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="na"&gt;getSimpleName&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forks&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;// Use 1 JVM forks&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warmupIterations&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="c1"&gt;// 5 warmup iterations&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warmupTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeValue&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="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// 1 seconds per warmup iteration&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurementIterations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurementTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeValue&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="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// 2 seconds per measurement iteration&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resultFormat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultFormatType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jmh-results.json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;availableProcessors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;availableProcessors&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Benchmark started: Sequential vs Parallel processing performance comparison"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data size: 1,000 transport plans"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Available processors (cores): "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;availableProcessors&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"---------------------------------------------"&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;Runner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accurateOptions&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="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;
  
  
  The Results: Quantifying the Performance Gain
&lt;/h2&gt;

&lt;p&gt;Now, let's examine the findings from our benchmark tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 1: A 1,000-Item Dataset
&lt;/h3&gt;

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

&lt;p&gt;First, let's look at the results for a dataset of 1,000 items. The &lt;code&gt;parallelValidation&lt;/code&gt; benchmark scored &lt;strong&gt;6.392 ms/op&lt;/strong&gt;, while &lt;code&gt;sequentialValidation&lt;/code&gt; came in at &lt;strong&gt;20.359 ms/op&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;This translates to a &lt;strong&gt;68.60%&lt;/strong&gt; performance improvement, a significant gain even for a moderately sized dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 2: A 10,000-Item Dataset
&lt;/h3&gt;

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

&lt;p&gt;When we increased the load to 10,000 items, the advantage of parallel processing became even more stark. The parallel version clocked in at &lt;strong&gt;321.574 ms/op&lt;/strong&gt; compared to the sequential version's &lt;strong&gt;1917.098 ms/op&lt;/strong&gt;. The performance improvement jumped to an impressive &lt;strong&gt;83.23%&lt;/strong&gt;. This demonstrates that the benefits of &lt;code&gt;parallelStream&lt;/code&gt; become more pronounced as the data volume grows and the initial thread management overhead becomes less significant relative to the total work.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Visualizing the Results
&lt;/h2&gt;

&lt;p&gt;To make the results easier to interpret, the output JSON from JMH can be uploaded to the &lt;a href="https://jmh.morethan.io/" rel="noopener noreferrer"&gt;JMH Visualizer&lt;/a&gt;. This free tool generates clear charts that compare the performance scores, making it easy to see the difference at a glance.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Before applying parallelStream across your application, always consider the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallelism is Not a Silver Bullet&lt;/strong&gt;. Unconditional parallel processing is not always better. For small datasets or very simple tasks, the overhead costs can lead to performance degradation. You must consider if the cost of task splitting, merging, and Fork/Join scheduling is more expensive than the actual operation (like a simple string comparison).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Benchmarking is Essential&lt;/strong&gt;. The success in this scenario does not guarantee success in others. The only way to know for sure if a change yields a performance benefit is to test it in a controlled environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to the most critical takeaway: &lt;strong&gt;Measure, Don't Assume&lt;/strong&gt;. While theory provides a strong foundation, only empirical data from a benchmark can confirm whether a change is a true optimization for your specific use case.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>java</category>
    </item>
    <item>
      <title>Discover how ForkJoinPool powers Java's high-performance parallel processing</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Tue, 03 Jun 2025 06:54:07 +0000</pubDate>
      <link>https://dev.to/headf1rst/discover-how-forkjoinpool-powers-javas-high-performance-parallel-processing-3pn7</link>
      <guid>https://dev.to/headf1rst/discover-how-forkjoinpool-powers-javas-high-performance-parallel-processing-3pn7</guid>
      <description>&lt;p&gt;Recently, I optimized our transport operation plan Excel upload feature, boosting performance for logistics system administrators. This tool allows them to upload weekly transportation schedules that our system processes and registers. The upload includes critical validation checks: verifying vehicle numbers, preventing duplicate plans, and identifying scheduling conflicts. By implementing parallel processing for these validations, processing time significantly reduced, enabling logistics managers to finalize transportation plans more efficiently.&lt;/p&gt;

&lt;p&gt;To achieve this, I leveraged &lt;strong&gt;parallel stream&lt;/strong&gt;, a powerful feature introduced in Java 8. While the specific performance metrics and a deep dive into parallel stream with JMH will be covered in a future post, this article focuses on &lt;strong&gt;ForkJoinPool&lt;/strong&gt;, engine that parallel stream uses internally to work its parallel processing.&lt;/p&gt;




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

&lt;p&gt;At its core, ForkJoinPool is a specialized ThreadPoolExecutor designed to efficiently run a large number of tasks using a pool of worker threads. It's built around the &lt;strong&gt;divide-and-conquer&lt;/strong&gt; principle. Large tasks are recursively broken down into smaller, more manageable subtasks (the "fork" step). These subtasks are then processed independently by different threads. Once these smaller tasks complete, their results are progressively combined (the "join" step) to produce the final result of the original large task.&lt;/p&gt;

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

&lt;p&gt;This approach is particularly well-suited for CPU-intensive operations where work can be easily parallelized.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Work-Stealing Algorithm: Keeping Threads Busy
&lt;/h2&gt;

&lt;p&gt;One of the standout features of ForkJoinPool is its work-stealing algorithm, which significantly boosts efficiency. Here's how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each worker thread in the ForkJoinPool maintains its own deque (double-ended queue) of tasks assigned to it.&lt;/li&gt;
&lt;li&gt;A thread primarily processes tasks from the head of its own deque.&lt;/li&gt;
&lt;li&gt;If a thread finishes all its tasks and becomes idle, it doesn't just sit there. Instead, it looks at the deques of other busy threads and "steals" a task from the tail of their deque.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This work-stealing mechanism ensures that threads remain busy as long as there's work to be done anywhere in the pool. It provides excellent load balancing, maximizes CPU utilization, and helps improve overall throughput, especially for tasks with unpredictable execution times.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  ParallelStream and CommonPool
&lt;/h2&gt;

&lt;p&gt;When you use &lt;code&gt;parallelStream()&lt;/code&gt; in Java 8 and later, you're implicitly using a ForkJoinPool. Specifically, parallelStream operations are executed on the static &lt;code&gt;ForkJoinPool.commonPool()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The commonPool() is a JVM-managed, globally available instance of ForkJoinPool. It's convenient because you don't need to manually create, configure, or shut down the pool; the JVM handles its lifecycle. This makes it very easy to introduce parallelism into your applications.&lt;/p&gt;

&lt;p&gt;By default, the number of threads in the commonPool() (its parallelism level) is typically set to the number of available processor cores - 1.&lt;br&gt;
(&lt;code&gt;Runtime.getRuntime().availableProcessors() - 1&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;The "minus one" is to leave a core for the main application thread or other non-ForkJoinPool tasks. However, if the system has only a single processor, the commonPool()'s parallelism will be 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  CommonPool is a Shared Resource
&lt;/h2&gt;

&lt;p&gt;It's crucial to note that the commonPool() is a shared resource throughout your application. Not only parallelStream but also &lt;code&gt;CompletableFuture&lt;/code&gt;'s asynchronous methods (like &lt;code&gt;supplyAsync()&lt;/code&gt; and &lt;code&gt;runAsync(Runnable)&lt;/code&gt;) use the commonPool() by default if no specific Executor is provided.&lt;/p&gt;

&lt;p&gt;Because it's shared, you need to be careful about the types of tasks you submit to it. ForkJoinPool is optimized for CPU-bound tasks – computations that keep the CPU busy. If you use the commonPool() for long-running I/O-bound tasks (e.g., network requests, database queries, file operations) where threads might block and wait for extended periods, you risk starving the pool. &lt;/p&gt;

&lt;p&gt;If all threads in the commonPool() become blocked on I/O operations, other parts of your application relying on it (including other parallelStream operations or CompletableFutures) will be unable to get processing time, leading to severe performance degradation or even deadlocks.&lt;/p&gt;

&lt;p&gt;For I/O-bound tasks, it's generally better to use a separate, dedicated ExecutorService (like a cached thread pool or a fixed-size thread pool configured for the expected number of blocking tasks) to avoid monopolizing the commonPool().&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In essence, ForkJoinPool provides a powerful and efficient framework for parallel task execution, especially for computational workloads. Understanding its mechanics, particularly the work-stealing algorithm and its use in the commonPool(), can help you write more performant and scalable Java applications.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
    </item>
    <item>
      <title>Why @Transactional Sometimes Fails: A Deep Dive into Spring AOP Proxies</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Sun, 01 Jun 2025 10:18:20 +0000</pubDate>
      <link>https://dev.to/headf1rst/the-aop-secret-behind-transactional-you-cant-ignore-4l6d</link>
      <guid>https://dev.to/headf1rst/the-aop-secret-behind-transactional-you-cant-ignore-4l6d</guid>
      <description>&lt;h2&gt;
  
  
  How @Transactional Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Transaction Management via AOP and the Proxy Pattern
&lt;/h3&gt;

&lt;p&gt;AOP (Aspect-Oriented Programming) is a programming paradigm that complements Object-Oriented Programming (OOP) by addressing its limitations in enterprise application development. It allows developers to separate and modularize cross-cutting concerns like transactions, caching, and logging, enabling them to focus on business logic.&lt;/p&gt;

&lt;p&gt;Spring supports AOP using the Proxy Pattern. A proxy object intercepts method calls to the real object, applying common functionalities before and after the business logic executes.&lt;/p&gt;

&lt;p&gt;Similarly, for transaction management, when a method annotated with &lt;code&gt;@Transactional&lt;/code&gt; is called, a proxy object handles the transaction's start and end (commit/rollback) before and after the method execution. So, how is this proxy object created?&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Proxy Creation Methods in Spring AOP
&lt;/h2&gt;

&lt;p&gt;Spring offers two ways to create proxy objects: &lt;strong&gt;JDK Dynamic Proxy&lt;/strong&gt; and &lt;strong&gt;CGLIB Proxy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. JDK Dynamic Proxy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This method uses JDK's &lt;code&gt;java.lang.reflect.Proxy&lt;/code&gt; class to dynamically create proxy objects at runtime that implement interfaces. The target object must implement at least one interface, and the proxy object is created based on the abstract methods of that interface.&lt;/p&gt;

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

&lt;p&gt;Interface-based proxy objects intercept method calls via an &lt;code&gt;InvocationHandler&lt;/code&gt; to process additional functionalities.&lt;br&gt;
For example, if logging is added in addition to transaction handling, another necessary proxy object is created, and methods are called in the following order:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ProxyLogging -&amp;gt; ProxyTransaction -&amp;gt; Target&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. CGLIB Proxy (Default Method)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CGLIB (Code Generation Library) is a library that can dynamically generate classes at runtime by manipulating bytecode. Unlike JDK Dynamic Proxy, it creates proxy objects by subclassing the target object. This means a proxy can be created even if the target object doesn't implement an interface. The proxy object overrides the target object's methods to intercept calls and process additional functionalities before and after method execution.&lt;/p&gt;

&lt;p&gt;Since Spring Boot 2.x, &lt;strong&gt;CGLIB proxy is the default method&lt;/strong&gt; for creating AOP proxies, including for &lt;code&gt;@Transactional&lt;/code&gt; behaviour, regardless of whether the target object implements an interface.&lt;/p&gt;

&lt;p&gt;While this proxy-based AOP mechanism is very powerful, it also creates certain scenarios that require caution. One common point of confusion for developers is method calls within the same class, i.e., self-invocation scenarios. With this understanding of how @Transactional operates through proxies, let's now delve into why transactions might not be applied in such self-invocation cases and the reasons behind it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Internal Method Calls and AOP Proxies
&lt;/h2&gt;

&lt;p&gt;Now that we understand the principle behind &lt;code&gt;@Transactional&lt;/code&gt;, let's examine why transactions are not applied when an internal method (a method within the same class) is called, and the reasons for this behavior.&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="nd"&gt;@RequiredArgsConstructor&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;TransportService&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;TransportRepository&lt;/span&gt; &lt;span class="n"&gt;transportRepository&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;sendTransportEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;transportIds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;transportRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;changeStatuses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportIds&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Sending...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TransportRepository&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;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transport&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;changeStatuses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;transportIds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;transportId&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transportIds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;changeStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Internal call within the proxy if TransportRepositoryImpl is proxied&lt;/span&gt;
        &lt;span class="o"&gt;}&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;changeStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;transportId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Repository&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;TransportRepositoryImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;TransportRepository&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;TransportJpaRepository&lt;/span&gt; &lt;span class="n"&gt;transportJpaRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transport&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;transportJpaRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRES_NEW&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="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;changeStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;transportId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Transport&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transportJpaRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportId&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="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SENT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;transportJpaRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransportServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;TransportService&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;TransportRepository&lt;/span&gt; &lt;span class="n"&gt;transportRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This will be a proxy&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;whenTransportEventsAreSent_statusChangesToSENT&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// given&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transportA&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;Transport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PENDING&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;transportB&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;Transport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2L&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PENDING&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportA&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transportB&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;transportRepository:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// when&lt;/span&gt;
        &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendTransportEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2L&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// then&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transportRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transportRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2L&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;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&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;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SENT&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;result2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&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;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SENT&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;In &lt;code&gt;TransportRepositoryImpl.changeStatus(transportId)&lt;/code&gt;, we attempt to change the &lt;code&gt;transport's&lt;/code&gt; status from PENDING to &lt;code&gt;SENT&lt;/code&gt; using JPA's dirty checking. If dirty checking worked as intended, the test should pass. However, the test fails, and the status remains &lt;code&gt;PENDING&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Does This Happen?
&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;@Repository&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@Slf4j&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;TransportRepositoryImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;TransportRepository&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;TransportJpaRepository&lt;/span&gt; &lt;span class="n"&gt;transportJpaRepository&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;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Propagation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REQUIRES_NEW&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="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;changeStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;transportId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Transport&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transportJpaRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transportId&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="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransportStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SENT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entity is managed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entity is detached"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// This will be logged&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transaction active: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransactionSynchronizationManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isActualTransactionActive&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// false&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;For dirty checking to work correctly, the entity must be managed by the persistence context. &lt;/p&gt;

&lt;p&gt;However, the logs would show that the &lt;code&gt;Transport&lt;/code&gt; entity is not managed by the persistence context (Entity is detached) and that no transaction is active (Transaction active: false). This is because the transaction was not applied to this method call.&lt;/p&gt;

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

&lt;p&gt;The lifecycle of the persistence context is tied to the transaction. If a transaction is not active, a persistence context is not created (or an existing one isn't joined), and thus, the entity is not managed within it. Consequently, in the &lt;code&gt;changeStatus()&lt;/code&gt; method where no transaction is applied during this specific invocation path, a persistence context isn't effectively used for dirty checking.&lt;/p&gt;

&lt;p&gt;Spring creates a proxy object for &lt;code&gt;TransportRepositoryImpl&lt;/code&gt; (likely using CGLIB by default). This proxy object overrides the &lt;code&gt;changeStatus()&lt;/code&gt; method (which is annotated with @Transactional) to inject transaction-related logic before and after the actual method call.&lt;/p&gt;

&lt;p&gt;The problem arises because the &lt;code&gt;TransportRepository's&lt;/code&gt; default method &lt;code&gt;changeStatuses()&lt;/code&gt; calls &lt;code&gt;changeStatus()&lt;/code&gt; through the this reference of the proxy. However, when &lt;code&gt;changeStatuses()&lt;/code&gt; (which itself is not @Transactional on the interface proxy) iterates and calls &lt;code&gt;changeStatus(transportId)&lt;/code&gt;, this call is an internal invocation within the target &lt;code&gt;TransportRepositoryImpl&lt;/code&gt; object if the proxy delegates to it without re-intercepting.&lt;/p&gt;

&lt;p&gt;More precisely:&lt;br&gt;
When &lt;code&gt;transportRepository.changeStatuses()&lt;/code&gt; is called on the proxy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The proxy might invoke the default method &lt;code&gt;changeStatuses()&lt;/code&gt; on itself.&lt;/li&gt;
&lt;li&gt;Inside &lt;code&gt;changeStatuses()&lt;/code&gt;, the call &lt;code&gt;changeStatus(transportId)&lt;/code&gt; is effectively &lt;code&gt;this.changeStatus(transportId)&lt;/code&gt;. If &lt;code&gt;this&lt;/code&gt; refers to the raw &lt;code&gt;TransportRepositoryImpl&lt;/code&gt; instance or if the proxy doesn't re-intercept calls from within itself to itself, the proxy's transactional advice for &lt;code&gt;changeStatus()&lt;/code&gt; is bypassed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spring proxies can only apply transactional (and other AOP) advice to calls that go through the proxy from an external caller. Methods called from within the same class instance (self-invocation) typically bypass the proxy mechanism, so transactions are not applied to the inner call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Do Internal Calls Bypass the Proxy?
&lt;/h2&gt;

&lt;p&gt;In Spring AOP, proxies operate when called by an external client (another bean or component). When an external client calls a method on a bean that is proxied, it's actually calling a method on the proxy object. The proxy then intercepts this call, applies any configured AOP advice (like starting a transaction), and then delegates to the actual method on the target object.&lt;/p&gt;

&lt;p&gt;However, when a method is called from within the same class instance (e.g., &lt;code&gt;this.someOtherMethod()&lt;/code&gt;), it's not an external call being made through the proxy. Instead, the method is invoked directly on the current instance's &lt;code&gt;this&lt;/code&gt; reference, which refers to the raw target object, not the proxy. Therefore, the proxy's AOP advice (including transactional behavior) is bypassed.&lt;/p&gt;

&lt;p&gt;To resolve this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It's recommended to apply @Transactional to methods that are called externally (e.g., directly from the &lt;code&gt;TransportService&lt;/code&gt; to &lt;code&gt;transportRepository.changeStatus(id)&lt;/code&gt; for each ID).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alternatively, you can separate the internal method that needs its own transaction into a different Spring bean so it can be called externally (i.e., injected and then called, ensuring the call goes through its proxy).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the given example, the most straightforward fix to ensure &lt;code&gt;changeStatus&lt;/code&gt; is transactional would be to call it directly from &lt;code&gt;TransportService&lt;/code&gt; in a loop, or refactor &lt;code&gt;TransportRepository&lt;/code&gt; so changeStatuses itself is @Transactional (if that's the desired transactional boundary) and handles the logic appropriately, or to make &lt;code&gt;changeStatuses&lt;/code&gt; a method on &lt;code&gt;TransportRepositoryImpl&lt;/code&gt; directly and have &lt;code&gt;TransportService&lt;/code&gt; call it. The current default method on the interface makes the proxy interaction complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To summarize, Spring's @Transactional annotation relies on AOP proxies (typically CGLIB in modern Spring Boot) to manage transactions by intercepting external method calls. A critical takeaway is that @Transactional may not apply to self-invocations because these internal calls bypass the proxy mechanism.&lt;/p&gt;

&lt;p&gt;It's important to understand that this self-invocation challenge and the underlying proxy behavior are not exclusive to @Transactional. This is a fundamental aspect of how Spring AOP functions. Consequently, other AOP-driven features, such as declarative caching (@Cacheable), method security (@Secured, @PreAuthorize), or any custom aspects you implement, can be similarly affected by internal calls bypassing the proxy.&lt;/p&gt;

&lt;p&gt;Therefore, a solid understanding of AOP's core working principles—especially how proxies intercept external calls and why self-invocations are not subject to this interception—is paramount. This knowledge is key not just for mastering @Transactional, but for effectively utilizing the full spectrum of Spring's powerful AOP capabilities, ultimately leading to more robust and predictable applications.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Java ClassLoaders: How the JVM Dynamically Loads &amp; Executes Your Code.</title>
      <dc:creator>Sanha Ko</dc:creator>
      <pubDate>Tue, 27 May 2025 16:28:01 +0000</pubDate>
      <link>https://dev.to/headf1rst/java-classloaders-how-the-jvm-dynamically-loads-executes-your-code-56bl</link>
      <guid>https://dev.to/headf1rst/java-classloaders-how-the-jvm-dynamically-loads-executes-your-code-56bl</guid>
      <description>&lt;p&gt;Java’s "Write Once, Run Anywhere" principle is foundational to its sustained popularity. This portability is powered by the Java Virtual Machine (JVM), specifically its sophisticated system of ClassLoaders and dynamic class-loading mechanisms. Understanding these components can greatly improve your insight into Java’s runtime behaviour and performance characteristics.&lt;br&gt;
Let’s explore step-by-step how Java transforms your code into executable functionality.&lt;/p&gt;
&lt;h2&gt;
  
  
  What are Java ClassLoaders?
&lt;/h2&gt;

&lt;p&gt;A Java ClassLoader is responsible for dynamically loading Java classes into the JVM at runtime. When the JVM requires a class, it’s the ClassLoader’s task to locate the class definition (typically a &lt;code&gt;.class&lt;/code&gt; file) and load it into memory.&lt;br&gt;
Java uses a hierarchical ClassLoader structure composed of three built-in loaders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bootstrap ClassLoader:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The root ClassLoader, implemented in native code.&lt;/li&gt;
&lt;li&gt;Loads Java's core API classes from &lt;code&gt;JAVA_HOME/lib&lt;/code&gt; (e.g., &lt;code&gt;rt.jar&lt;/code&gt;, &lt;code&gt;tools.jar&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Platform ClassLoader&lt;/strong&gt; (formerly Extension ClassLoader):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loads extension classes from &lt;code&gt;JAVA_HOME/lib/ext&lt;/code&gt; or directories specified by the &lt;code&gt;java.ext.dirs&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;Typically handles classes prefixed with &lt;code&gt;javax.*&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Application ClassLoader&lt;/strong&gt; (System ClassLoader):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loads classes specified by the application’s classpath (&lt;code&gt;CLASSPATH&lt;/code&gt; or &lt;code&gt;-cp&lt;/code&gt; option).&lt;/li&gt;
&lt;li&gt;Handles application-level classes and JARs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The ClassLoader Hierarchy &amp;amp; Parent Delegation Model
&lt;/h3&gt;

&lt;p&gt;Java ClassLoaders operate according to the &lt;strong&gt;Parent Delegation Model&lt;/strong&gt;. Under this model, class loading requests are first delegated up to the parent ClassLoader. Only when all parent loaders fail to find the class does the current loader attempt to load it.&lt;br&gt;
The hierarchy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bootstrap ClassLoader
      ↑ delegates to
Platform ClassLoader
      ↑ delegates to
Application ClassLoader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages of Parent Delegation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prevents Redundant Loading&lt;/strong&gt;: Ensures classes are loaded just once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintains Consistency&lt;/strong&gt;: Core classes like &lt;code&gt;java.lang.Object&lt;/code&gt; are uniformly loaded by the Bootstrap loader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Protects core Java classes from malicious overrides by lower-level loaders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The JVM Class Loading Process
&lt;/h2&gt;

&lt;p&gt;Java's class loading consists of three main phases:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. Loading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The ClassLoader reads the class bytecode into memory.&lt;/li&gt;
&lt;li&gt;Parses class metadata (name, superclass, interfaces, methods, fields).&lt;/li&gt;
&lt;li&gt;Stores metadata in the JVM Method Area.&lt;/li&gt;
&lt;li&gt;Creates a corresponding &lt;code&gt;java.lang.Class&lt;/code&gt; instance in heap memory.&lt;/li&gt;
&lt;li&gt;Classes are loaded dynamically, triggered upon their first usage (object instantiation, static method invocation, or static field access).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Linking
&lt;/h3&gt;

&lt;p&gt;This prepares a loaded class for execution, divided into three sub-steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verification&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Ensures bytecode integrity, adherence to JVM specifications.&lt;/li&gt;
&lt;li&gt;Checks class structure, inheritance rules, interface implementation, bytecode validity, and symbolic reference correctness.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Preparation&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Allocates memory for static fields and assigns default values (e.g., numeric fields to &lt;code&gt;0&lt;/code&gt;, object references to &lt;code&gt;null&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Resolution&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Converts symbolic references into direct memory addresses or offsets.&lt;/li&gt;
&lt;li&gt;JVM implementations can perform resolution eagerly (at link-time) or lazily (upon first reference).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Initialization
&lt;/h3&gt;

&lt;p&gt;This is the final phase of class loading. During initialization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static variables are assigned their actual values as defined in the code (e.g., &lt;code&gt;static int count = 100;&lt;/code&gt; would set &lt;code&gt;count&lt;/code&gt; to &lt;code&gt;100&lt;/code&gt;, overriding the default &lt;code&gt;0&lt;/code&gt; from the preparation phase).&lt;/li&gt;
&lt;li&gt;Static initialization blocks (if any) are executed.
This process is triggered only when the class is actively used for the first time (e.g., an instance is created, a static method is called, or a static field is accessed that is not a compile-time constant). The JVM ensures that initialization is done in a thread-safe manner.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dynamic Class Loading and Binding in Java
&lt;/h2&gt;

&lt;p&gt;Java’s dynamic loading and binding capabilities provide substantial flexibility:&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Loading
&lt;/h3&gt;

&lt;p&gt;Java loads classes at runtime, only when needed.&lt;br&gt;
You can explicitly load classes via:&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;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.MyClass"&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someCondition&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="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.MyClass"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeclaredConstructor&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// use instance&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&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;h3&gt;
  
  
  Dynamic Binding (Late Binding)
&lt;/h3&gt;

&lt;p&gt;JVM determines the exact method to invoke at runtime.&lt;/p&gt;

&lt;p&gt;Essential for polymorphism:&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;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;sound&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Animal makes a sound"&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;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dog barks"&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;DynamicBindingExample&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="n"&gt;animal&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;Dog&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Outputs: "Dog barks"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros and Cons of Java’s Dynamic Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Cost&lt;/strong&gt;: Runtime loading, verification, and binding introduce slight overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Flexibility&lt;/strong&gt;: Enables runtime decisions, dynamic plugins, and extensible designs without recompilation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Interface-Driven Runtime Decisions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using interfaces allows runtime determination of implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;pay&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;CreditCardPayment&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Paying with Credit Card"&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;PayPalPayment&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Paying with PayPal"&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;PaymentProcessor&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="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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CreditCardPayment"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This could come from config or user input&lt;/span&gt;
        &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;paymentService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeclaredConstructor&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;paymentService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pay&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// "Paying with Credit Card"&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;
  
  
  Java Object Layout and JOL
&lt;/h2&gt;

&lt;p&gt;Java Object Layout (JOL) helps developers understand object memory structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object Header&lt;/strong&gt;: Metadata (hash code, garbage collection info, lock states).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Fields&lt;/strong&gt;: Object’s actual data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Padding&lt;/strong&gt;: Ensures memory alignment (typically multiples of 8 bytes).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using JOL Library
&lt;/h3&gt;

&lt;p&gt;You can use the JOL library to inspect these details. Add the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.openjdk.jol:jol-core:0.16'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, observe the layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.openjdk.jol.info.ClassLayout&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;SimpleObject&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;intField&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;longField&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;byteField&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;refField&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;JolTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SimpleObject&lt;/span&gt; &lt;span class="n"&gt;obj&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;SimpleObject&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Before hashCode():"&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;ClassLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toPrintable&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Calling hashCode() can trigger its computation and storage in the Mark Word&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hashCode&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"After hashCode():"&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;ClassLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toPrintable&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;Running this code will show the internal layout of &lt;code&gt;SimpleObject&lt;/code&gt;. The output before and after calling &lt;code&gt;obj.hashCode()&lt;/code&gt; might reveal changes in the object's Mark Word, as the hash code, if not already computed, gets stored there.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Object Header
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mark Word&lt;/strong&gt;: Stores hash codes, GC status (age bits), synchronization states (lock information). Its structure can change depending on the object's state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class Pointer (Klass Pointer)&lt;/strong&gt;: References class metadata in the Method Area, linking the object instance to its class definition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Java’s ClassLoader and dynamic class loading system enable JVM’s platform-independent and extensible runtime environment. By loading, verifying, initializing, and binding classes on-demand, Java strikes a balance between performance and flexibility, making it ideal for complex, secure applications. A deep understanding of these internals transforms your role from a Java developer into a confident operator of the JVM.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>java</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
