{"id":2214,"date":"2026-07-07T01:48:50","date_gmt":"2026-07-06T23:48:50","guid":{"rendered":"https:\/\/kindsonthegenius.com\/blog\/spring-boot-3-java-21-virtual-threads-rest-api-tutorial\/"},"modified":"2026-07-07T01:48:50","modified_gmt":"2026-07-06T23:48:50","slug":"spring-boot-3-java-21-virtual-threads-rest-api-tutorial","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/spring-boot-3-java-21-virtual-threads-rest-api-tutorial\/","title":{"rendered":"Spring Boot 3 + Java 21: Virtual Threads and REST API Tutorial (2026)"},"content":{"rendered":"<p><!-- ktg-updated-banner --><\/p>\n<div class=\"ktg-updated-banner\" style=\"margin:1em 0;padding:0.75em 1em;background:#eff6ff;border-left:4px solid #3b82f6;border-radius:4px;\">\n<p><strong>Updated July 7, 2026:<\/strong> Covers Spring Boot 3.3, Java 21 LTS, virtual thread configuration, a sample REST API, and load-testing tips. Part of the Spring Boot 2026 tutorial series.<\/p>\n<\/div>\n<p>Learn how to use <strong>Java 21 virtual threads with Spring Boot 3<\/strong> to build REST APIs that handle thousands of concurrent I\/O-bound requests without thread-pool exhaustion. This <strong>spring boot java 21 tutorial<\/strong> shows configuration, a working controller, and when virtual threads beat platform threads.<\/p>\n<p><strong>Prerequisites:<\/strong> Java basics and Spring Boot fundamentals. Complete <a href=\"https:\/\/kindsonthegenius.com\/blog\/java-progamming-for-beginners-lesson-1-introduction-to-java-and-intallation-of-netbeans\/\">Java Lesson 1<\/a> if you are new to Java.<\/p>\n<p><strong>Estimated time:<\/strong> 60\u201375 minutes. <strong>Difficulty:<\/strong> Intermediate.<\/p>\n<p><!-- ktg-article-toc --><\/p>\n<nav class=\"ktg-article-toc\" aria-label=\"Spring Boot Java 21 virtual threads tutorial table of contents\" style=\"margin:1.5em 0;padding:1em 1.25em;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;\">\n<h2>Table of Contents<\/h2>\n<ol>\n<li><a href=\"#virtual-threads\">What are virtual threads?<\/a><\/li>\n<li><a href=\"#when-to-use\">When to use virtual threads in Spring Boot<\/a><\/li>\n<li><a href=\"#requirements\">What you need<\/a><\/li>\n<li><a href=\"#create-project\">Create the Spring Boot 3 project<\/a><\/li>\n<li><a href=\"#enable-virtual-threads\">Enable virtual threads<\/a><\/li>\n<li><a href=\"#rest-api\">Build a REST API with blocking I\/O<\/a><\/li>\n<li><a href=\"#structured-concurrency\">Structured concurrency example<\/a><\/li>\n<li><a href=\"#benchmark\">Benchmark platform vs virtual threads<\/a><\/li>\n<li><a href=\"#faq\">Frequently asked questions<\/a><\/li>\n<li><a href=\"#next-steps\">Next steps<\/a><\/li>\n<\/ol>\n<\/nav>\n<h2 id=\"virtual-threads\">What Are Virtual Threads?<\/h2>\n<p><strong>Virtual threads<\/strong> (Project Loom, finalized in Java 21) are lightweight threads managed by the JVM rather than the operating system. A single platform thread can run thousands of virtual threads, making them ideal for <strong>I\/O-bound<\/strong> workloads like HTTP clients, database queries, and file reads.<\/p>\n<figure style=\"margin:1.5em 0;text-align:center;\"><img decoding=\"async\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/07\/java-21-virtual-threads-diagram.svg\" alt=\"Diagram comparing Java platform threads one-to-one with OS threads versus many virtual threads sharing carrier threads\" width=\"800\" height=\"380\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">Platform threads vs Java 21 virtual threads \u2014 memory and scalability comparison<\/figcaption><\/figure>\n<h2 id=\"when-to-use\">When to Use Virtual Threads in Spring Boot<\/h2>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0;\">\n<thead>\n<tr style=\"background:#f1f5f9;\">\n<th style=\"padding:0.5em;border:1px solid #e2e8f0;\">Use virtual threads<\/th>\n<th style=\"padding:0.5em;border:1px solid #e2e8f0;\">Stick with platform threads<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">REST APIs with blocking JDBC\/JPA<\/td>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">CPU-heavy computation (image processing, ML inference)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">Microservices calling external HTTP APIs<\/td>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">Code using synchronized blocks on hot paths (pinning)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">High concurrency, moderate latency tolerance<\/td>\n<td style=\"padding:0.5em;border:1px solid #e2e8f0;\">Legacy libraries not yet virtual-thread friendly<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"requirements\">What You Need<\/h2>\n<ul>\n<li><strong>JDK 21<\/strong> (LTS) \u2014 download from <a href=\"https:\/\/adoptium.net\/\" target=\"_blank\" rel=\"noopener noreferrer\">Adoptium<\/a><\/li>\n<li><strong>Spring Boot 3.2+<\/strong> (3.3 recommended)<\/li>\n<li>Maven 3.9+ or Gradle 8+<\/li>\n<li>Optional: <code>hey<\/code> or Apache JMeter for load testing<\/li>\n<\/ul>\n<h2 id=\"create-project\">Step 1: Create the Spring Boot 3 Project<\/h2>\n<p>At <a href=\"https:\/\/start.spring.io\/\" target=\"_blank\" rel=\"noopener noreferrer\">start.spring.io<\/a>, select Java <strong>21<\/strong>, Spring Boot <strong>3.3.x<\/strong>, and add <strong>Spring Web<\/strong>. Name the project <code>virtual-threads-demo<\/code>.<\/p>\n<pre><code class=\"language-xml\">&lt;properties&gt;\n  &lt;java.version&gt;21&lt;\/java.version&gt;\n&lt;\/properties&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n  &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n&lt;\/dependency&gt;\n<\/code><\/pre>\n<figure style=\"margin:1.5em 0;text-align:center;\"><img decoding=\"async\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/07\/spring-boot-java21-start-project.png\" alt=\"Spring Initializr showing Java 21 and Spring Boot 3.3 selected for new REST API project\" width=\"640\" height=\"400\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">Spring Initializr \u2014 Java 21 + Spring Boot 3.3<\/figcaption><\/figure>\n<h2 id=\"enable-virtual-threads\">Step 2: Enable Virtual Threads<\/h2>\n<p>Add one line to <code>application.properties<\/code>:<\/p>\n<pre><code class=\"language-properties\">spring.threads.virtual.enabled=true\n<\/code><\/pre>\n<p>Spring Boot 3.2+ configures Tomcat to use virtual threads for request handling automatically. No custom <code>Executor<\/code> bean is required for standard MVC controllers.<\/p>\n<figure style=\"margin:1.5em 0;text-align:center;\"><img decoding=\"async\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/07\/spring-boot-virtual-threads-config.png\" alt=\"application.properties file with spring.threads.virtual.enabled=true highlighted in IDE\" width=\"640\" height=\"320\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">Enable virtual threads with a single Spring Boot property<\/figcaption><\/figure>\n<h2 id=\"rest-api\">Step 3: Build a REST API with Blocking I\/O<\/h2>\n<p>Create a controller that simulates I\/O latency \u2014 the classic use case for virtual threads:<\/p>\n<pre><code class=\"language-java\">@RestController\n@RequestMapping(\"\/api\/products\")\npublic class ProductController {\n\n    @GetMapping(\"\/{id}\")\n    public Product getProduct(@PathVariable Long id) throws InterruptedException {\n        \/\/ Simulate blocking database or HTTP call\n        Thread.sleep(200);\n        return new Product(id, \"Widget \" + id, 29.99);\n    }\n\n    @GetMapping\n    public List&lt;Product&gt; listProducts() throws InterruptedException {\n        Thread.sleep(100);\n        return List.of(\n            new Product(1L, \"Widget 1\", 19.99),\n            new Product(2L, \"Widget 2\", 29.99)\n        );\n    }\n\n    record Product(Long id, String name, double price) {}\n}\n<\/code><\/pre>\n<p>Run the app:<\/p>\n<pre><code class=\"language-bash\">mvn spring-boot:run\ncurl http:\/\/localhost:8080\/api\/products\/1\n<\/code><\/pre>\n<figure style=\"margin:1.5em 0;text-align:center;\"><img decoding=\"async\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/07\/spring-boot-virtual-threads-curl-response.png\" alt=\"Terminal curl command returning JSON product response from Spring Boot REST API\" width=\"640\" height=\"300\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">REST API response \u2014 each request runs on a virtual thread<\/figcaption><\/figure>\n<h2 id=\"structured-concurrency\">Step 4: Structured Concurrency Example<\/h2>\n<p>Java 21&#8217;s structured concurrency lets you fan out parallel I\/O safely:<\/p>\n<pre><code class=\"language-java\">@GetMapping(\"\/aggregate\/{id}\")\npublic AggregatedProduct aggregate(@PathVariable Long id) throws Exception {\n    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {\n        Subtask&lt;Product&gt; product = scope.fork(() -&gt; fetchProduct(id));\n        Subtask&lt;Reviews&gt; reviews = scope.fork(() -&gt; fetchReviews(id));\n\n        scope.join();\n        scope.throwIfFailed();\n\n        return new AggregatedProduct(product.get(), reviews.get());\n    }\n}\n<\/code><\/pre>\n<p>This pattern works well in Spring services that call multiple downstream APIs \u2014 common in microservices like the <a href=\"https:\/\/kindsonthegenius.com\/blog\/inventoryms-complete-application-spring-boot-api\/\">InventoryMS Spring Boot API<\/a>.<\/p>\n<h2 id=\"benchmark\">Step 5: Benchmark Platform vs Virtual Threads<\/h2>\n<p>Load-test with <code>hey<\/code> (install via Homebrew or GitHub releases):<\/p>\n<pre><code class=\"language-bash\"># With virtual threads enabled (default after step 2)\nhey -n 5000 -c 200 http:\/\/localhost:8080\/api\/products\/1\n\n# Disable virtual threads, restart, and compare:\n# spring.threads.virtual.enabled=false\nhey -n 5000 -c 200 http:\/\/localhost:8080\/api\/products\/1\n<\/code><\/pre>\n<figure style=\"margin:1.5em 0;text-align:center;\"><img decoding=\"async\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/07\/spring-boot-virtual-threads-benchmark.png\" alt=\"hey load test results comparing requests per second with virtual threads enabled versus disabled\" width=\"640\" height=\"400\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">Virtual threads typically sustain higher concurrency under I\/O-bound load<\/figcaption><\/figure>\n<h2 id=\"faq\">Frequently Asked Questions<\/h2>\n<p><strong>Do I need to change my Spring Boot controller code for virtual threads?<\/strong><br \/>\nNo. Enable <code>spring.threads.virtual.enabled=true<\/code> and existing blocking MVC controllers run on virtual threads automatically. Avoid wrapping every call in <code>CompletableFuture<\/code> unless you have a specific reason.<\/p>\n<p><strong>Are virtual threads available in Java 17?<\/strong><br \/>\nVirtual threads were preview\/incubator in Java 19\u201320 and finalized in <strong>Java 21 LTS<\/strong>. Spring Boot 3.2+ requires Java 17 minimum but virtual thread support targets Java 21.<\/p>\n<p><strong>What is thread pinning and should I worry?<\/strong><br \/>\nPinning occurs when synchronized blocks or native code prevent a virtual thread from unmounting. Most Spring\/JDBC drivers are pinning-safe in 2026, but profile CPU-bound synchronized code if throughput plateaus.<\/p>\n<p><strong>Do virtual threads work with Spring WebFlux?<\/strong><br \/>\nWebFlux is already reactive\/non-blocking. Virtual threads target the <strong>Servlet stack<\/strong> (Spring MVC + Tomcat). Use one model per service \u2014 don&#8217;t mix unnecessarily.<\/p>\n<p><strong>Can I use virtual threads with @Async?<\/strong><br \/>\nYes. Define a <code>TaskExecutor<\/code> bean using <code>Executors.newVirtualThreadPerTaskExecutor()<\/code> and reference it in <code>@Async(\"virtualThreadExecutor\")<\/code>.<\/p>\n<p><strong>How does this relate to the RAG chatbot tutorial?<\/strong><br \/>\nAI chat endpoints are I\/O-bound (embedding + LLM calls). Virtual threads let a RAG service handle many concurrent users \u2014 see the <a href=\"https:\/\/kindsonthegenius.com\/blog\/how-to-build-a-rag-chatbot-with-spring-boot-and-openai-step-by-step\/\">Spring Boot RAG tutorial<\/a>.<\/p>\n<h2 id=\"next-steps\">Next Steps<\/h2>\n<ul>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/how-to-build-a-rag-chatbot-with-spring-boot-and-openai-step-by-step\/\"><strong>Build a RAG Chatbot with Spring Boot<\/strong> \u2014 AI + REST API<\/a><\/li>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/deploy-spring-boot-kubernetes-docker-compose-minikube-2026\/\"><strong>Deploy Spring Boot to Kubernetes<\/strong> \u2014 production deployment<\/a><\/li>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/json-web-token-how-to-secure-rest-api-using-jwt-in-spring-boot\/\">Secure REST APIs with JWT in Spring Boot<\/a><\/li>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/15-easy-free-java-tutorials-with-step-by-step-examples-and-quiz\/\">15 Easy Free Java Tutorials<\/a><\/li>\n<li><a href=\"https:\/\/www.alkademy.com\/courses\" target=\"_blank\" rel=\"noopener noreferrer\">Alkademy Java courses<\/a><\/li>\n<\/ul>\n<p><!-- ktg-series-nav --><\/p>\n<nav class=\"ktg-series-nav\" aria-label=\"Spring Boot 2026 tutorial series\">\n<p><strong>Series:<\/strong> <a href=\"https:\/\/kindsonthegenius.com\/blog\/how-to-build-a-rag-chatbot-with-spring-boot-and-openai-step-by-step\/\">RAG Chatbot<\/a> \u00b7 <a href=\"https:\/\/kindsonthegenius.com\/blog\/deploy-spring-boot-kubernetes-docker-compose-minikube-2026\/\">Kubernetes Deployment<\/a><\/p>\n<\/nav>\n<p><!-- ktg-alkademy-cta --><\/p>\n<div class=\"ktg-alkademy-cta\" style=\"margin:2em 0;padding:1.25em;border-left:4px solid #2563eb;background:#f8fafc;\">\n<p><strong>Master modern Java?<\/strong> Join <a href=\"https:\/\/www.alkademy.com\/courses\" target=\"_blank\" rel=\"noopener noreferrer\">Alkademy<\/a> for live Java 21 and Spring Boot courses with real-world projects.<\/p>\n<\/div>\n<p><!-- ktg-faq-schema --><br \/>\n<script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"Do I need to change Spring Boot controller code for virtual threads?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"No. Set spring.threads.virtual.enabled=true and existing blocking MVC controllers run on virtual threads automatically in Spring Boot 3.2+.\"}},{\"@type\":\"Question\",\"name\":\"Are virtual threads available in Java 17?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Virtual threads were finalized in Java 21 LTS. Spring Boot 3.2+ supports them when running on JDK 21.\"}},{\"@type\":\"Question\",\"name\":\"Do virtual threads work with Spring WebFlux?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"WebFlux is reactive and non-blocking. Virtual threads target the Servlet stack (Spring MVC + Tomcat). Use one model per service.\"}},{\"@type\":\"Question\",\"name\":\"When should I not use virtual threads?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Avoid virtual threads for CPU-bound workloads, heavy synchronized hot paths, or legacy libraries that pin carrier threads.\"}}]}<\/script><\/p>\n<p><!-- ktg-howto-schema --><br \/>\n<script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"HowTo\",\"name\":\"Spring Boot 3 with Java 21 Virtual Threads REST API\",\"description\":\"Enable Java 21 virtual threads in Spring Boot 3 and build a high-throughput blocking REST API.\",\"totalTime\":\"PT75M\",\"tool\":[{\"@type\":\"HowToTool\",\"name\":\"JDK 21\"},{\"@type\":\"HowToTool\",\"name\":\"Spring Boot 3.3\"},{\"@type\":\"HowToTool\",\"name\":\"Maven\"}],\"step\":[{\"@type\":\"HowToStep\",\"name\":\"Create Spring Boot project\",\"text\":\"Generate a Spring Boot 3.3 project with Java 21 and Spring Web dependency.\"},{\"@type\":\"HowToStep\",\"name\":\"Enable virtual threads\",\"text\":\"Add spring.threads.virtual.enabled=true to application.properties.\"},{\"@type\":\"HowToStep\",\"name\":\"Create REST controller\",\"text\":\"Build a controller with blocking I\/O simulation using Thread.sleep or JDBC calls.\"},{\"@type\":\"HowToStep\",\"name\":\"Test endpoints\",\"text\":\"Run the application and verify JSON responses with curl.\"},{\"@type\":\"HowToStep\",\"name\":\"Benchmark concurrency\",\"text\":\"Load test with hey or JMeter comparing virtual threads enabled vs disabled.\"}]}<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Updated July 7, 2026: Covers Spring Boot 3.3, Java 21 LTS, virtual thread configuration, a sample REST API, and load-testing tips. Part of the Spring &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[85],"tags":[],"class_list":["post-2214","post","type-post","status-publish","format-standard","hentry","category-java"],"_links":{"self":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2214","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/comments?post=2214"}],"version-history":[{"count":0,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2214\/revisions"}],"wp:attachment":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media?parent=2214"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/categories?post=2214"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/tags?post=2214"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}