<?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: Ashish Sharda</title>
    <description>The latest articles on DEV Community by Ashish Sharda (@ashish_sharda_a540db2e50e).</description>
    <link>https://dev.to/ashish_sharda_a540db2e50e</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%2F3245022%2F1ae02152-ef10-4c46-9ec2-3824fb575cf3.jpeg</url>
      <title>DEV Community: Ashish Sharda</title>
      <link>https://dev.to/ashish_sharda_a540db2e50e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashish_sharda_a540db2e50e"/>
    <language>en</language>
    <item>
      <title>The IDE Is Dead. Long Live the Agent: What Google Antigravity 2.0 Actually Means for Developers</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 24 May 2026 14:17:07 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/the-ide-is-dead-long-live-the-agent-what-google-antigravity-20-actually-means-for-developers-1fb0</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/the-ide-is-dead-long-live-the-agent-what-google-antigravity-20-actually-means-for-developers-1fb0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-io-writing-2026-05-19"&gt;Google I/O Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Everyone came to Google I/O 2026 expecting the consumer fireworks — Gemini Omni generating videos from text, the redesigned Gemini app, smart glasses, AI Mode in Search. And Google delivered all of it, loudly.&lt;/p&gt;

&lt;p&gt;But buried under those headlines was the announcement that actually changes how we build software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Antigravity 2.0&lt;/strong&gt; — and with it, Managed Agents in the Gemini API — is not a product update. It's a declaration that the IDE is no longer the unit of work. And if you're a software engineer who hasn't internalized what that means yet, this is your wake-up call.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Shipped
&lt;/h2&gt;

&lt;p&gt;Let me be precise, because the marketing language around this was characteristically slippery.&lt;/p&gt;

&lt;p&gt;Antigravity 2.0 is not a new IDE. It's a &lt;strong&gt;five-surface agent platform&lt;/strong&gt; that shipped simultaneously on May 19, 2026:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Desktop App&lt;/strong&gt; — A standalone multi-agent orchestration hub. Not a text editor with autocomplete. An environment where you spawn multiple agents in parallel, schedule background tasks, and issue voice commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;)&lt;/strong&gt; — The same agent harness, terminal-first. One &lt;code&gt;curl | bash&lt;/code&gt; install. Google simultaneously killed Gemini CLI with a June 18 shutdown date. That's not a pivot — that's a declaration of direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Antigravity SDK&lt;/strong&gt; — Programmatic access to the underlying agent harness. Host it on your own infrastructure. Embed it inside your own products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Agents in the Gemini API&lt;/strong&gt; — A single API call that spins up a full remote Linux environment with reasoning, tool use, code execution, and &lt;strong&gt;stateful persistence across sessions&lt;/strong&gt;. No context plumbing required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Enterprise Agent Platform&lt;/strong&gt; — The enterprise-grade deployment path with Google Cloud privacy guarantees and centralized governance for regulated industries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The default model powering all of this is &lt;strong&gt;Gemini 3.5 Flash&lt;/strong&gt; — a model that outperforms Gemini 3.1 Pro on coding and agentic benchmarks while running four times faster than comparable frontier models.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Is Bigger Than the Gemini Omni Demo
&lt;/h2&gt;

&lt;p&gt;Gemini Omni is impressive. The multimodal video generation got the loudest applause. But Omni is a &lt;em&gt;tool&lt;/em&gt;. Antigravity 2.0 is an &lt;em&gt;architecture shift&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here's the problem every serious developer has been fighting for the past two years: &lt;strong&gt;agentic orchestration is miserable to build from scratch.&lt;/strong&gt; If you've ever tried to wire together LangChain, a vector store, a sandboxed code executor, session memory, and tool routing into something that doesn't fall apart in production — you know exactly what I mean.&lt;/p&gt;

&lt;p&gt;The frameworks help. They also introduce abstraction layers that become debugging nightmares the moment something goes wrong inside a black box.&lt;/p&gt;

&lt;p&gt;Antigravity 2.0's Managed Agents API solves the hardest parts of this directly at the infrastructure level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The sandbox is managed for you.&lt;/strong&gt; One API call spins up an isolated Linux environment. The agent reasons, calls tools, runs code, browses the web — inside that environment. No Docker configs. No security surface to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State persists across calls.&lt;/strong&gt; Files and context survive turns. You don't write your own context management plumbing. This alone eliminates hundreds of lines of boilerplate from most agent implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The distribution pipeline is end-to-end.&lt;/strong&gt; AI Studio → Antigravity → Google Play. Agents can natively call Google Workspace APIs. The path from prototype to production product runs through surfaces Google already controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is Google doing what Google does best: vertically integrating the entire stack and making the path-of-least-resistance also the path to production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Things Google Didn't Say Loudly Enough
&lt;/h2&gt;

&lt;p&gt;I want to talk about the parts of this announcement that deserve more scrutiny.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AGENTS.md Pattern
&lt;/h3&gt;

&lt;p&gt;Buried in the Antigravity 2.0 documentation is a detail that I think will define agent development for the next several years:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of writing complex orchestration code, you can define everything in markdown files like &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;SKILL.md&lt;/code&gt; and register them as a named agent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the AGENTS.md pattern going first-class. If you've been following the cursor/Copilot space, you know that configuration-as-markdown for agent behavior has been bubbling up as a community convention. Google just made it an official primitive in their platform.&lt;/p&gt;

&lt;p&gt;That's a big deal. It means agent behavior becomes auditable, version-controllable, and readable by non-engineers. For any team trying to build internal tooling with agents, this reduces the blast radius of a bad agent dramatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebMCP: The Standard Nobody Is Talking About
&lt;/h3&gt;

&lt;p&gt;Google proposed &lt;strong&gt;WebMCP&lt;/strong&gt;, an open web standard for exposing JavaScript functions and HTML forms as structured tools to browser-based agents. It's in early preview, and the coverage has been minimal.&lt;/p&gt;

&lt;p&gt;But think about the trajectory here: if WebMCP gains adoption, every web application becomes a potential tool surface for agents. You don't build a custom integration — you expose your existing interface through a standard protocol and agents discover it.&lt;/p&gt;

&lt;p&gt;That's not a small thing. That's potentially the browser equivalent of what REST did for APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pricing Reality Check
&lt;/h3&gt;

&lt;p&gt;The full capability set requires the new &lt;strong&gt;AI Ultra tier at $100/month&lt;/strong&gt;. Managed Agents bills per run, not per token — which means long-running agents get expensive fast.&lt;/p&gt;

&lt;p&gt;Google is offering a $100 bonus credit buffer through May 25, 2026 (tomorrow, as of this writing), but individual developers and startups need to think carefully about the unit economics before going all-in on Managed Agents for production workloads. Cache aggressively. Mock expensive runs during development. Be deliberate about when an agent needs the full managed sandbox versus a lighter-weight tool-call loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model Shift
&lt;/h2&gt;

&lt;p&gt;Here's the frame I'd offer for thinking about what just happened:&lt;/p&gt;

&lt;p&gt;For the last decade, the IDE was the unit of developer work. Your editor was your context. Your plugins were your tools. Your workflow started and ended in that window.&lt;/p&gt;

&lt;p&gt;Antigravity 2.0 argues that &lt;strong&gt;the agent is the unit of work&lt;/strong&gt;. Your agents have context. Your skills (markdown files) are your tools. Your workflow is a mesh of coordinating agents that reason, execute, and persist state across sessions.&lt;/p&gt;

&lt;p&gt;The IDE doesn't disappear — but it becomes one surface among many, not the center of gravity.&lt;/p&gt;

&lt;p&gt;This is the agentic era in practice, not just in marketing copy. And for engineers who build platforms, infrastructure, or developer tooling, the Managed Agents API represents the most production-ready on-ramp to that era that any major cloud provider has shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do First
&lt;/h2&gt;

&lt;p&gt;If I were spinning up a new project tomorrow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the &lt;code&gt;agy&lt;/code&gt; CLI and run through a basic agent harness locally.&lt;/strong&gt; Get the mental model from the terminal up, not the GUI down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the Antigravity AGENTS.md spec carefully.&lt;/strong&gt; This is going to be a pattern that outlasts the 2.0 release cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try a Managed Agent API call that does something stateful&lt;/strong&gt; — have it execute code, read a file in the sandbox, and pick up where it left off on the next call. Understanding state persistence hands-on is worth more than any documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look at WebMCP.&lt;/strong&gt; It's early, but understanding the proposed standard now means you're positioned to expose your interfaces correctly before the ecosystem converges on conventions.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Take
&lt;/h2&gt;

&lt;p&gt;Google I/O 2026 was genuinely impressive across the board. But the most important thing for developers — the thing that will still matter in five years — isn't the Gemini Omni video demos or the redesigned app interface.&lt;/p&gt;

&lt;p&gt;It's the fact that Google just shipped a production-grade infrastructure abstraction for agentic workflows, integrated it into a platform from prototype to Play Store, and quietly deprecated the tool that defined a generation of AI-assisted development (RIP Gemini CLI, you were fine).&lt;/p&gt;

&lt;p&gt;The IDE is no longer the center. The agent is.&lt;/p&gt;

&lt;p&gt;Adjust your mental model accordingly.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>googleiochallenge</category>
    </item>
    <item>
      <title>I Work in Drone Traffic Management. Here's What Gemma 4 Changed.</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 24 May 2026 14:02:59 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/i-work-in-drone-traffic-management-heres-what-gemma-4-changed-1g0g</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/i-work-in-drone-traffic-management-heres-what-gemma-4-changed-1g0g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Write About Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every morning before I open Slack, I think about airspace.&lt;/p&gt;

&lt;p&gt;Not metaphorically. I'm the Head of Engineering at a drone UTM (Unmanned Traffic Management) platform — software that keeps drones from colliding with each other and with manned aircraft over cities where large-scale commercial drone operators and FAA-regulated fleets are flying missions. Our system processes flight plans, validates airspace conflicts in real time, and communicates with the FAA's DroneZone infrastructure.&lt;/p&gt;

&lt;p&gt;Airspace is genuinely complex. A single flight corridor over a city involves Class B shelves, TFRs that pop up with 20 minutes of notice, active NOTAMs, LAANC grid authorizations, and dynamic separation rules that change based on weather and traffic density. &lt;/p&gt;

&lt;p&gt;Then Gemma 4 dropped on April 2nd, 2026, and I started rethinking some assumptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Needed (And Why Previous Models Fell Short)
&lt;/h2&gt;

&lt;p&gt;The pain point isn't data — we have plenty of that. It's &lt;em&gt;translation&lt;/em&gt;. Drone operators range from ex-military pilots who know the FARs cold, to delivery companies that just hired someone last week to run a drone route. The same airspace data needs to be communicated completely differently to each audience.&lt;/p&gt;

&lt;p&gt;NOTAMs (Notices to Air Missions) are a perfect example. They look like this:&lt;br&gt;
!PHX 05/014 PHX NAV ILS RWY 8 LOC UNUSABLE 140DEG CW 220DEG BEYOND 18NM&lt;br&gt;
BLW 4000FT MSL 2605161400-2605171400&lt;/p&gt;

&lt;p&gt;For a Part 107 certified pilot, that's readable. For a logistics coordinator managing 40 simultaneous drone deliveries, that's a 500ms context-switch that shouldn't be their problem.&lt;/p&gt;

&lt;p&gt;I'd tried using LLMs for this translation layer before. The issues were consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination on technical specifics&lt;/strong&gt;: Models would confidently describe restrictions that didn't exist, or miss ones that did&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No reasoning about implications&lt;/strong&gt;: Summarizing the NOTAM is one thing; telling the operator "this means your planned corridor at 350ft AGL through this zone is blocked from 2pm to 4pm" requires multi-step spatial reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context window constraints&lt;/strong&gt;: A real airspace brief for a complex metro area involves dozens of NOTAMs, the full Part 107 ruleset, current TFR data, and the specific flight plan. Earlier models hit walls fast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size vs. capability tradeoff&lt;/strong&gt;: Running a 70B model locally on field hardware wasn't viable. The lighter models weren't smart enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemma 4 changes this calculus in specific, measurable ways.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Model I Chose and Why It's the Right Tool
&lt;/h2&gt;

&lt;p&gt;I'm using the &lt;strong&gt;Gemma 4 26B MoE&lt;/strong&gt; (Mixture-of-Experts) variant, and the choice was deliberate.&lt;/p&gt;

&lt;p&gt;The 26B MoE activates only ~4 billion parameters per inference pass — meaning it has the intelligence of a much larger model at a fraction of the compute cost. On a single RTX 4090 (24GB VRAM), it runs comfortably at full precision. For an application that needs to serve dozens of operators simultaneously with sub-3-second response times, this matters enormously.&lt;/p&gt;

&lt;p&gt;Here's the tradeoff table I worked through:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Why I considered it&lt;/th&gt;
&lt;th&gt;Why I didn't pick it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E4B&lt;/td&gt;
&lt;td&gt;Runs on mobile / edge hardware&lt;/td&gt;
&lt;td&gt;Not enough reasoning depth for multi-NOTAM conflict detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B Dense&lt;/td&gt;
&lt;td&gt;Maximum capability, 256K context&lt;/td&gt;
&lt;td&gt;20GB+ VRAM, overkill for most queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 26B MoE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Near-31B quality, 12GB VRAM, 256K context&lt;/td&gt;
&lt;td&gt;— This is the one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 256K context window on the MoE is the other unlock. I can now dump an entire airspace brief — full NOTAM batch, the relevant CFR sections, the operator's specific flight plan, their certification history — into a single prompt and get coherent, cross-referenced reasoning back. No chunking, no retrieval gymnastics, no lost context.&lt;/p&gt;

&lt;p&gt;And then there's the benchmark that actually made me sit up: &lt;strong&gt;86.4% on τ2-bench Retail (multi-step agentic tool use)&lt;/strong&gt;. Up from 6.6% on Gemma 3. That's not an incremental improvement. That's a model that can now actually do things in a multi-step workflow, not just describe what should be done.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: SkyBrief
&lt;/h2&gt;

&lt;p&gt;I built a tool called &lt;strong&gt;SkyBrief&lt;/strong&gt; — a Gemma 4-powered airspace intelligence assistant for drone operators.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://skybrief-nine.vercel.app" rel="noopener noreferrer"&gt;https://skybrief-nine.vercel.app&lt;/a&gt;&lt;br&gt;
🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ashishjsharda/skybrief" rel="noopener noreferrer"&gt;https://github.com/ashishjsharda/skybrief&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Operator drops in a NOTAM (PDF, text, or image of a sectional chart)&lt;/li&gt;
&lt;li&gt;Or types a natural language query: &lt;em&gt;"I need to fly at 400ft AGL over downtown Phoenix tomorrow at 10am — what do I need?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Gemma 4 26B MoE processes it with a system prompt encoding FAA Part 107 rules, airspace classification knowledge, and LAANC requirements&lt;/li&gt;
&lt;li&gt;Returns a structured response: plain-English summary, specific conflicts identified, and an operator-ready compliance checklist&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key architectural decision: &lt;strong&gt;Gemma 4 does the reasoning, not the retrieval&lt;/strong&gt;. We keep a separate data layer for live NOTAM feeds and TFR data. Gemma's job is to take that data and turn it into something a human operator can act on in under 10 seconds.&lt;/p&gt;

&lt;p&gt;The thinking mode is not a gimmick here. For complex multi-NOTAM scenarios, watching the model reason step-by-step through airspace geometry produces outputs that are verifiably correct in ways that single-pass models can't match.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Here's the thing I keep coming back to.&lt;/p&gt;

&lt;p&gt;We've built an enormous amount of programmatic logic to parse airspace rules and flag conflicts. That logic is valuable — it's precise, auditable, and fast. But it's also brittle. New airspace designations, new waiver categories, new operator certification types — every change requires engineering cycles to handle.&lt;/p&gt;

&lt;p&gt;What Gemma 4 represents is a system that understands the underlying regulatory intent, not just the current ruleset. When I prompt it with a scenario that doesn't fit neatly into our existing rule categories, it reasons through it from first principles.&lt;/p&gt;

&lt;p&gt;The Apache 2.0 license matters here too. In aviation and airspace management, you cannot run mission-critical reasoning through an external API with opaque terms of service. The ability to deploy Gemma 4 on-premises, air-gapped if necessary, with full control over the model weights, is not a nice-to-have. It's table stakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemma 4 still hallucinates on specifics.&lt;/strong&gt; NOTAM coordinates, frequency values, altitude limits — anything that requires exact numerical precision should be validated against ground truth data, not trusted from model output alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 26B MoE still needs real hardware.&lt;/strong&gt; An RTX 4090 or equivalent isn't cheap. For edge deployment or mobile scenarios, the E4B is a better choice — but you'll trade reasoning depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thinking mode adds latency.&lt;/strong&gt; For use cases where operators need answers in under two seconds, gate thinking mode based on query complexity signals.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Local AI Actually Means for High-Stakes Domains
&lt;/h2&gt;

&lt;p&gt;The drone industry is a useful lens because the stakes are concrete. A bad airspace brief doesn't just inconvenience someone — it creates collision risk.&lt;/p&gt;

&lt;p&gt;Gemma 4 running locally changes what's possible. Not in a "LLMs can solve everything" way. But in a specific, bounded way: the gap between what rule-based systems can handle and what a human expert would reason through is meaningfully narrowed by a 26B model that fits on hardware we actually own.&lt;/p&gt;

&lt;p&gt;That gap is where the interesting engineering work lives right now.&lt;/p&gt;

&lt;p&gt;If you're building in aviation, healthcare, legal, defense, or any domain where data sovereignty and auditability aren't optional — this is the moment to start taking local inference seriously.&lt;/p&gt;

&lt;p&gt;I built SkyBrief in an evening session. The harder work is integrating it thoughtfully into systems where the outputs matter. That's the engineering problem worth spending time on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ashish is Head of Engineering in the drone UTM space and an O'Reilly author and LinkedIn Learning instructor. Find him on DEV and Medium.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>SkyBrief: I Built a Drone Airspace Intelligence Tool with Gemma 4 26B MoE</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 24 May 2026 13:27:38 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/skybrief-i-built-a-drone-airspace-intelligence-tool-with-gemma-4-26b-moe-4k79</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/skybrief-i-built-a-drone-airspace-intelligence-tool-with-gemma-4-26b-moe-4k79</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;SkyBrief is an AI-powered drone airspace intelligence assistant built with Gemma 4 26B MoE. Drop in a NOTAM, airspace map, or type a plain-English query — Gemma 4 reasons over it and returns a structured brief with conflict detection and a compliance checklist.&lt;/p&gt;

&lt;p&gt;The problem it solves: NOTAMs (Notices to Air Missions) look like this:&lt;/p&gt;

&lt;p&gt;!PHX 05/014 PHX NAV ILS RWY 8 LOC UNUSABLE 140DEG CW 220DEG BEYOND 18NM&lt;br&gt;
BLW 4000FT MSL 2605161400-2605171400&lt;/p&gt;

&lt;p&gt;A Part 107 pilot can parse that. A logistics coordinator managing 40 drone deliveries cannot. SkyBrief uses Gemma 4's reasoning to translate complex airspace data into plain-English operator briefs — with specific conflicts flagged and a ready-to-use compliance checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://skybrief-nine.vercel.app" rel="noopener noreferrer"&gt;https://skybrief-nine.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try these queries to see all three status states:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🟢 CLEAR:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to fly a drone in a rural area, Class G airspace, 200ft AGL, no airports within 10 miles, clear weather, daytime. Part 107 certified pilot. Is this flight legal?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;🟡 CAUTION:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need to fly at 350ft AGL, 4 miles from a Class D airport, winds 15kt gusting 22kt, daytime, Part 107 certified. What do I need to proceed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;🔴 RESTRICTED:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to fly over a stadium during a live NFL game, 500ft AGL, no waiver, downtown Chicago near O'Hare Class B airspace. What are my restrictions?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ashishjsharda/skybrief" rel="noopener noreferrer"&gt;https://github.com/ashishjsharda/skybrief&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 4 26B MoE via OpenRouter (free tier)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Vercel Edge Functions — API key stays server-side, never exposed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vanilla HTML/CSS/JS — zero dependencies, zero build step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;I chose &lt;strong&gt;Gemma 4 26B MoE&lt;/strong&gt; specifically — and the choice was deliberate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Why I considered it&lt;/th&gt;
&lt;th&gt;Why I didn't pick it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E4B&lt;/td&gt;
&lt;td&gt;Runs on mobile / edge hardware&lt;/td&gt;
&lt;td&gt;Not enough reasoning depth for multi-NOTAM conflict detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B Dense&lt;/td&gt;
&lt;td&gt;Maximum capability, 256K context&lt;/td&gt;
&lt;td&gt;20GB+ VRAM, overkill for most queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 26B MoE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Near-31B quality, 12GB VRAM, 256K context&lt;/td&gt;
&lt;td&gt;— This is the one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why MoE wins for this use case:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only ~4B parameters active per inference — near-31B reasoning at a fraction of compute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;256K context window&lt;/strong&gt; fits entire NOTAM batches in one prompt — no chunking, no lost context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;86.4% on agentic tool-use benchmarks&lt;/strong&gt; (up from 6.6% on Gemma 3) — handles multi-constraint airspace reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache 2.0&lt;/strong&gt; — can be deployed on-premises, air-gapped for aviation compliance environments where external APIs aren't allowed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How Gemma 4 powers SkyBrief:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Operator types a query or uploads a NOTAM/airspace map&lt;/li&gt;
&lt;li&gt;Vercel Edge Function sends it to Gemma 4 26B MoE with a FAA Part 107 system prompt&lt;/li&gt;
&lt;li&gt;Gemma reasons through airspace rules, identifies conflicts, generates checklist&lt;/li&gt;
&lt;li&gt;Returns structured JSON: status (CLEAR/CAUTION/RESTRICTED), summary, conflicts, checklist&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gemma 4's thinking mode is the key unlock here. For complex multi-NOTAM scenarios, it reasons step-by-step through airspace geometry — producing verifiably correct outputs that single-pass models couldn't match. Previous models hallucinated on technical specifics or lacked reasoning depth for multi-constraint airspace scenarios. Gemma 4 26B MoE solved both.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>7 Things Java Devs Still Get Wrong in 2026 (Java 25/26 Edition)</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 17 May 2026 12:03:04 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/7-things-java-devs-still-get-wrong-in-2026-java-2526-edition-58hm</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/7-things-java-devs-still-get-wrong-in-2026-java-2526-edition-58hm</guid>
      <description>&lt;p&gt;&lt;em&gt;Java 25 is the new LTS. Java 26 just dropped. Are you still writing Java 8?&lt;/em&gt;&lt;/p&gt;

&lt;h2&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%2Fmk74480xhkwhv2t7f1ll.png" alt="Modern Java has moved on. The question is: has your codebase?" width="800" height="533"&gt;
&lt;/h2&gt;

&lt;p&gt;Java has changed more in the last four years than in the previous decade. Records, sealed classes, pattern matching, virtual threads, scoped values, structured concurrency — the language is practically unrecognizable from Java 8. And yet, the codebase you're shipping today? Probably still full of habits from 2015.&lt;/p&gt;

&lt;p&gt;Here are 7 things devs consistently get wrong when writing modern Java — and what to do instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Using &lt;code&gt;Optional&lt;/code&gt; as a return type... everywhere
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Optional&lt;/code&gt; was a great idea. It's also one of the most abused APIs in modern Java.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What devs do:&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="nc"&gt;Optional&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="nf"&gt;getUserName&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;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="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofNullable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;User:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getName&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;Then they chain &lt;code&gt;.get()&lt;/code&gt; on it anyway:&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;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getUserName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&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;// ← defeats the entire purpose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or worse, they use &lt;code&gt;Optional&lt;/code&gt; as a field type, a method parameter, or inside a collection — all of which the &lt;code&gt;Optional&lt;/code&gt; Javadoc explicitly warns against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Optional&lt;/code&gt; belongs at the &lt;em&gt;boundary&lt;/em&gt; — as a return type when absence is meaningful and you want to force the caller to handle it.&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;// Good — forces the caller to handle absence&lt;/span&gt;
&lt;span class="n"&gt;getUserName&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="na"&gt;ifPresentOrElse&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;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;render&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="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Anonymous"&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're writing &lt;code&gt;isPresent()&lt;/code&gt; followed by &lt;code&gt;get()&lt;/code&gt;, you've completely bypassed the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Ignoring Records for simple data carriers
&lt;/h2&gt;

&lt;p&gt;How many DTOs and POJOs in your codebase look like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDTO&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;String&lt;/span&gt; &lt;span class="n"&gt;name&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;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getName&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;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getEmail&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;email&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;equals&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;o&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="nd"&gt;@Override&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;hashCode&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="nd"&gt;@Override&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's ~30 lines. Here it is as a Record (finalized in Java 16, standard since Java 21+):&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Immutable. Has &lt;code&gt;equals&lt;/code&gt;, &lt;code&gt;hashCode&lt;/code&gt;, &lt;code&gt;toString&lt;/code&gt;, and accessor methods — all generated. It also signals &lt;em&gt;intent&lt;/em&gt;: this is a data carrier, not a service object.&lt;/p&gt;

&lt;p&gt;In 2026, if you're not using Records for your DTOs, you're writing boilerplate the compiler already knows how to write for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Writing &lt;code&gt;instanceof&lt;/code&gt; checks like it's 2010
&lt;/h2&gt;

&lt;p&gt;Before Java 16, pattern matching for &lt;code&gt;instanceof&lt;/code&gt; didn't exist. That excuse expired four years ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Old way:&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Circle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Circle&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Circle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;shape&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;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;radius&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;Modern 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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Circle&lt;/span&gt; &lt;span class="n"&gt;c&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;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;radius&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;And with Java 25/26, pattern matching now extends to &lt;strong&gt;all primitive types&lt;/strong&gt; too (JEP 530, fourth preview in Java 26). You can match on &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt; directly in switch expressions:&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="kt"&gt;double&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;switch&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="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&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;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseDouble&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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;Combine sealed classes with switch expressions and the compiler enforces exhaustiveness at compile time. Add a new subclass and forget to handle it? Won't compile. That's the kind of safety you used to need a framework for.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Using &lt;code&gt;ThreadLocal&lt;/code&gt; in a world that has &lt;code&gt;ScopedValues&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This one costs you in subtle ways that only surface under load.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ThreadLocal&lt;/code&gt; has been the go-to for passing context (user sessions, request IDs, tracing info) down a call stack for 20+ years. The problem: its lifetime is unclear, it leaks in thread pools, and it's hazardous with virtual threads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java 25 finalized &lt;code&gt;ScopedValue&lt;/code&gt; (JEP 487)&lt;/strong&gt; — a proper replacement:&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;// Old: ThreadLocal — mutable, leaks, unclear lifetime&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="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CURRENT_USER&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;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="no"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;processRequest&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="no"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// easy to forget&lt;/span&gt;

&lt;span class="c1"&gt;// New: ScopedValue — immutable, bounded, safe with virtual threads&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="nc"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CURRENT_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScopedValue&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="nc"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;run&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;processRequest&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// automatically cleaned up when the lambda exits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ScopedValue&lt;/code&gt; is immutable within its scope, its lifetime is strictly bounded by the runtime, and it composes cleanly with structured concurrency and virtual threads. If you're still reaching for &lt;code&gt;ThreadLocal&lt;/code&gt; in new code in 2026, stop.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Spawning platform threads for everything concurrent
&lt;/h2&gt;

&lt;p&gt;This one's a performance trap that's hard to see until you're at scale.&lt;/p&gt;

&lt;p&gt;Classic pattern: you have 10,000 concurrent HTTP requests on a &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;. Each platform thread = ~1MB of memory. At 10K concurrent requests you're looking at ~10GB of heap just for threads, and throughput flatlines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual threads (finalized Java 21, now standard) change the equation:&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="c1"&gt;// Old: fixed-size thread pool, expensive context switches&lt;/span&gt;
&lt;span class="nc"&gt;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newFixedThreadPool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// New: virtual threads — JVM manages scheduling&lt;/span&gt;
&lt;span class="nc"&gt;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Virtual threads are cheap. Millions of them on a modest machine. You write normal blocking code; the JVM handles cooperative scheduling. No callback hell, no reactive chains, no &lt;code&gt;.flatMap()&lt;/code&gt; soup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 2026 add-on:&lt;/strong&gt; pair virtual threads with &lt;strong&gt;Structured Concurrency&lt;/strong&gt; (still in preview as of Java 26, JEP 525). It makes concurrent subtasks feel like sequential code and guarantees clean shutdown:&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;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StructuredTaskScope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&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;user&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fork&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;fetchUser&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fork&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;fetchOrder&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="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;order&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one subtask fails, the scope cancels the others automatically. No manual &lt;code&gt;Future&lt;/code&gt; wrangling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The caveat:&lt;/strong&gt; virtual threads shine on I/O-bound workloads. CPU-bound tasks still benefit from platform threads. If you're using &lt;code&gt;synchronized&lt;/code&gt; blocks heavily, use &lt;code&gt;ReentrantLock&lt;/code&gt; instead to avoid pinning.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Skipping &lt;code&gt;SequencedCollection&lt;/code&gt; for first/last element access
&lt;/h2&gt;

&lt;p&gt;This one flies under the radar. Java 21 introduced &lt;code&gt;SequencedCollection&lt;/code&gt;, &lt;code&gt;SequencedSet&lt;/code&gt;, and &lt;code&gt;SequencedMap&lt;/code&gt; — interfaces that unify first/last element access across collection types. It's been standard for two LTS releases and devs still aren't using it.&lt;/p&gt;

&lt;p&gt;Before this, getting the last element of a list was embarrassing:&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;list&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;list&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="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// verbose, brittle&lt;/span&gt;
&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;LinkedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getLast&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// only works if you know the concrete type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFirst&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// works on any SequencedCollection&lt;/span&gt;
&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLast&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reversed&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// returns a reversed view, no copying&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, safe, no index math. Update your collection-handling code.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Treating &lt;code&gt;Stream&lt;/code&gt; as a one-size-fits-all tool
&lt;/h2&gt;

&lt;p&gt;Streams are elegant. They're also overused to the point where devs write convoluted pipelines for things a simple &lt;code&gt;for&lt;/code&gt; loop would express more clearly.&lt;/p&gt;

&lt;p&gt;This is fine:&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAge&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Comparator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;comparing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;User:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getName&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="nl"&gt;User:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// .toList() is cleaner than .collect(Collectors.toList()) — use it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is too much:&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;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;total&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="n"&gt;list&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="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&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;i&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isActive&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapToObj&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&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;i&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Integer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is clearer:&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;total&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;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;list&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;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isActive&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&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;Streams are ideal for: transforming and filtering collections declaratively, parallel processing with &lt;code&gt;parallelStream()&lt;/code&gt;, and composing pipelines that read naturally. They're not ideal for: stateful operations, index-aware iteration, or anything where a &lt;code&gt;for&lt;/code&gt; loop communicates intent more clearly.&lt;/p&gt;

&lt;p&gt;Also: stop writing &lt;code&gt;.collect(Collectors.toList())&lt;/code&gt;. It's been &lt;code&gt;.toList()&lt;/code&gt; since Java 16.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Java 25 is the LTS you should be targeting in 2026. Java 26 dropped March 17th with more pattern matching improvements, lazy constants, and Valhalla value classes inching toward production. The ecosystem has moved.&lt;/p&gt;

&lt;p&gt;Here's the migration priority list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Old habit&lt;/th&gt;
&lt;th&gt;Modern replacement&lt;/th&gt;
&lt;th&gt;Since&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boilerplate DTO&lt;/td&gt;
&lt;td&gt;&lt;code&gt;record&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;instanceof&lt;/code&gt; cast&lt;/td&gt;
&lt;td&gt;Pattern matching&lt;/td&gt;
&lt;td&gt;Java 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ThreadLocal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ScopedValue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java 25 (final)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread pool for I/O&lt;/td&gt;
&lt;td&gt;Virtual threads&lt;/td&gt;
&lt;td&gt;Java 21 (final)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual futures&lt;/td&gt;
&lt;td&gt;Structured Concurrency&lt;/td&gt;
&lt;td&gt;Java 25 (preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index math on collections&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SequencedCollection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java 21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.collect(Collectors.toList())&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.toList()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java 16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fastest path to cleaner Java in 2026 isn't a new framework. It's actually using what the language already ships.&lt;/p&gt;

&lt;p&gt;What's the Java 25/26 feature that's changed your code the most? Drop it in the comments — genuinely curious what the community is shipping.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow me for more on modern Java, Rust, and building production AI systems on the JVM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: &lt;code&gt;#java&lt;/code&gt; &lt;code&gt;#programming&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#todayilearned&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>webdev</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>I Built an AI Agent in Java (No Python. No Hype. Just Code.)</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 03 May 2026 11:35:15 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/i-built-an-ai-agent-in-java-no-python-no-hype-just-code-1p2l</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/i-built-an-ai-agent-in-java-no-python-no-hype-just-code-1p2l</guid>
      <description>&lt;p&gt;&lt;em&gt;Everyone told me I needed Python for AI. I didn't listen. Here's what happened.&lt;/em&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%2Fzmva57onuqob4q9xsc3m.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%2Fzmva57onuqob4q9xsc3m.png" alt="AI Agent in Java " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me be real with you.&lt;/p&gt;

&lt;p&gt;Every time I say "I'm building an AI agent," people assume I'm wrist-deep in Python virtual environments, pip dependencies, and a LangChain tutorial from 2023. And when I say "in Java?" — I get the look. You know the one.&lt;/p&gt;

&lt;p&gt;So I built it anyway.&lt;/p&gt;

&lt;p&gt;A fully functional AI agent. With tool use. With RAG. With MCP. Running on the JVM. Spring Boot 3.5, zero Python sidecars, no regrets.&lt;/p&gt;

&lt;p&gt;Here's exactly how I did it — with real code you can run today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Java for AI? (The Short Version)
&lt;/h2&gt;

&lt;p&gt;The honest answer: because that's where my backend already lives.&lt;/p&gt;

&lt;p&gt;Python is great for training models. But if your production system is Java — and for most of us in enterprise land, it is — then integrating AI means either maintaining a Python sidecar service, doing HTTP hops between runtimes, or just... not doing it cleanly.&lt;/p&gt;

&lt;p&gt;Spring AI changes that equation completely. As of &lt;strong&gt;Spring AI 1.1.5&lt;/strong&gt; (released April 27, 2026 — yes, last week), you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;ChatClient&lt;/code&gt; that works with 20+ AI model providers (OpenAI including GPT-5, Anthropic Claude, Ollama, Google Gemini, Azure OpenAI, and more)&lt;/li&gt;
&lt;li&gt;A full &lt;strong&gt;Advisors API&lt;/strong&gt; for RAG and conversation memory&lt;/li&gt;
&lt;li&gt;Native &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; support — servers and clients, annotation-driven&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caching&lt;/strong&gt; for Anthropic and AWS Bedrock (up to 90% cost reduction)&lt;/li&gt;
&lt;li&gt;Switching AI providers = one line in &lt;code&gt;application.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Heads up for the curious:&lt;/strong&gt; Spring AI 2.0 is in active milestone with GA targeting late May 2026. It moves to a Spring Boot 4.0 baseline and adds full null-safety via JSpecify. The 1.1.x line is stable and production-ready right now — that's what we're using here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's build something real.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Research Agent&lt;/strong&gt; that can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept a natural language question from an HTTP endpoint&lt;/li&gt;
&lt;li&gt;Search a knowledge base (RAG) for relevant context&lt;/li&gt;
&lt;li&gt;Call external tools via MCP (a news search tool)&lt;/li&gt;
&lt;li&gt;Return a grounded, intelligent answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No LangChain. No Python. Just Java 21 + Spring Boot 3.5 + Spring AI 1.1.5.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Dependencies (pom.xml)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-bom&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.1.5&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;pom&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;import&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencyManagement&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Spring Boot Web --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Spring AI - Anthropic Claude --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-anthropic&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Spring AI - MCP Client --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-mcp-client&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Spring AI - Vector Store (in-memory for this demo) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-vector-store-simple&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Swap &lt;code&gt;spring-ai-starter-model-anthropic&lt;/code&gt; for &lt;code&gt;spring-ai-starter-model-openai&lt;/code&gt; and change one config line. The &lt;code&gt;ChatClient&lt;/code&gt; code stays identical. That's the point.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1 — Configure the Model and MCP
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;anthropic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ANTHROPIC_API_KEY}&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-20250514&lt;/span&gt;
    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;research-agent-client&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;tool-callbacks-enabled&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;streamable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;connections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;news-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="s"&gt;http://localhost:8090&lt;/span&gt;  &lt;span class="c1"&gt;# Our MCP tool server (built below)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for config. Spring Boot auto-configuration handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Build the MCP Tool Server
&lt;/h2&gt;

&lt;p&gt;This is the agent's "hands." A separate Spring Boot app that exposes tools the AI can call.&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;@SpringBootApplication&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;NewsToolServer&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;SpringApplication&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="nc"&gt;NewsToolServer&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;args&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;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsSearchTool&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Search for recent news articles on a given topic. "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                         &lt;span class="s"&gt;"Returns a list of relevant headlines and summaries."&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;NewsResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;searchNews&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"The topic or keyword to search for"&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;topic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Max number of results to return"&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;maxResults&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// In production: call a real news API (NewsAPI, Bing, etc.)&lt;/span&gt;
        &lt;span class="c1"&gt;// For this demo, we return simulated results&lt;/span&gt;
        &lt;span class="k"&gt;return&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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NewsResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Java Sees Surge in AI Workloads in 2026"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"Enterprise teams are increasingly choosing Java for AI production systems..."&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;NewsResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Spring AI 1.1.5 Ships with Security Fixes and OpenAI SDK Integration"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"JVM developers can now build AI agents without Python sidecars..."&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxResults&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="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;NewsResult&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;headline&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;summary&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 yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml for the tool server&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8090&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;news-server&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring Boot auto-configuration discovers the &lt;code&gt;@Tool&lt;/code&gt; annotation and registers &lt;code&gt;searchNews&lt;/code&gt; as an MCP-exposed tool over Streamable HTTP. &lt;strong&gt;Zero boilerplate registration.&lt;/strong&gt; Annotate a method, you're done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Wire the Agent (The Good Part)
&lt;/h2&gt;

&lt;p&gt;Back in our main application. This is where it all comes together.&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;AgentConfig&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;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;ToolCallbackProvider&lt;/span&gt; &lt;span class="n"&gt;mcpTools&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;VectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&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;builder&lt;/span&gt;
            &lt;span class="c1"&gt;// Give the agent access to MCP tools (news search, etc.)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultToolCallbacks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// RAG advisor — searches vector store before every prompt&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultAdvisors&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;QuestionAnswerAdvisor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectorStore&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;MessageChatMemoryAdvisor&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;InMemoryChatMemory&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// System prompt defines agent behavior&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultSystem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                You are a research assistant with access to real-time news tools
                and a curated knowledge base. Always cite your sources.
                When answering questions, first check your knowledge base,
                then use available tools to find current information.
                Be concise, accurate, and honest about what you don't know.
                """&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;The &lt;code&gt;ToolCallbackProvider&lt;/code&gt; is the key abstraction here. Spring AI auto-populates it with every tool discovered from connected MCP servers, sends the tool schemas to the LLM with the initial prompt, executes tool calls when the model decides it needs them, and feeds results back — all transparently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — The REST Endpoint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/agent"&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;ResearchAgentController&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;ChatClient&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ResearchAgentController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;researchAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ask"&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;AgentResponse&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"session-1"&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;sessionId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;advisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advisor&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;advisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;MessageChatMemoryAdvisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CHAT_MEMORY_CONVERSATION_ID_KEY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;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;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Seed the knowledge base&lt;/span&gt;
    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/knowledge"&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;addKnowledge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;VectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;vectorStore&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;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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&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="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Map&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;"source"&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="na"&gt;source&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;AgentResponse&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;question&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;answer&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;KnowledgeRequest&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;content&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;source&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;
  
  
  Let's See It Work
&lt;/h2&gt;

&lt;p&gt;Start the news tool server on port 8090, then the main agent on port 8080.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seed some knowledge:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/agent/knowledge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "Spring AI 1.1.5 supports 20+ AI model providers including OpenAI with GPT-5, Anthropic Claude, Google Gemini, Ollama, and Azure OpenAI. It provides a unified ChatClient API.",
    "source": "Spring AI release notes"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ask the agent:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:8080/agent/ask?question=What+AI+models+does+Spring+AI+support+and+what+is+happening+with+Java+AI+adoption?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What happens under the hood:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;QuestionAnswerAdvisor&lt;/code&gt; searches the vector store and injects relevant context&lt;/li&gt;
&lt;li&gt;The model sees the question + retrieved docs&lt;/li&gt;
&lt;li&gt;The model decides it wants current news → calls &lt;code&gt;searchNews("Java AI adoption", 3)&lt;/code&gt; via MCP&lt;/li&gt;
&lt;li&gt;Spring AI executes the tool call, feeds results back to the model&lt;/li&gt;
&lt;li&gt;The model synthesizes a final grounded answer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MessageChatMemoryAdvisor&lt;/code&gt; stores this exchange for the next turn in the session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of that — tool use, RAG, memory — in a single &lt;code&gt;.prompt().user().call().content()&lt;/code&gt; chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part That Gets Me
&lt;/h2&gt;

&lt;p&gt;Here's the full &lt;code&gt;ChatClient&lt;/code&gt; call:&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;String&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;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;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines. The advisors and tool routing handle everything else.&lt;/p&gt;

&lt;p&gt;Compare this to the equivalent Python + LangChain setup: agent chains, callback handlers, tool registrations, memory buffer classes, and a &lt;code&gt;requirements.txt&lt;/code&gt; that breaks every two months.&lt;/p&gt;




&lt;h2&gt;
  
  
  Switching AI Providers
&lt;/h2&gt;

&lt;p&gt;Want to swap Claude for GPT-4o? Two changes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pom.xml:&lt;/strong&gt; Replace &lt;code&gt;spring-ai-starter-model-anthropic&lt;/code&gt; with &lt;code&gt;spring-ai-starter-model-openai&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;application.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;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;openai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;ChatClient&lt;/code&gt; code? &lt;strong&gt;Unchanged.&lt;/strong&gt; Not a single line.&lt;/p&gt;

&lt;p&gt;Want to run locally with zero API costs? Swap in Ollama:&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;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llama3.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same code. Different model. That's the portable API promise, and it actually delivers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Unlocks
&lt;/h2&gt;

&lt;p&gt;Once you're here, the next steps are straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent systems&lt;/strong&gt; — wire multiple &lt;code&gt;ChatClient&lt;/code&gt; beans with different system prompts and tool sets, let them coordinate via MCP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses&lt;/strong&gt; — swap &lt;code&gt;.call().content()&lt;/code&gt; for &lt;code&gt;.stream().content()&lt;/code&gt; and pipe to an SSE endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caching&lt;/strong&gt; — Spring AI 1.1.5 ships Anthropic prompt caching support out of the box, cutting costs up to 90% for repeated context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production observability&lt;/strong&gt; — native Micrometer integration gives you token counts, latency, and model call traces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraalVM native&lt;/strong&gt; — compile the whole agent to a native binary for sub-100ms startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring AI 2.0&lt;/strong&gt; — if you're starting fresh and don't mind milestones, 2.0 adds Spring Boot 4.0 baseline, full JSpecify null-safety, and Jackson 3. GA is targeted for late May 2026.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Python is great for training models. For building AI agents on top of them — integrating with your existing infrastructure, your databases, your APIs, your Spring services — Java is not second class anymore.&lt;/p&gt;

&lt;p&gt;Spring AI 1.1.5 isn't a wrapper around Python tooling. It's a native, production-grade AI framework built for the JVM by the same team that built Spring Boot. The &lt;code&gt;ChatClient&lt;/code&gt; API is clean. The MCP integration is real. The Advisors chain is genuinely powerful.&lt;/p&gt;

&lt;p&gt;You don't need a Python sidecar. You don't need to learn LangChain. You don't need to maintain two runtimes.&lt;/p&gt;

&lt;p&gt;You just need Spring Boot and an API key.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Spring Boot 3.5, Spring AI 1.1.5, Java 21, Claude Sonnet via Anthropic API. Published May 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop a comment if you want a Part 2 — streaming responses, multi-agent coordination, or GraalVM native compilation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Spring Boot 4.0 Is Here — And It's a Big Deal 🍃</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sun, 26 Apr 2026 14:33:14 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/spring-boot-40-is-here-and-its-a-big-deal-2lh</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/spring-boot-40-is-here-and-its-a-big-deal-2lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Spring Boot 4.0 is the biggest release since the 3.0 jakarta migration. Virtual threads are now the default. GraalVM AOT is first-class. The modular starter redesign cleans up years of dependency bloat. Spring Framework 7 brings built-in API versioning. If you're still on 3.x, your migration window is closing.&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%2Fztyxhp3tbrtngsfmn0tv.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%2Fztyxhp3tbrtngsfmn0tv.png" alt="Spring Boot" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Version Are We Even On?
&lt;/h2&gt;

&lt;p&gt;As of April 2026, &lt;strong&gt;Spring Boot 4.0.6&lt;/strong&gt; is the current stable release, and &lt;strong&gt;4.1.0-RC1&lt;/strong&gt; just dropped on the Spring blog. Spring Boot 3.5 — the last 3.x line — loses open-source support on &lt;strong&gt;June 30, 2026&lt;/strong&gt;, so the upgrade train is no longer optional.&lt;/p&gt;

&lt;p&gt;The version ladder that matters right now:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Release&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Support Until&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spring Boot 4.0.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Current stable&lt;/td&gt;
&lt;td&gt;Dec 31, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot 4.1.0-RC1&lt;/td&gt;
&lt;td&gt;🔬 Release candidate&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot 3.5.x&lt;/td&gt;
&lt;td&gt;⚠️ Maintenance only&lt;/td&gt;
&lt;td&gt;Jun 30, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot 3.4.x&lt;/td&gt;
&lt;td&gt;❌ EOL&lt;/td&gt;
&lt;td&gt;Dec 31, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Big 5 Things That Actually Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 🧵 Virtual Threads Are Now the Default
&lt;/h3&gt;

&lt;p&gt;This is the headliner. In Spring Boot 3.2, virtual threads (Project Loom) were opt-in behind a flag. In 4.0, &lt;strong&gt;they're the recommended threading model on Java 21+&lt;/strong&gt; — the auto-configuration detects your JVM version and adjusts thread pools accordingly. No code changes required.&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;// Your existing blocking JDBC code? It just… works better now.&lt;/span&gt;
&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="kt"&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="c1"&gt;// Blocking call — but cheap on a virtual thread 🎉&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderRepository&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="na"&gt;orElseThrow&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;Tomcat and Jetty handlers use virtual threads by default on Java 21+. &lt;code&gt;@Async&lt;/code&gt; tasks and scheduled operations follow the same model. The "virtual threads vs reactive" debate has largely been settled: for most standard I/O-heavy Spring MVC apps, you get reactive-scale throughput with imperative code simplicity.&lt;/p&gt;

&lt;p&gt;One important caveat: virtual threads excel at I/O-bound workloads. CPU-intensive apps won't benefit as much, and you'll want to audit thread-local storage patterns since virtual threads can create millions of instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 🏗️ Modular Starter Redesign
&lt;/h3&gt;

&lt;p&gt;The starters got a significant overhaul. Previously, pulling in &lt;code&gt;spring-boot-starter-web&lt;/code&gt; dragged along a lot of optional integrations you might never use. The new design separates optional integrations (Micrometer, OpenTelemetry, specific persistence technologies) into dedicated modules.&lt;/p&gt;

&lt;p&gt;The practical wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster builds&lt;/strong&gt; — AOT processing has less unnecessary metadata to churn through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner dependency trees&lt;/strong&gt; — only what you declare, not what Spring assumed you wanted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better GraalVM native image support&lt;/strong&gt; — fewer reflection hints needed for unused paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use starter POMs, your &lt;code&gt;pom.xml&lt;/code&gt; or &lt;code&gt;build.gradle&lt;/code&gt; doesn't change. The modularization happens underneath. For teams doing native compilation, the build time improvements are immediately noticeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🔢 Built-in API Versioning (Spring Framework 7)
&lt;/h3&gt;

&lt;p&gt;Spring Framework 7 ships under the hood, and one of its headline features is &lt;strong&gt;first-class API versioning support&lt;/strong&gt; — no more custom interceptors or messy URL path gymnastics.&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;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/greet"&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;GreetingController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&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;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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;greetV1&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;Map&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;"message"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&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;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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;greetV2&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;Map&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;"message"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello!"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"timestamp"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&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;Spring handles the routing. You declare the version; the framework dispatches it. For anyone building long-lived public APIs with multiple client generations, this alone is worth the upgrade conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 📊 Unified Observability (OpenTelemetry + Micrometer)
&lt;/h3&gt;

&lt;p&gt;The observability story in 4.0 is significantly cleaner. Configuration that previously required manual bean registration is now auto-configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single property namespace&lt;/strong&gt; for configuring OTLP exporters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in trace-to-log correlation&lt;/strong&gt; out of the box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Grafana and Prometheus setup&lt;/strong&gt; through new starter dependencies&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-boot-starter-actuator&lt;/code&gt; now includes observability defaults
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# One block to rule your telemetry&lt;/span&gt;
&lt;span class="na"&gt;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://otel-collector:4318/v1/traces&lt;/span&gt;
  &lt;span class="na"&gt;tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sampling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;probability&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've been manually wiring &lt;code&gt;ObservationRegistry&lt;/code&gt; beans or fighting with Micrometer context propagation, the new defaults will feel like a breath of fresh air.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 🚀 GraalVM AOT Goes Mainstream
&lt;/h3&gt;

&lt;p&gt;Native image support has been in Spring since 3.0, but it always felt like a second-class citizen — reflection configuration was finicky, build times were brutal, and dynamic features broke in unexpected ways. Boot 4.0 changes that posture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Spring Boot 4.0 provides first-class framework support for both AOT compilation and virtual threads, eliminating the integration friction that plagued earlier adoption attempts."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AOT engine now handles most common reflection patterns automatically. Third-party libraries increasingly ship GraalVM metadata out of the box. If you tried native images on 3.x and hit walls, it's worth retesting on 4.0.&lt;/p&gt;

&lt;p&gt;Real-world benchmark from the Project Leyden team: &lt;strong&gt;Spring PetClinic starts 41% faster&lt;/strong&gt; with AOT caching enabled in JDK 26. That's not GraalVM native territory, but it's approaching it — without rewriting code or giving up the JIT.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Got Removed
&lt;/h2&gt;

&lt;p&gt;Know before you upgrade:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Removed&lt;/th&gt;
&lt;th&gt;Replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SecurityFilterChain&lt;/code&gt; beans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undertow embedded server&lt;/td&gt;
&lt;td&gt;Tomcat or Jetty (Undertow ≠ Servlet 6.1 compatible)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.config.use-legacy-processing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A — removed entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;RestTemplate&lt;/code&gt; auto-configuration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RestClient&lt;/code&gt; (imperative) or &lt;code&gt;@HttpExchange&lt;/code&gt; (declarative)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Retry as separate dependency&lt;/td&gt;
&lt;td&gt;Built-in resilience via &lt;code&gt;@Retry&lt;/code&gt; and &lt;code&gt;@ConcurrencyLimit&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;RestTemplate&lt;/code&gt; one trips people up. It's not gone — but its auto-configuration is now &lt;strong&gt;opt-in&lt;/strong&gt;. If you have &lt;code&gt;RestTemplate&lt;/code&gt; beans autowired across your codebase, plan to migrate to &lt;code&gt;RestClient&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;// Old way (still works, but no longer auto-configured)&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;RestTemplate&lt;/span&gt; &lt;span class="n"&gt;restTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// New way — RestClient (Spring Framework 6.1+)&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;RestClient&lt;/span&gt; &lt;span class="n"&gt;restClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RestClient&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;fetchData&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;url&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;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="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="n"&gt;url&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;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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Migration Path
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From Spring Boot 3.4/3.5 → 4.0:&lt;/strong&gt; Smooth if you've been cleaning up deprecation warnings. No namespace migration (that was the 2.x → 3.x jump). Main watchpoints: Spring Security 7 new defaults, removed deprecated APIs, and JSpecify null-safety annotations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From Spring Boot 2.x → 4.0:&lt;/strong&gt; Don't do this in one shot. Migrate 2.x → 3.5 first, stabilize, then go to 4.0. You'll hit the &lt;code&gt;javax.*&lt;/code&gt; to &lt;code&gt;jakarta.*&lt;/code&gt; namespace change regardless, and stacking that on top of the 4.0 changes is pain you don't need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Update your parent POM --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-parent&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;4.0.6&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Minimum Java version:&lt;/strong&gt; 17. &lt;strong&gt;Recommended:&lt;/strong&gt; 21 or 25 to actually benefit from virtual threads and the newer JVM features.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's on the Horizon: Spring Boot 4.1
&lt;/h2&gt;

&lt;p&gt;The 4.1.0-RC1 is already out with some interesting additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AMQP 1.0 spec support&lt;/strong&gt; — auto-configuration of &lt;code&gt;AmqpConnectionFactory&lt;/code&gt; and &lt;code&gt;AmqpClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Batch + MongoDB&lt;/strong&gt; — new &lt;code&gt;spring-boot-batch-data-mongo&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring AI deeper integration&lt;/strong&gt; — the ecosystem is converging on AI-native patterns fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spring AI deserves its own post, honestly — but the short version is that Java developers in 2026 have roughly the same toolkit for agentic AI development as Python developers, without needing to stand up separate services.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Spring Boot 4.0 isn't just an incremental patch release — it's a reset. Virtual threads make high-throughput I/O trivial. The modular redesign clears out years of accumulated dependency weight. Native images have finally crossed from "experimental" to "production-viable." And Spring Framework 7's API versioning fixes one of the oldest annoyances in REST API design.&lt;/p&gt;

&lt;p&gt;If you're on 3.5.x, your support window closes June 30. If you're on 3.4.x or earlier, you're already in EOL territory. The upgrade path is genuinely smoother than 2.x → 3.x was — no namespace migration, just deprecation cleanup.&lt;/p&gt;

&lt;p&gt;There's no good reason to wait.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading! Drop a 💬 if you've already migrated to 4.0 — curious what pain points you hit and what surprised you positively.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Redis 8.6 is here: faster, smarter, and built for AI-era workloads</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Mon, 20 Apr 2026 16:14:05 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/redis-86-is-here-faster-smarter-and-built-for-ai-era-workloads-p8f</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/redis-86-is-here-faster-smarter-and-built-for-ai-era-workloads-p8f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvy4qk7hgkvngvcqdief3.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%2Fvy4qk7hgkvngvcqdief3.png" alt="Redis 8.6" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Redis 8.6 just went GA in open source. It brings more than 5× throughput over Redis 7.2, up to 35% lower latency on sorted sets, massive memory savings on hashes and sorted sets, production-grade Streams guarantees, hot key detection, smarter eviction, TLS auto-auth, and native NaN support in time series. Here's everything that matters.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;01 — The numbers: performance at a glance&lt;/li&gt;
&lt;li&gt;02 — Streams: at-most-once production guarantee&lt;/li&gt;
&lt;li&gt;03 — Hot key detection with HOTKEYS&lt;/li&gt;
&lt;li&gt;04 — New eviction policies for AI workloads&lt;/li&gt;
&lt;li&gt;05 — TLS auto-auth via certificate CN&lt;/li&gt;
&lt;li&gt;06 — NaN support in time series&lt;/li&gt;
&lt;li&gt;07 — How to upgrade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  01 — The numbers: performance at a glance
&lt;/h2&gt;

&lt;p&gt;Redis 8.6 isn't a quiet patch release. This is a generational performance leap. On a single node using 16 cores (ARM Graviton4), Redis 8.6 hits &lt;strong&gt;3.5M ops/sec&lt;/strong&gt; at pipeline size 16. That's more than &lt;strong&gt;5× the throughput of Redis 7.2&lt;/strong&gt; on the same hardware.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Improvement vs Redis 8.4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sorted set commands latency&lt;/td&gt;
&lt;td&gt;↓ 35%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET latency (short strings)&lt;/td&gt;
&lt;td&gt;↓ 15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List commands latency&lt;/td&gt;
&lt;td&gt;↓ 11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash commands latency&lt;/td&gt;
&lt;td&gt;↓ 7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sorted set memory footprint&lt;/td&gt;
&lt;td&gt;↓ 30.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash memory footprint&lt;/td&gt;
&lt;td&gt;↓ 16.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector insertion (VADD)&lt;/td&gt;
&lt;td&gt;↑ 43%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector query performance&lt;/td&gt;
&lt;td&gt;↑ 58%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Memory footprint improvements are just as notable: hashes shrink by up to &lt;strong&gt;16.7%&lt;/strong&gt;, sorted sets by up to &lt;strong&gt;30.5%&lt;/strong&gt;. That's real money saved on cloud infra.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Benchmark context:&lt;/strong&gt; Single node, 16 cores on an m8g.24xlarge (ARM Graviton4), 11 io-threads, pipeline size 16, 2000 clients, 1M keys, 1K string values, 1:10 SET:GET ratio.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  02 — Streams: at-most-once production guarantee
&lt;/h2&gt;

&lt;p&gt;Streams got a huge reliability upgrade. Redis 8.6 introduces &lt;strong&gt;idempotent production&lt;/strong&gt; — a guarantee that a message will be added to a stream &lt;em&gt;at most once&lt;/em&gt;, even when producers crash and retry.&lt;/p&gt;

&lt;p&gt;This addresses a very real pain point in event-driven systems. When a producer sends a message, gets a network timeout, and retries — without this guarantee, you end up with duplicate events. Now Redis handles deduplication natively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;XADD mystream IDEM producer-id seq-123 &lt;span class="k"&gt;*&lt;/span&gt; field value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combined with the 8.2 multi-group acknowledgment and 8.4's pending message recovery, Streams in Redis 8.6 are genuinely production-ready for financial systems, media pipelines, and AI inference queues.&lt;/p&gt;




&lt;h2&gt;
  
  
  03 — Hot key detection with HOTKEYS
&lt;/h2&gt;

&lt;p&gt;One of the trickiest production scaling problems — hot keys — now has a native Redis command. The new &lt;code&gt;HOTKEYS&lt;/code&gt; command lets you detect keys consuming disproportionate CPU or network resources in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HOTKEYS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This completes a trifecta of hot spot tooling across three releases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Release&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 8.2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLUSTER SLOT-STATS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detect hot slots across the cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 8.4&lt;/td&gt;
&lt;td&gt;Atomic Slot Migration&lt;/td&gt;
&lt;td&gt;Zero-downtime rebalancing of hot slots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 8.6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HOTKEYS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pinpoint individual hot keys causing load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together, these give you end-to-end cluster observability — from slot to individual key — without reaching for external profiling tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  04 — New eviction policies for AI workloads
&lt;/h2&gt;

&lt;p&gt;Redis has long had LRU (least recently used) eviction. Now 8.6 introduces two new eviction policies based on &lt;strong&gt;least recently modified (LRM)&lt;/strong&gt; semantics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allkeys-lrm&lt;/code&gt; — evict the least recently &lt;em&gt;modified&lt;/em&gt; key across all keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volatile-lrm&lt;/code&gt; — same, but only among keys with a TTL set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why does this matter for AI?&lt;/strong&gt; In AI inference pipelines, a key might be read frequently (LRU keeps it hot) but rarely updated. LRM lets you evict stale model outputs or embedding caches that are no longer being written to — exactly the behavior you want when managing inference result caches at scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Tip:&lt;/strong&gt; If you're on &lt;code&gt;allkeys-lru&lt;/code&gt; and running AI inference caches, consider evaluating &lt;code&gt;allkeys-lrm&lt;/code&gt; — you may see better cache hit rates and more predictable memory behavior.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  05 — TLS auto-auth via certificate CN
&lt;/h2&gt;

&lt;p&gt;This one is genuinely slick. Prior to 8.6, even with mutual TLS configured, clients still had to send an &lt;code&gt;AUTH&lt;/code&gt; command at the application layer. Starting with 8.6, Redis can &lt;strong&gt;automatically authenticate mTLS clients&lt;/strong&gt; based on the certificate's Common Name (CN).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;tls&lt;/span&gt;-&lt;span class="n"&gt;auth&lt;/span&gt;-&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;tls&lt;/span&gt;-&lt;span class="n"&gt;client&lt;/span&gt;-&lt;span class="n"&gt;cert&lt;/span&gt;-&lt;span class="n"&gt;auth&lt;/span&gt;-&lt;span class="n"&gt;user&lt;/span&gt;-&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="n"&gt;acl&lt;/span&gt;-&lt;span class="n"&gt;user&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certificate possession becomes the sole credential. No more managing ACL passwords in your app config alongside TLS certs — the cert &lt;em&gt;is&lt;/em&gt; the auth. This is huge for service mesh deployments and zero-trust architectures.&lt;/p&gt;




&lt;h2&gt;
  
  
  06 — NaN support in time series
&lt;/h2&gt;

&lt;p&gt;Redis time series now supports &lt;strong&gt;NaN (Not a Number) values&lt;/strong&gt; as a first-class way to represent missing measurements. Previously, teams used sentinel values like &lt;code&gt;-1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; which would corrupt aggregations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;TS.ADD sensor:temp 1714500000 NaN
TS.ADD sensor:temp 1714503600 23.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can mark a data point as "unavailable at collection time" and fill it in later, without breaking range queries or aggregations. Critical for telemetry pipelines, observability stacks, and financial data collection where gaps are expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  07 — How to upgrade
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Available in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;td&gt;✅ GA&lt;/td&gt;
&lt;td&gt;Redis Open Source now; Cloud/Software soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;✅ GA&lt;/td&gt;
&lt;td&gt;Redis Cloud (Essentials &amp;amp; Pro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;td&gt;🔶 M02 preview&lt;/td&gt;
&lt;td&gt;Docker Hub (not for production)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pull Redis 8.6 via Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull redis:8.6
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; redis86 &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 redis:8.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or grab it from &lt;a href="https://redis.io/downloads" rel="noopener noreferrer"&gt;redis.io/downloads&lt;/a&gt;. The release is backward compatible — a standard rolling upgrade applies for most configurations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📦 If you're on Redis Cloud, 8.4 is already rolling out to Essentials and Pro. Redis 8.6 will follow into Cloud and Redis Software in upcoming releases. No action needed on managed offerings.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Redis 8.6 is one of the more meaningful releases in recent memory. The throughput and latency numbers are genuinely exciting, and the operational improvements — &lt;code&gt;HOTKEYS&lt;/code&gt;, mTLS auto-auth, LRM eviction, Streams idempotency — close gaps that have been friction points for production teams for years. If you're running Redis in anger, this one is worth the upgrade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://redis.io/blog/announcing-redis-86-performance-improvements-streams/" rel="noopener noreferrer"&gt;Redis 8.6 announcement blog&lt;/a&gt; · &lt;a href="https://redis.io/blog/whats-new-in-two-march-2026-edition/" rel="noopener noreferrer"&gt;What's new in two — March 2026&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>Java's Dark Corners: Things That Will Break You If You're Not Paying Attention</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Mon, 13 Apr 2026 02:59:09 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/javas-dark-corners-things-that-will-break-you-if-youre-not-paying-attention-5c6b</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/javas-dark-corners-things-that-will-break-you-if-youre-not-paying-attention-5c6b</guid>
      <description>&lt;p&gt;&lt;em&gt;You've been writing Java for years. You think you know it. You don't.&lt;/em&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%2Fff9u5vfuav3h7gbzgwe3.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%2Fff9u5vfuav3h7gbzgwe3.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a special kind of confidence that comes after a few years of Java. You've survived generics. You've stared down the garbage collector. You've argued about whether checked exceptions were a mistake (they were). You feel safe.&lt;/p&gt;

&lt;p&gt;You're not safe.&lt;/p&gt;

&lt;p&gt;Java has corners. Dark ones. Places the docs gloss over, the tutorials skip, and the interviewer never asks about — until production is on fire at 2am and you're staring at a stack trace that makes no sense.&lt;/p&gt;

&lt;p&gt;Let's go there.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;String.format&lt;/code&gt; is not your friend under load
&lt;/h2&gt;

&lt;p&gt;You've used it ten thousand times. It's readable. It's civilized. It's also one of the sneakiest performance traps in the JDK.&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;// Looks innocent. Isn't.&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&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;"Processing order %s for user %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what most devs miss: &lt;code&gt;String.format&lt;/code&gt; uses &lt;code&gt;java.util.Formatter&lt;/code&gt; internally, which allocates a new &lt;code&gt;Formatter&lt;/code&gt; object, a &lt;code&gt;StringBuilder&lt;/code&gt;, and runs a regex-based parser on your format string — &lt;strong&gt;every single call&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a hot path, this is brutal. Use &lt;code&gt;String.valueOf()&lt;/code&gt; + concatenation (the compiler optimizes it to &lt;code&gt;StringBuilder&lt;/code&gt;), or better yet — if you're on Java 15+ — text blocks and &lt;code&gt;formatted()&lt;/code&gt; with proper caching.&lt;/p&gt;

&lt;p&gt;And if you're still calling &lt;code&gt;String.format&lt;/code&gt; inside a log statement without checking &lt;code&gt;isDebugEnabled()&lt;/code&gt; first? Fix that before you close this tab.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;HashMap&lt;/code&gt; has a trapdoor called hash collision amplification
&lt;/h2&gt;

&lt;p&gt;You know &lt;code&gt;HashMap&lt;/code&gt; degrades from O(1) to O(n) under hash collisions. Fine, textbook stuff. But here's what the textbook doesn't tell you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Java 8&lt;/strong&gt;, a pathological attacker (or a badly-designed key class) could reduce a &lt;code&gt;HashMap&lt;/code&gt; to O(n) by simply engineering collisions. This was a real CVE. JSON parsing libraries that used &lt;code&gt;HashMap&lt;/code&gt; for keys were vulnerable to hash-flooding DoS attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After Java 8&lt;/strong&gt;, the JDK added treeification — once a bucket hits 8 entries, it converts to a red-black tree, bringing worst case back to O(log n). But here's the trap:&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;record&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&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="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;hashCode&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;x&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "looks fine"&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;(0,1)&lt;/code&gt; and &lt;code&gt;(1,0)&lt;/code&gt; and &lt;code&gt;(0,0)&lt;/code&gt; and... you see the problem. XOR is symmetric in ways that cluster your keys. In a spatial data structure with millions of points, you've just handed yourself a treeified nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; If you override &lt;code&gt;hashCode&lt;/code&gt;, use &lt;code&gt;Objects.hash()&lt;/code&gt; or multiply by a prime. No cleverness.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;Optional&lt;/code&gt; was never meant for what you're using it for
&lt;/h2&gt;

&lt;p&gt;The original intent of &lt;code&gt;Optional&lt;/code&gt;, per Brian Goetz himself: a &lt;strong&gt;return type&lt;/strong&gt; for methods that might not have a value. That's it.&lt;/p&gt;

&lt;p&gt;Not a field type. Not a method parameter. Not a collection element.&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;// Every senior dev has seen this and died inside&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;User&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;Optional&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;middleName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// NO&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;Optional&lt;/code&gt; is not serializable. It doesn't play well with Jackson without custom configuration. It adds allocation overhead. And it signals to anyone reading your code that you fundamentally misread the javadoc.&lt;/p&gt;

&lt;p&gt;The real dark corner: &lt;strong&gt;&lt;code&gt;Optional.get()&lt;/code&gt; is worse than a null check&lt;/strong&gt;. If you're calling &lt;code&gt;get()&lt;/code&gt; without &lt;code&gt;isPresent()&lt;/code&gt;, you've replaced a &lt;code&gt;NullPointerException&lt;/code&gt; with a &lt;code&gt;NoSuchElementException&lt;/code&gt; and gained nothing — except you now have to unwrap it manually and the compiler won't warn you.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;orElse&lt;/code&gt;, &lt;code&gt;orElseGet&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;. Or switch to Kotlin where nullability is a first-class citizen and this whole conversation disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;code&gt;==&lt;/code&gt; on Integer will burn you exactly at ±127
&lt;/h2&gt;

&lt;p&gt;This one is ancient. Senior devs know it. And senior devs still get burned by it, because it only fails at a specific threshold.&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;Integer&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;127&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="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&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="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JVM caches &lt;code&gt;Integer&lt;/code&gt; instances for values between -128 and 127. Outside that range, autoboxing creates new objects. &lt;code&gt;==&lt;/code&gt; compares references. You get &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason this is dangerous for seniors specifically: you know to use &lt;code&gt;.equals()&lt;/code&gt; for strings. You've internalized it. But Integer feels primitive enough that &lt;code&gt;==&lt;/code&gt; feels right. It isn't.&lt;/p&gt;

&lt;p&gt;Where this actually kills you in production: comparison logic in service layers that works fine in unit tests (with small IDs or status codes) and fails mysteriously with real data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always use &lt;code&gt;.equals()&lt;/code&gt;. Always.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. &lt;code&gt;ConcurrentHashMap&lt;/code&gt; doesn't make your compound operations atomic
&lt;/h2&gt;

&lt;p&gt;This is the one that humbles people.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt; is thread-safe. Individual operations like &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt; are atomic. But the moment you do two operations in sequence, you've lost the guarantee.&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;ConcurrentHashMap&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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counts&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;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// This is a race condition, full stop&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&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;key&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Between &lt;code&gt;containsKey&lt;/code&gt; and &lt;code&gt;put&lt;/code&gt;, another thread can — and will — do the same thing. You now have the wrong count.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;compute&lt;/code&gt;, &lt;code&gt;computeIfAbsent&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;. These are atomic. The JDK gave you the tools in Java 8. Use them.&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Integer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// atomic, correct, one line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dark corner within the dark corner: &lt;strong&gt;&lt;code&gt;putIfAbsent&lt;/code&gt; is also not the fix&lt;/strong&gt; for the check-then-act pattern unless you are genuinely only initializing. Know which operation matches your intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The &lt;code&gt;finally&lt;/code&gt; block can silently swallow your exception
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"the real problem"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"the distraction"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What gets thrown? The second one. The original exception is &lt;strong&gt;gone&lt;/strong&gt;. No chaining, no suppressed list, nothing. Your real error evaporated.&lt;/p&gt;

&lt;p&gt;This is especially vicious with &lt;code&gt;return&lt;/code&gt; in a &lt;code&gt;finally&lt;/code&gt; block:&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;try&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;computeSomething&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// throws&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// silently wins&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exception is swallowed. The caller gets &lt;code&gt;fallback()&lt;/code&gt;'s return value and has no idea anything went wrong. This pattern is a production debugging nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Never &lt;code&gt;return&lt;/code&gt; or &lt;code&gt;throw&lt;/code&gt; from &lt;code&gt;finally&lt;/code&gt;. If you must catch in &lt;code&gt;finally&lt;/code&gt;, use &lt;code&gt;addSuppressed()&lt;/code&gt; to preserve the original exception.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Virtual threads will expose your hidden blocking code (and that's the point)
&lt;/h2&gt;

&lt;p&gt;Java 21's virtual threads are genuinely transformative — but they're a lie detector, not a magic wand.&lt;/p&gt;

&lt;p&gt;The pitch: mount thousands of virtual threads on a handful of OS threads. When a virtual thread blocks (I/O, sleep, etc.), it unmounts, freeing the carrier thread. Massive throughput with minimal resources.&lt;/p&gt;

&lt;p&gt;The trap: &lt;strong&gt;synchronized blocks pin the virtual thread to its carrier&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;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;someBlockingIoCall&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// pins the carrier thread. now you've lost the benefit.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With virtual threads, &lt;code&gt;synchronized&lt;/code&gt; + blocking I/O is worse than before — you've created the illusion of concurrency while secretly serializing on your carrier pool.&lt;/p&gt;

&lt;p&gt;The fix is to use &lt;code&gt;ReentrantLock&lt;/code&gt; instead of &lt;code&gt;synchronized&lt;/code&gt;. But that requires you to actually audit your code — including every library you've pulled in. If your JDBC driver uses &lt;code&gt;synchronized&lt;/code&gt; internally, you're blocked.&lt;/p&gt;

&lt;p&gt;Virtual threads don't make your code concurrent. They make your blocking code &lt;em&gt;cheaper&lt;/em&gt; — as long as your blocking code cooperates.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern underneath all of this
&lt;/h2&gt;

&lt;p&gt;Notice what all seven of these have in common: they're not bugs. They're &lt;strong&gt;correct behavior that surprises you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Java does exactly what the spec says. The problem is that the spec doesn't always match your mental model, and the gap between those two things is where production incidents live.&lt;/p&gt;

&lt;p&gt;The senior move isn't memorizing this list. It's developing a healthy paranoia: &lt;em&gt;when this seems too simple, what am I missing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Write it down. Teach it to your team. And next time you're 100% sure about how something works in Java — go read the source.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your favorite Java dark corner? Drop it in the comments. I'll add the worst ones to a follow-up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>backend</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Java's Dark Corners: Things That Will Break You If You're Not Paying Attention</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Mon, 13 Apr 2026 02:59:09 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/javas-dark-corners-things-that-will-break-you-if-youre-not-paying-attention-4kk</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/javas-dark-corners-things-that-will-break-you-if-youre-not-paying-attention-4kk</guid>
      <description>&lt;p&gt;&lt;em&gt;You've been writing Java for years. You think you know it. You don't.&lt;/em&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%2Fff9u5vfuav3h7gbzgwe3.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%2Fff9u5vfuav3h7gbzgwe3.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a special kind of confidence that comes after a few years of Java. You've survived generics. You've stared down the garbage collector. You've argued about whether checked exceptions were a mistake (they were). You feel safe.&lt;/p&gt;

&lt;p&gt;You're not safe.&lt;/p&gt;

&lt;p&gt;Java has corners. Dark ones. Places the docs gloss over, the tutorials skip, and the interviewer never asks about — until production is on fire at 2am and you're staring at a stack trace that makes no sense.&lt;/p&gt;

&lt;p&gt;Let's go there.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;String.format&lt;/code&gt; is not your friend under load
&lt;/h2&gt;

&lt;p&gt;You've used it ten thousand times. It's readable. It's civilized. It's also one of the sneakiest performance traps in the JDK.&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;// Looks innocent. Isn't.&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&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;"Processing order %s for user %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what most devs miss: &lt;code&gt;String.format&lt;/code&gt; uses &lt;code&gt;java.util.Formatter&lt;/code&gt; internally, which allocates a new &lt;code&gt;Formatter&lt;/code&gt; object, a &lt;code&gt;StringBuilder&lt;/code&gt;, and runs a regex-based parser on your format string — &lt;strong&gt;every single call&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a hot path, this is brutal. Use &lt;code&gt;String.valueOf()&lt;/code&gt; + concatenation (the compiler optimizes it to &lt;code&gt;StringBuilder&lt;/code&gt;), or better yet — if you're on Java 15+ — text blocks and &lt;code&gt;formatted()&lt;/code&gt; with proper caching.&lt;/p&gt;

&lt;p&gt;And if you're still calling &lt;code&gt;String.format&lt;/code&gt; inside a log statement without checking &lt;code&gt;isDebugEnabled()&lt;/code&gt; first? Fix that before you close this tab.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;HashMap&lt;/code&gt; has a trapdoor called hash collision amplification
&lt;/h2&gt;

&lt;p&gt;You know &lt;code&gt;HashMap&lt;/code&gt; degrades from O(1) to O(n) under hash collisions. Fine, textbook stuff. But here's what the textbook doesn't tell you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Java 8&lt;/strong&gt;, a pathological attacker (or a badly-designed key class) could reduce a &lt;code&gt;HashMap&lt;/code&gt; to O(n) by simply engineering collisions. This was a real CVE. JSON parsing libraries that used &lt;code&gt;HashMap&lt;/code&gt; for keys were vulnerable to hash-flooding DoS attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After Java 8&lt;/strong&gt;, the JDK added treeification — once a bucket hits 8 entries, it converts to a red-black tree, bringing worst case back to O(log n). But here's the trap:&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;record&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&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="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;hashCode&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;x&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "looks fine"&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;(0,1)&lt;/code&gt; and &lt;code&gt;(1,0)&lt;/code&gt; and &lt;code&gt;(0,0)&lt;/code&gt; and... you see the problem. XOR is symmetric in ways that cluster your keys. In a spatial data structure with millions of points, you've just handed yourself a treeified nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; If you override &lt;code&gt;hashCode&lt;/code&gt;, use &lt;code&gt;Objects.hash()&lt;/code&gt; or multiply by a prime. No cleverness.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;Optional&lt;/code&gt; was never meant for what you're using it for
&lt;/h2&gt;

&lt;p&gt;The original intent of &lt;code&gt;Optional&lt;/code&gt;, per Brian Goetz himself: a &lt;strong&gt;return type&lt;/strong&gt; for methods that might not have a value. That's it.&lt;/p&gt;

&lt;p&gt;Not a field type. Not a method parameter. Not a collection element.&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;// Every senior dev has seen this and died inside&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;User&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;Optional&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;middleName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// NO&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;Optional&lt;/code&gt; is not serializable. It doesn't play well with Jackson without custom configuration. It adds allocation overhead. And it signals to anyone reading your code that you fundamentally misread the javadoc.&lt;/p&gt;

&lt;p&gt;The real dark corner: &lt;strong&gt;&lt;code&gt;Optional.get()&lt;/code&gt; is worse than a null check&lt;/strong&gt;. If you're calling &lt;code&gt;get()&lt;/code&gt; without &lt;code&gt;isPresent()&lt;/code&gt;, you've replaced a &lt;code&gt;NullPointerException&lt;/code&gt; with a &lt;code&gt;NoSuchElementException&lt;/code&gt; and gained nothing — except you now have to unwrap it manually and the compiler won't warn you.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;orElse&lt;/code&gt;, &lt;code&gt;orElseGet&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;. Or switch to Kotlin where nullability is a first-class citizen and this whole conversation disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;code&gt;==&lt;/code&gt; on Integer will burn you exactly at ±127
&lt;/h2&gt;

&lt;p&gt;This one is ancient. Senior devs know it. And senior devs still get burned by it, because it only fails at a specific threshold.&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;Integer&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;127&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="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&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="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JVM caches &lt;code&gt;Integer&lt;/code&gt; instances for values between -128 and 127. Outside that range, autoboxing creates new objects. &lt;code&gt;==&lt;/code&gt; compares references. You get &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason this is dangerous for seniors specifically: you know to use &lt;code&gt;.equals()&lt;/code&gt; for strings. You've internalized it. But Integer feels primitive enough that &lt;code&gt;==&lt;/code&gt; feels right. It isn't.&lt;/p&gt;

&lt;p&gt;Where this actually kills you in production: comparison logic in service layers that works fine in unit tests (with small IDs or status codes) and fails mysteriously with real data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always use &lt;code&gt;.equals()&lt;/code&gt;. Always.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. &lt;code&gt;ConcurrentHashMap&lt;/code&gt; doesn't make your compound operations atomic
&lt;/h2&gt;

&lt;p&gt;This is the one that humbles people.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt; is thread-safe. Individual operations like &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt; are atomic. But the moment you do two operations in sequence, you've lost the guarantee.&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;ConcurrentHashMap&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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counts&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;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// This is a race condition, full stop&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&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;key&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Between &lt;code&gt;containsKey&lt;/code&gt; and &lt;code&gt;put&lt;/code&gt;, another thread can — and will — do the same thing. You now have the wrong count.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;compute&lt;/code&gt;, &lt;code&gt;computeIfAbsent&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;. These are atomic. The JDK gave you the tools in Java 8. Use them.&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;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Integer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// atomic, correct, one line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dark corner within the dark corner: &lt;strong&gt;&lt;code&gt;putIfAbsent&lt;/code&gt; is also not the fix&lt;/strong&gt; for the check-then-act pattern unless you are genuinely only initializing. Know which operation matches your intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The &lt;code&gt;finally&lt;/code&gt; block can silently swallow your exception
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"the real problem"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"the distraction"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What gets thrown? The second one. The original exception is &lt;strong&gt;gone&lt;/strong&gt;. No chaining, no suppressed list, nothing. Your real error evaporated.&lt;/p&gt;

&lt;p&gt;This is especially vicious with &lt;code&gt;return&lt;/code&gt; in a &lt;code&gt;finally&lt;/code&gt; block:&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;try&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;computeSomething&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// throws&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// silently wins&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exception is swallowed. The caller gets &lt;code&gt;fallback()&lt;/code&gt;'s return value and has no idea anything went wrong. This pattern is a production debugging nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Never &lt;code&gt;return&lt;/code&gt; or &lt;code&gt;throw&lt;/code&gt; from &lt;code&gt;finally&lt;/code&gt;. If you must catch in &lt;code&gt;finally&lt;/code&gt;, use &lt;code&gt;addSuppressed()&lt;/code&gt; to preserve the original exception.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Virtual threads will expose your hidden blocking code (and that's the point)
&lt;/h2&gt;

&lt;p&gt;Java 21's virtual threads are genuinely transformative — but they're a lie detector, not a magic wand.&lt;/p&gt;

&lt;p&gt;The pitch: mount thousands of virtual threads on a handful of OS threads. When a virtual thread blocks (I/O, sleep, etc.), it unmounts, freeing the carrier thread. Massive throughput with minimal resources.&lt;/p&gt;

&lt;p&gt;The trap: &lt;strong&gt;synchronized blocks pin the virtual thread to its carrier&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;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;someBlockingIoCall&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// pins the carrier thread. now you've lost the benefit.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With virtual threads, &lt;code&gt;synchronized&lt;/code&gt; + blocking I/O is worse than before — you've created the illusion of concurrency while secretly serializing on your carrier pool.&lt;/p&gt;

&lt;p&gt;The fix is to use &lt;code&gt;ReentrantLock&lt;/code&gt; instead of &lt;code&gt;synchronized&lt;/code&gt;. But that requires you to actually audit your code — including every library you've pulled in. If your JDBC driver uses &lt;code&gt;synchronized&lt;/code&gt; internally, you're blocked.&lt;/p&gt;

&lt;p&gt;Virtual threads don't make your code concurrent. They make your blocking code &lt;em&gt;cheaper&lt;/em&gt; — as long as your blocking code cooperates.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern underneath all of this
&lt;/h2&gt;

&lt;p&gt;Notice what all seven of these have in common: they're not bugs. They're &lt;strong&gt;correct behavior that surprises you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Java does exactly what the spec says. The problem is that the spec doesn't always match your mental model, and the gap between those two things is where production incidents live.&lt;/p&gt;

&lt;p&gt;The senior move isn't memorizing this list. It's developing a healthy paranoia: &lt;em&gt;when this seems too simple, what am I missing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Write it down. Teach it to your team. And next time you're 100% sure about how something works in Java — go read the source.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your favorite Java dark corner? Drop it in the comments. I'll add the worst ones to a follow-up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>backend</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Java 25 LTS: The Game-Changer You've Been Waiting For</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Thu, 22 Jan 2026 00:39:07 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/java-25-lts-the-game-changer-youve-been-waiting-for-jkf</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/java-25-lts-the-game-changer-youve-been-waiting-for-jkf</guid>
      <description>&lt;p&gt;The Java ecosystem is a vibrant and ever-evolving landscape. With a predictable release cadence, developers consistently receive powerful new features that enhance productivity, performance, and the overall developer experience. Among these releases, the &lt;strong&gt;Long-Term Support (LTS)&lt;/strong&gt; versions stand out, offering extended stability and a commitment to long-term maintenance.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;Java 25 LTS&lt;/strong&gt;, officially released on &lt;strong&gt;September 16, 2025&lt;/strong&gt;. This isn't just another update; it's a monumental release packed with features that simplify the language for newcomers, slash boilerplate code for seasoned veterans, and supercharge performance, especially for emerging AI and data-intensive workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Flexible Constructor Bodies (JEP 513)
&lt;/h2&gt;

&lt;p&gt;For decades, Java constructors had a strict rule: the call to &lt;code&gt;super()&lt;/code&gt; or &lt;code&gt;this()&lt;/code&gt; had to be the &lt;em&gt;very first statement&lt;/em&gt;. This led to awkward workarounds when you needed to validate parameters &lt;em&gt;before&lt;/em&gt; delegating to a superclass.&lt;/p&gt;

&lt;p&gt;Java 25 liberates us from this constraint. You can now place logic before the &lt;code&gt;super()&lt;/code&gt; call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Example:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;After Java 25:&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;class&lt;/span&gt; &lt;span class="nc"&gt;PremiumUser&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PremiumUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Validation logic BEFORE super()!&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;discount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;discount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&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;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Discount must be between 0 and 1."&lt;/span&gt;&lt;span class="o"&gt;);&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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&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;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discount&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;
  
  
  2. Compact Source Files &amp;amp; Instance Main Methods (JEP 512)
&lt;/h2&gt;

&lt;p&gt;Java 25 dramatically lowers the barrier to entry by introducing Instance Main Methods. You no longer need &lt;code&gt;public static void main(String[] args)&lt;/code&gt; or even an explicit class declaration for simple scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// No class declaration, no 'static', no 'String[] args'&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="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;"Hello, Java 25!"&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;"No more boilerplate for simple scripts."&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;
  
  
  3. Module Import Declarations (JEP 511)
&lt;/h2&gt;

&lt;p&gt;Instead of managing a wall of import statements, JEP 511 allows you to import an entire module with a single line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Imports all exported packages from java.base (List, Map, etc.)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;module&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;base&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;MyUtility&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;names&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;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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;scores&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;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Scoped Values (JEP 506)
&lt;/h2&gt;

&lt;p&gt;ThreadLocal has long been the standard for sharing data across a thread, but it can be memory-intensive. Scoped Values offer a safer, more performant alternative, especially for Virtual Threads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ScopedValue&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="no"&gt;CONTEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScopedValue&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONTEXT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"request-id-123"&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;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;processTask&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processTask&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Safely retrieve the scoped value&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;"Handling: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;CONTEXT&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Under-the-Hood: Performance &amp;amp; AI
&lt;/h2&gt;

&lt;p&gt;While the syntax changes are exciting, the JVM itself received massive upgrades:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compact Object Headers (JEP 519)&lt;/strong&gt;: Reduces object header size to 8 bytes. This significantly lowers the heap memory footprint for large-scale applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vector API (10th Incubator)&lt;/strong&gt;: Continues to optimize high-performance math operations, critical for modern AI and Machine Learning workloads in Java.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generational Shenandoah (JEP 521)&lt;/strong&gt;: Optimizes the Shenandoah GC for better handling of short-lived objects, reducing latency further.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Standardized Security: KDF API (JEP 510)
&lt;/h2&gt;

&lt;p&gt;Java 25 introduces a native Key Derivation Function (KDF) API. This makes it easier to implement modern, secure password hashing (like Argon2) without relying on heavy third-party libraries.&lt;/p&gt;




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

&lt;p&gt;Java 25 LTS is a landmark release. It bridges the gap between the power of an enterprise language and the ease of use found in modern scripting languages. Whether you are building AI-driven infrastructure or a simple microservice, Java 25 has something for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are you most excited about in Java 25? Share your thoughts in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>lts</category>
    </item>
    <item>
      <title>Stop Prompting, Start Orchestrating: Why 2026 is the Year the "Chatbot" Dies 💀</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Mon, 05 Jan 2026 02:27:14 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/stop-prompting-start-orchestrating-why-2026-is-the-year-the-chatbot-dies-4699</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/stop-prompting-start-orchestrating-why-2026-is-the-year-the-chatbot-dies-4699</guid>
      <description>&lt;p&gt;CES 2026 is showing us physical robots, but the real revolution is happening in our terminals. Here’s why your &lt;strong&gt;"Prompt Engineering"&lt;/strong&gt; certificate is already gathering dust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Chatbot" is so 2024
&lt;/h2&gt;

&lt;p&gt;Remember when we were all impressed that an LLM could write a Python script? We’d copy the prompt, paste the code, fix the bug, and repeat. &lt;/p&gt;

&lt;p&gt;Fast forward to this week at &lt;strong&gt;CES 2026&lt;/strong&gt;. We’re seeing robots like LG’s &lt;strong&gt;CLOiD&lt;/strong&gt; folding laundry and navigating homes autonomously. They aren’t waiting for a "prompt" for every finger movement; they are &lt;strong&gt;orchestrating a goal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As developers, we are hitting the same wall. Typing into a chat box is a bottleneck. In 2026, if you’re still just "chatting" with AI, you’re manually doing the work an agent should be doing for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Prompts to "Agentic Workflows"
&lt;/h2&gt;

&lt;p&gt;The shift we’re seeing right now is from &lt;strong&gt;stateless prompts&lt;/strong&gt; to &lt;strong&gt;stateful orchestration.&lt;/strong&gt; * &lt;strong&gt;Old Way:&lt;/strong&gt; &lt;em&gt;"Hey AI, write a test for this function."&lt;/em&gt; (One-shot, manual intervention).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2026 Way:&lt;/strong&gt; An agentic loop using &lt;strong&gt;LangGraph&lt;/strong&gt; or &lt;strong&gt;CrewAI&lt;/strong&gt; that detects a new commit, identifies missing tests, writes them, runs the suite, fixes its own hallucinations, and only pings you on Slack when the PR is ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We aren't "prompt engineers" anymore. We are &lt;strong&gt;Agent Architects.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3 Pillars of the 2026 Stack
&lt;/h2&gt;

&lt;p&gt;If you want to stay relevant this year, stop obsessing over which model is better (Gemini vs. GPT-x is a commodity now). Start obsessing over these three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration (The Brain):&lt;/strong&gt; Moving beyond simple chains. We’re talking about "Manager Agents" that delegate to "Specialist Agents."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool-Use (The Hands):&lt;/strong&gt; Teaching your AI to actually use your internal APIs, navigate your Jira, or even check your AWS billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory (The History):&lt;/strong&gt; Giving agents a persistent state so they don’t "forget" what they did in the last deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  "Secure by Default" is the new vibe
&lt;/h3&gt;

&lt;p&gt;With Microsoft flipping the switch on &lt;strong&gt;"Secure by Default"&lt;/strong&gt; for Teams this week, the same applies to our agents. We can’t have "double agents" with unchecked access. 2026 is the year of &lt;strong&gt;Identity-based Agent Security.&lt;/strong&gt; Every agent you build needs a scope, a permission set, and an audit log.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Prediction: The "Agent OS"
&lt;/h2&gt;

&lt;p&gt;By the end of this year, we won't be talking about "AI features." We’ll be talking about the &lt;strong&gt;Agent OS&lt;/strong&gt;—a layer in our stack where autonomous workers live. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The question isn't whether AI will replace devs. The question is: &lt;strong&gt;Are you the dev who writes the code, or the dev who manages the 10 agents writing the code?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  💬 Let’s Discuss
&lt;/h3&gt;

&lt;p&gt;Are you moving your projects to a multi-agent setup yet? Or are you still team "Single Prompt"? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm curious—what’s the first task you’re handing off to a permanent agent this year?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ces2026</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Java Streams Explained: A Faster, Cleaner, More Powerful Way to Work With Collections</title>
      <dc:creator>Ashish Sharda</dc:creator>
      <pubDate>Sat, 06 Dec 2025 13:27:05 +0000</pubDate>
      <link>https://dev.to/ashish_sharda_a540db2e50e/java-streams-explained-a-faster-cleaner-more-powerful-way-to-work-with-collections-58j0</link>
      <guid>https://dev.to/ashish_sharda_a540db2e50e/java-streams-explained-a-faster-cleaner-more-powerful-way-to-work-with-collections-58j0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxouy0l2zrrtazvgwfdk.png%3Fw%3D1200%26h%3D400%26fit%3Dcrop" 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%2Ftxouy0l2zrrtazvgwfdk.png%3Fw%3D1200%26h%3D400%26fit%3Dcrop" alt="Java Stream pipelines" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most developers start their Java journey writing loops — &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, enhanced &lt;code&gt;for&lt;/code&gt;, nested loops, and occasionally, loops inside database calls that we pretend aren't loops.&lt;/p&gt;

&lt;p&gt;But modern applications generate and consume huge amounts of data, and iterating manually becomes:&lt;/p&gt;

&lt;p&gt;❌ Verbose&lt;br&gt;&lt;br&gt;
❌ Error-prone&lt;br&gt;&lt;br&gt;
❌ Harder to maintain&lt;br&gt;&lt;br&gt;
❌ Not optimized for parallel execution&lt;/p&gt;

&lt;p&gt;That's where Java Streams change the game.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Exactly Is a Stream?
&lt;/h2&gt;

&lt;p&gt;A Stream in Java is &lt;strong&gt;not a data structure&lt;/strong&gt; — it's a pipeline that processes data in a functional style.&lt;/p&gt;

&lt;p&gt;Think of a stream like water running through pipes:&lt;/p&gt;

&lt;p&gt;✔ You can filter dirt&lt;br&gt;&lt;br&gt;
✔ You can change color&lt;br&gt;&lt;br&gt;
✔ You can collect it into a bottle&lt;/p&gt;

&lt;p&gt;But the water is still the same water — &lt;strong&gt;the stream never stores it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A stream has three stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;List, Set, Array, Files&lt;/td&gt;
&lt;td&gt;Where data flows from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intermediate Ops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;map()&lt;/code&gt;, &lt;code&gt;filter()&lt;/code&gt;, &lt;code&gt;sorted()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Transforming data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terminal Ops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;collect()&lt;/code&gt;, &lt;code&gt;forEach()&lt;/code&gt;, &lt;code&gt;reduce()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Producing output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Why Developers Love Streams
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without Streams&lt;/th&gt;
&lt;th&gt;With Streams&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;More lines&lt;/td&gt;
&lt;td&gt;Less code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Imperative logic&lt;/td&gt;
&lt;td&gt;Functional style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harder to parallelize&lt;/td&gt;
&lt;td&gt;Built-in parallel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tied to manual state&lt;/td&gt;
&lt;td&gt;Stateless operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Java streams allow you to write &lt;strong&gt;cleaner, faster, scalable code&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  1️⃣Filtering Data (The Cleaner If-Condition)
&lt;/h2&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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nums&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;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;nums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;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;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="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;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [12, 20]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;filter()&lt;/code&gt; returns only items that match the condition.&lt;/p&gt;
&lt;h2&gt;
  
  
  2️⃣ Transforming Values Using map()
&lt;/h2&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;names&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="s"&gt;"ashish"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"kumar"&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;upper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;String:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toUpperCase&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="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;upper&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [ASHISH, KUMAR]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;map()&lt;/code&gt; transforms each value &lt;strong&gt;without altering the original collection&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  3️⃣ Sorting Made Simple
&lt;/h2&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;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nums&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;8&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="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&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="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;sorted&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 3, 6, 8]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also pass a custom comparator:&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4️⃣ Reduce — Calculating a Single Result (Sum, Max, etc.)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reduce&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="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&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="n"&gt;sum&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;Reduce means:&lt;/strong&gt; "Take all values → return one answer"&lt;/p&gt;

&lt;p&gt;You can also compute maximum:&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Integer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✨ Bonus: Streams &amp;amp; Objects (Real-World Use Case)
&lt;/h2&gt;

&lt;p&gt;Let's say you have employees:&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;Employee&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;Employee&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;n&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;s&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
        &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&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;Extract names only:&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="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;namesOnly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Filter high salary professionals:&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="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;highEarners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&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;-&amp;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;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100000&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parallel Streams — Multithreading Without Threads
&lt;/h2&gt;

&lt;p&gt;Just change:&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;employees&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java will:&lt;/p&gt;

&lt;p&gt;✔ Split workload across CPU cores&lt;br&gt;&lt;br&gt;
✔ Run tasks concurrently&lt;br&gt;&lt;br&gt;
✔ Auto merge the result&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However — use carefully for:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;❌ Very small collections&lt;br&gt;&lt;br&gt;
❌ Concurrent modification&lt;br&gt;&lt;br&gt;
✔ CPU-intensive operations&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 When to Use Streams (and When Not to)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Good For&lt;/th&gt;
&lt;th&gt;Not Great For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Filtering&lt;/td&gt;
&lt;td&gt;Very complex branching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapping&lt;/td&gt;
&lt;td&gt;Code that relies on mutation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregation&lt;/td&gt;
&lt;td&gt;Debugging heavy logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel performance&lt;/td&gt;
&lt;td&gt;Nested changing state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Streams are about &lt;strong&gt;data transformation&lt;/strong&gt;, not updating shared variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏁 Final Thoughts — Streams Make You Write Better Java
&lt;/h2&gt;

&lt;p&gt;Java Streams aren't just a syntax trick — they encourage &lt;strong&gt;cleaner architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;🚀 No mutable state&lt;br&gt;&lt;br&gt;
🚀 Easier parallelization&lt;br&gt;&lt;br&gt;
🚀 Less code, same meaning&lt;br&gt;&lt;br&gt;
🚀 More declarative thinking&lt;/p&gt;

&lt;p&gt;Once you start thinking in pipelines, your code naturally becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller&lt;/li&gt;
&lt;li&gt;Faster&lt;/li&gt;
&lt;li&gt;Easier to change&lt;/li&gt;
&lt;li&gt;Easier to reason about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Java Streams don't just process data — they reshape how you think as a Java developer.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you started using Java Streams in your projects? What's been your experience? Drop a comment below and let's discuss how streams have changed your coding style!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the Author:&lt;/strong&gt; Ashish Sharda is a seasoned engineering leader with 15+ years of experience across companies like Apple, Salesforce, and Yahoo. He's passionate about teaching modern Java practices and helping developers write cleaner, more efficient code. Follow for more deep dives into Java and software engineering best practices.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
