<?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: David Brown</title>
    <description>The latest articles on DEV Community by David Brown (@davebrown1975).</description>
    <link>https://dev.to/davebrown1975</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F198946%2Fd475a0f4-201e-48d3-ac6d-d5a7b6272af1.jpeg</url>
      <title>DEV Community: David Brown</title>
      <link>https://dev.to/davebrown1975</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davebrown1975"/>
    <language>en</language>
    <item>
      <title>gRPC moved into Spring Boot 4.1: the new starters, the property renames, and what we measured</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Wed, 22 Jul 2026 07:28:57 +0000</pubDate>
      <link>https://dev.to/davebrown1975/grpc-moved-into-spring-boot-41-the-new-starters-the-property-renames-and-what-we-measured-1mne</link>
      <guid>https://dev.to/davebrown1975/grpc-moved-into-spring-boot-41-the-new-starters-the-property-renames-and-what-we-measured-1mne</guid>
      <description>&lt;p&gt;Spring Boot 4.1 went GA on 10 June 2026, and its biggest change for service-to-service work is that gRPC support moved into Spring Boot itself. The spring-grpc 1.1.0 release notes state it in a single line: "The main change in 1.1.0 is the migration of autoconfiguration to Spring Boot 4.1.0."&lt;/p&gt;

&lt;p&gt;Concretely, the starters you add to a build are now Boot's own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;org.springframework.boot:spring-boot-starter-grpc-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;org.springframework.boot:spring-boot-starter-grpc-client&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;org.springframework.boot:spring-boot-starter-grpc-server-test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;org.springframework.boot:spring-boot-starter-grpc-client-test&lt;/code&gt;
The spring-grpc project lives on as the programming model: &lt;code&gt;spring-grpc-core:1.1.0&lt;/code&gt; supplies &lt;code&gt;@GrpcService&lt;/code&gt;, &lt;code&gt;@ImportGrpcClients&lt;/code&gt; and &lt;code&gt;GrpcChannelFactory&lt;/code&gt;, and it arrives transitively. You never declare spring-grpc in your build. The old &lt;code&gt;org.springframework.grpc&lt;/code&gt; starters are deprecated, with the 1.0.x artifacts still on Maven Central for projects pinned to Boot 4.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the merge was announced in November 2025, the plan included explicit migration guidance at release time. What exists as I write is a draft wiki page, and the tutorials that rank for the topic still teach the old coordinates or the abandoned &lt;code&gt;net.devh&lt;/code&gt; starter; Baeldung's guide still declares Boot 3.4/3.5 support. We built a complete server and client on 4.1.0 GA for a full tutorial on our blog; this post is the condensed version of what changed and what we found.&lt;/p&gt;

&lt;h3&gt;
  
  
  The property renames
&lt;/h3&gt;

&lt;p&gt;The property tree was restructured as well as re-homed. These are the renames most likely to bite when you migrate a spring-grpc 1.0.x project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;spring-grpc 1.0 (Boot 4.0)&lt;/th&gt;
&lt;th&gt;Spring Boot 4.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.client.channels.&amp;lt;name&amp;gt;.address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;spring.grpc.client.channel.&amp;lt;name&amp;gt;.target&lt;/code&gt; (singular &lt;code&gt;channel&lt;/code&gt;, &lt;code&gt;address&lt;/code&gt; becomes &lt;code&gt;target&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.client.channels.&amp;lt;name&amp;gt;.default-deadline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.client.channel.&amp;lt;name&amp;gt;.default.deadline&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.server.keep-alive.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.server.keepalive.*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.server.max-inbound-message-size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.server.inbound.message.max-size&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;spring.grpc.server.address&lt;/code&gt; (combined &lt;code&gt;host:port&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;spring.grpc.server.address&lt;/code&gt; (address only) plus a separate &lt;code&gt;spring.grpc.server.port&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring.grpc.server.health.actuator.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removed; health is configured under &lt;code&gt;spring.grpc.server.health.*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these was checked against the 4.1.0 GA jars and the migration guide while building the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The build file delta is zero lines
&lt;/h3&gt;

&lt;p&gt;This was the finding I expected least. The Spring Initializr skeleton applies &lt;code&gt;com.google.protobuf&lt;/code&gt; version 0.9.6 with no &lt;code&gt;protobuf{}&lt;/code&gt; block and no codegen configuration at all. Drop a &lt;code&gt;.proto&lt;/code&gt; file into &lt;code&gt;src/main/proto/&lt;/code&gt; and the build generates both the message classes and the gRPC service stubs.&lt;/p&gt;

&lt;p&gt;It works because Boot 4.1's Gradle plugin ships a &lt;code&gt;ProtobufPluginAction&lt;/code&gt; that reacts to the protobuf plugin being applied and configures the &lt;code&gt;protoc&lt;/code&gt; artifact (4.34.2, aligned with the managed &lt;code&gt;protobuf-java&lt;/code&gt;), the &lt;code&gt;io.grpc:protoc-gen-grpc-java&lt;/code&gt; codegen plugin (1.80.0, aligned with the managed gRPC BOM), and a resolution strategy keeping them in step with the runtime classpath. Every pre-4.1 tutorial showing a 20-line &lt;code&gt;protobuf{}&lt;/code&gt; block is describing configuration this setup no longer needs. Across the whole project, server and client, &lt;code&gt;build.gradle&lt;/code&gt; never changed from the skeleton.&lt;/p&gt;

&lt;h3&gt;
  
  
  What runs before you configure anything
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;spring-boot-starter-grpc-server&lt;/code&gt; on the classpath and one &lt;code&gt;@GrpcService&lt;/code&gt; class, startup gives you a Netty gRPC server on port 9090 alongside Tomcat, reflection registered by default (grpcurl works with just &lt;code&gt;-plaintext&lt;/code&gt;), and &lt;code&gt;grpc.health.v1.Health&lt;/code&gt; answering &lt;code&gt;SERVING&lt;/code&gt; out of the box, bridged from Boot's health system when actuator is present.&lt;/p&gt;

&lt;p&gt;One default deserves attention: &lt;code&gt;spring.grpc.server.observation.enabled&lt;/code&gt; is true, so an &lt;code&gt;ObservationGrpcServerInterceptor&lt;/code&gt; instruments every call even when nothing consumes the observations. In our A/B it cost roughly 20 to 25% of gRPC throughput with no collector attached (a single clean measurement pair, so treat the exact percentage as indicative). If you export metrics or traces you're paying for something you use; if you don't, that property is the off switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The benchmark
&lt;/h3&gt;

&lt;p&gt;We measured unary REST (Spring MVC, JSON) against unary gRPC on the same running process, same H2-backed repository, same row, with k6 and ghz in four order-balanced pairs, both protocols seconds apart within each pair. Median run: REST 1,613 req/s with p50/p95/p99 of 7.0/27.4/38.0ms; gRPC 1,926 req/s with 7.9/14.0/19.7ms. That is about 19% more throughput and roughly half the tail latency, with medians close to equal.&lt;/p&gt;

&lt;p&gt;The environment note belongs next to the numbers, so here it is: this ran on a shared, virtualised 2-vCPU sandbox whose two vCPUs are hyperthread siblings of one physical core, with invisible host throttling we controlled for using a fixed-work CPU probe. The ratios between the two protocols under identical conditions are the result; the absolute figures describe that box and nothing else. The full methodology, fairness notes and raw outputs ship with the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The full version
&lt;/h3&gt;

&lt;p&gt;The complete tutorial adds TLS with SSL bundles, error handling with &lt;code&gt;@GrpcAdvice&lt;/code&gt;, in-process and real-transport testing, OpenTelemetry tracing with a verified client-to-server trace, and a server-streaming demonstration: &lt;a href="https://tucanoo.com/grpc-in-spring-boot-4-1-auto-configuration-tutorial/" rel="noopener noreferrer"&gt;gRPC in Spring Boot 4.1: The New Auto-Configuration, End to End&lt;/a&gt;. All the code, benchmark scripts and raw results are in the &lt;a href="https://github.com/tucanoo/spring-boot-grpc-tutorial" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>grpc</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a multi-step AI agent in Java with Spring AI 2.0</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Tue, 30 Jun 2026 19:02:21 +0000</pubDate>
      <link>https://dev.to/davebrown1975/building-a-multi-step-ai-agent-in-java-with-spring-ai-20-4nk3</link>
      <guid>https://dev.to/davebrown1975/building-a-multi-step-ai-agent-in-java-with-spring-ai-20-4nk3</guid>
      <description>&lt;p&gt;I wanted to try Spring AI's agentic tooling on something that resembles a real production project rather than the usual weather-API call, so I built an example Spring Boot API: a civic research assistant. You give it a plain-English issue and a UK postcode, and it works out the most effective way to raise that issue with Parliament. It uses an LLM (ChatGPT) along with the free UK postcode and Open Parliament APIs. The flow isn't dictated in code. We register a handful of @Tool methods (resolve the postcode, find the MP, search bills, find committees and open inquiries, check the voting record) and let the model decide which to call and in what order. It chains them itself and returns a final answer with a citation on every claim. The stack is Java 25, Spring Boot 4.1 and Spring AI 2.0.&lt;br&gt;
The &lt;a href="https://tucanoo.com/building-an-agentic-workflow-in-java-multi-step-ai-agents-with-spring-ai-and-tool-calling/" rel="noopener noreferrer"&gt;full tutorial&lt;/a&gt; walks through the Spring AI 2.0 tool-calling setup and what changed since 1.x, registering the tools, getting structured typed output back instead of free text, and a small guard that checks every citation actually came from a tool call so the model can't quietly invent a source. It's all runnable, with the complete code on GitHub.&lt;/p&gt;

&lt;p&gt;Full tutorial: &lt;a href="https://tucanoo.com/building-an-agentic-workflow-in-java-multi-step-ai-agents-with-spring-ai-and-tool-calling/" rel="noopener noreferrer"&gt;https://tucanoo.com/building-an-agentic-workflow-in-java-multi-step-ai-agents-with-spring-ai-and-tool-calling/&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/tucanoo/spring-ai-civic-research-assistant" rel="noopener noreferrer"&gt;https://github.com/tucanoo/spring-ai-civic-research-assistant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're using Spring AI's tool calling on anything that touches real data, I'd be interested in how you're handling grounding to keep the model from citing things it never fetched.&lt;/p&gt;

</description>
      <category>java</category>
      <category>ai</category>
      <category>springboot</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Big Beautiful Bot - A Spring Boot &amp; LLM Chatbot with SSE -&gt; Next.js</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:16:09 +0000</pubDate>
      <link>https://dev.to/davebrown1975/the-big-beautiful-bot-a-spring-boot-llm-chatbot-with-sse-nextjs-15p4</link>
      <guid>https://dev.to/davebrown1975/the-big-beautiful-bot-a-spring-boot-llm-chatbot-with-sse-nextjs-15p4</guid>
      <description>&lt;p&gt;There's not much material out there showing how to use SSE (Server-Sent Events) with a Spring Boot back end connected to an LLM to power a ChatGPT-style Next.js front end, so I put together a full tutorial on it, with the complete code repository as always.&lt;/p&gt;

&lt;p&gt;Tutorial -&amp;gt; &lt;a href="https://tucanoo.com/streaming-llm-responses-end-to-end-a-spring-boot-sse-backend-and-a-next-js-frontend/" rel="noopener noreferrer"&gt;https://tucanoo.com/streaming-llm-responses-end-to-end-a-spring-boot-sse-backend-and-a-next-js-frontend/&lt;/a&gt;&lt;br&gt;
Repo -&amp;gt; &lt;a href="https://github.com/tucanoo/streaming-llm-spring-next" rel="noopener noreferrer"&gt;https://github.com/tucanoo/streaming-llm-spring-next&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>springboot</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Virtual Threads in Spring Boot 4: I rewrote a blocking service and measured everything</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Sun, 21 Jun 2026 09:52:15 +0000</pubDate>
      <link>https://dev.to/davebrown1975/virtual-threads-in-spring-boot-4-i-rewrote-a-blocking-service-and-measured-everything-d83</link>
      <guid>https://dev.to/davebrown1975/virtual-threads-in-spring-boot-4-i-rewrote-a-blocking-service-and-measured-everything-d83</guid>
      <description>&lt;p&gt;I took one realistic blocking Spring Boot 4 service — a PostgreSQL query through HikariCP plus a downstream HTTP call — flipped spring.threads.virtual.enabled=true, and benchmarked exactly what changed: throughput, tail latency, and where the bottleneck went.&lt;/p&gt;

&lt;p&gt;TL;DR: ~6× throughput at 2,500 concurrent users from one flag — but the connection pool quietly becomes your new ceiling, so you have to resize it. I also measured the JEP 491 pinning change (the "avoid synchronized" advice is now out of date).&lt;/p&gt;

&lt;p&gt;Full write-up, raw data, charts and a reproducible repo on the Tucanoo blog&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://tucanoo.com/virtual-threads-in-spring-boot-4" rel="noopener noreferrer"&gt;https://tucanoo.com/virtual-threads-in-spring-boot-4&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>webperf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Localised notifications with Firebase cloud messaging (FCM)</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Wed, 09 Nov 2022 09:08:05 +0000</pubDate>
      <link>https://dev.to/davebrown1975/localised-notifications-with-firebase-cloud-messaging-fcm-ecn</link>
      <guid>https://dev.to/davebrown1975/localised-notifications-with-firebase-cloud-messaging-fcm-ecn</guid>
      <description>&lt;p&gt;If you need to send localised push notifications from a Java server side application to mobile devices with Firebase Cloud Messaging (FCM), whether your server side is Grails, Spring boot or any other Java based framework utilising the Firebase (Admin) SDK then search no more as it's not so clear in the official documentation.  Most examples focus on the REST or JS implementations.&lt;/p&gt;

&lt;p&gt;In my use case, I wanted to alert users subscribing to a specific Topic of a particular event.  Users of this app are distributed globally so it was important to localise the messages depending on the users locale according to their device.  From the server side, I just want to generate the notification and let Firebase's servers handle the notifications to each device subscribed to the topic and then the device it self display a locales message, both the title and subtitle,  on both Android and IOS devices. &lt;/p&gt;

&lt;p&gt;Fortunately Firebase lets us do this by specifying localised 'keys', instead of specifying the title and body.    However finding an example was more of a struggle as the documentation is somewhat lacking.&lt;/p&gt;

&lt;p&gt;What we have to do is establish Options objects for each platform, i.e. one for Android and one for Apple devices and then deep within nested Builders we can finally set the localised keys.  These then can be interpreted from the resources within the mobile App itself and will be displayed to the user, both when the app is in the foreground AND also when it's in the background.&lt;/p&gt;

&lt;p&gt;So without further ado,  let's look at the code:  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: I'm omitting the rest of the app code assuming you already know how to setup Firebase etc and just need to see how to generate the notification.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void sendLocalisedNotification(String topic, String titleKey, String bodyKey) {
        // Set localisation key for Android
        AndroidConfig androidConfig = AndroidConfig.builder()
            .setNotification(
                AndroidNotification.builder()
                    .setTitleLocalizationKey(titleKey)
                    .setBodyLocalizationKey(bodyKey)
                    .build()
            ).build()

        // Set localisation key for Apple devices
        ApnsConfig apnsConfig = ApnsConfig.builder()
            .setAps(Aps.builder()
                .setAlert(ApsAlert.builder()
                    .setTitleLocalizationKey(titleKey)
                    .setSubtitleLocalizationKey(bodyKey)
                    .build()
                ).build()
            ).build()

        Message message = Message.builder()
            .setTopic(topic)
            .setAndroidConfig(androidConfig)
            .setApnsConfig(apnsConfig)
            .build();

        // And then send the message to the devices subscribed to the provided topic.
        try {
            String response = FirebaseMessaging.getInstance().send(message);

            // Response is a message ID string.
            log.debug("Firebase message $topic sent: " + response);
        } catch (Exception ex) {
            log.warn("Firebase message $topic failed: " + ex.message);
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that's the gist of it, this method can be called with the topic we want to publish to, the title key and the body key.&lt;/p&gt;

&lt;p&gt;I hope this helps save someone time discovering how to accomplish localised messages under a Java environment using the FCM SDK.  &lt;/p&gt;

&lt;p&gt;Dave / &lt;a href="https://tucanoo.com" rel="noopener noreferrer"&gt;Tucanoo Solutions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>java</category>
      <category>notifications</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Super Fast Prototyping with Grails Tutorial Available</title>
      <dc:creator>David Brown</dc:creator>
      <pubDate>Wed, 12 Feb 2020 08:39:54 +0000</pubDate>
      <link>https://dev.to/davebrown1975/super-fast-prototyping-with-grails-tutorial-available-1aij</link>
      <guid>https://dev.to/davebrown1975/super-fast-prototyping-with-grails-tutorial-available-1aij</guid>
      <description>&lt;p&gt;One of the greatest benefits of the Grails framework is how it allows you to put together a Proof Of Concept(POC) or a web app prototype in such a short period time.&lt;/p&gt;

&lt;p&gt;In a current world where there is lots of focus on UI frameworks and microservice architectures, sometimes, you just need to build something quickly without worrying about different front and backend layers.  &lt;/p&gt;

&lt;p&gt;Grails allows you to build apps fast, but with the benefit of sitting on top of the most robust Java-based libraries in existence, Spring, Hibernate, etc. etc.&lt;/p&gt;

&lt;p&gt;In this tutorial, you'll see how to build a simple CRM backed by a H2 in-memory database, prepopulating itself with 1000 fictitious records.&lt;/p&gt;

&lt;p&gt;The user is provided with basic CRUD screens,  create, edit, and a rich Datatables based list screen to allow filtering, sorting and ajax driven paging of the customer records.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tucanoo/crm_grails" rel="noopener noreferrer"&gt;Download full source on github&lt;/a&gt; Download the full source on github &lt;/p&gt;

&lt;p&gt;and &lt;a href="https://www.tucanoo.com/how-to-build-a-simple-crm-with-grails-4/" rel="noopener noreferrer"&gt;Read the full tutorial&lt;/a&gt;&lt;/p&gt;

</description>
      <category>grails</category>
      <category>tutorial</category>
      <category>groovy</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
