From d82d70e358d705c1cc9a8452f41d60c0db73737d Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Fri, 13 Mar 2026 09:41:19 -0400 Subject: [PATCH 1/7] Add gRPC client sample with Spring Integration Implement a new basic sample demonstrating Spring Integration's gRPC client capabilities. This sample shows how to integrate gRPC with Spring Boot and Spring Integration for both single-response and streaming response patterns. This example is based on the proto file that exists in the Spring gRPC project, to provide user familiarity --- basic/grpc-client/README.md | 58 +++++++++ .../integration/samples/Application.java | 40 ++++++ .../samples/GrpcClientConfiguration.java | 123 ++++++++++++++++++ basic/grpc-client/src/main/proto/hello.proto | 23 ++++ .../src/main/resources/application.properties | 2 + .../integration/samples/GrpcClientTests.java | 102 +++++++++++++++ build.gradle | 64 +++++++++ src/checkstyle/checkstyle-suppressions.xml | 1 + 8 files changed, 413 insertions(+) create mode 100644 basic/grpc-client/README.md create mode 100644 basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java create mode 100644 basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java create mode 100644 basic/grpc-client/src/main/proto/hello.proto create mode 100644 basic/grpc-client/src/main/resources/application.properties create mode 100644 basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java diff --git a/basic/grpc-client/README.md b/basic/grpc-client/README.md new file mode 100644 index 000000000..c3f78c734 --- /dev/null +++ b/basic/grpc-client/README.md @@ -0,0 +1,58 @@ +gRPC Client Sample +================== + +This example demonstrates the use of Spring Integration's **gRPC Outbound Gateway** for client-side communication. + +It uses Java configuration and the Spring Integration DSL to configure the gRPC client adapters. + +The sample demonstrates two gRPC communication patterns: + +1. **Single Response** - Simple request/reply pattern where the client sends a request and receives a single response +2. **Streaming Response** - The client sends a request and receives a stream of responses + +## Prerequisites + +This sample requires a gRPC server to be running. You can use any gRPC server that implements the `Simple` service defined in `hello.proto`, or run the test which uses an in-process gRPC server. + +## Running the sample + +### Command Line Using Gradle + +To run the sample using Gradle, execute: + + $ gradlew :grpc-client:bootRun + +This will start the Spring Boot application using the [Spring Boot Gradle Plugin](https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/). + +## Configuration + +The application expects a gRPC server to be available. By default, it connects to `localhost:9090`. You can configure the server address in `application.properties`: + +```properties +spring.grpc.client.channels.local.address=static://localhost:9090 +``` + +## Output + +The application demonstrates two patterns: + +### Single Response Pattern + +The client sends a `HelloRequest` with name "Jack" and receives a single `HelloReply`: + + Single response reply: message: Hello Jack + +### Streaming Response Pattern + +The client sends a `HelloRequest` with name "Jane" and receives multiple `HelloReply` messages: + + Stream received reply: Hello Jane + Stream received reply: Hello Again + +## Running the Tests + +The test uses an in-process gRPC server and does not require an external server: + + $ gradlew :grpc-client:test + +The test validates both single-response and streaming-response patterns. diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java new file mode 100644 index 000000000..3ac1ad24a --- /dev/null +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Run the gRPC client sample application. + *

+ * Demonstrates how to use Spring Integration with gRPC for client-side communication. + * + * @author Glenn Renfro + */ +@SpringBootApplication +public class Application { + + /** + * Run the Spring Boot application. + * @param args the command line arguments + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java new file mode 100644 index 000000000..e6fdd1287 --- /dev/null +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java @@ -0,0 +1,123 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples; + +import io.grpc.ManagedChannel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Flux; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelFactory; +import org.springframework.integration.channel.FluxMessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.grpc.dsl.Grpc; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +/** + * Service for demonstrating gRPC client functionality with Spring Integration. + * + * @author Glenn Renfro + */ +@Configuration(proxyBeanMethods = false) +public class GrpcClientConfiguration { + + private static final Log LOGGER = LogFactory.getLog(GrpcClientConfiguration.class); + + @Bean + ManagedChannel managedChannel(GrpcChannelFactory factory) { + return factory.createChannel("local"); + } + + /** + * Create an integration flow for outbound gRPC requests with single responses. + * @param managedChannel the gRPC managed channel + * @return the integration flow + */ + @Bean + IntegrationFlow grpcOutboundFlowSingleResponse(ManagedChannel managedChannel) { + return flow -> flow + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) + .methodName("SayHello")); + } + + /** + * Create an integration flow for outbound gRPC requests with streaming responses. + * @param managedChannel the gRPC managed channel + * @return the integration flow + */ + @Bean + IntegrationFlow grpcOutboundFlowStreamResponse(ManagedChannel managedChannel) { + return flow -> flow + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) + .methodName("StreamHello")); + } + + /** + * Create an application runner that sends a single gRPC request and receives a single response. + * @param grpcInputChannelSingleResponse the message channel for single response requests + * @return the application runner + */ + @Bean + ApplicationRunner grpcClientSingleResponse( + @Autowired @Qualifier("grpcOutboundFlowSingleResponse.input") MessageChannel grpcInputChannelSingleResponse) { + + return args -> { + HelloReply reply = new MessagingTemplate().convertSendAndReceive(grpcInputChannelSingleResponse, + HelloRequest.newBuilder().setName("Jack").build(), + HelloReply.class); + + LOGGER.info("Single response reply: " + (reply != null ? reply.getMessage() : "No reply received")); + }; + } + + /** + * Create an application runner that sends a gRPC request and receives a stream of responses. + * @param grpcInputChannel the message channel for streaming response requests + * @return the application runner + */ + @Bean + ApplicationRunner grpcClientStreamResponse( + @Autowired @Qualifier("grpcOutboundFlowStreamResponse.input") MessageChannel grpcInputChannel) { + + return args -> { + FluxMessageChannel grpcStreamOutputChannel = new FluxMessageChannel(); + Flux.from(grpcStreamOutputChannel) + .map(message -> (HelloReply) message.getPayload()) + .map(HelloReply::getMessage) + .doOnNext(msg -> LOGGER.info("Stream received reply: " + msg)) + .doOnError(error -> { + LOGGER.error("Error in stream: " + error.getMessage(), error); + }) + .subscribe(); + + Message requestMessage = MessageBuilder + .withPayload(HelloRequest.newBuilder().setName("Jane").build()) + .setReplyChannel(grpcStreamOutputChannel) + .build(); + grpcInputChannel.send(requestMessage); + }; + } + +} diff --git a/basic/grpc-client/src/main/proto/hello.proto b/basic/grpc-client/src/main/proto/hello.proto new file mode 100644 index 000000000..47b512ecc --- /dev/null +++ b/basic/grpc-client/src/main/proto/hello.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.springframework.integration.samples"; +option java_outer_classname = "HelloWorldProto"; + +// The greeting service definition. +service Simple { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) { + } + rpc StreamHello(HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/basic/grpc-client/src/main/resources/application.properties b/basic/grpc-client/src/main/resources/application.properties new file mode 100644 index 000000000..db93bd165 --- /dev/null +++ b/basic/grpc-client/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# gRPC Client Configuration +spring.grpc.client.channels.local.address=static://localhost:9090 diff --git a/basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java b/basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java new file mode 100644 index 000000000..25bc6f828 --- /dev/null +++ b/basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +/** + * Integration test for gRPC client functionality. + * + * @author Glenn Renfro + */ +@SpringBootTest(properties = { + "spring.grpc.server.inprocess.name=test", + "spring.grpc.client.channels.local.address=in-process:test" +}) +@DirtiesContext +@ExtendWith(OutputCaptureExtension.class) +public class GrpcClientTests { + + /** + * Verify the gRPC service returns the expected greeting message. + * @param output the captured console output + */ + @Test + void verifyMessage(CapturedOutput output) { + await() + .atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> { + assertThat(output.getAll()) + .contains("Single response reply: Hello Jack") + .contains("Stream received reply: Hello Jane") + .contains("Stream received reply: Hello Again"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class TestGrpcServerConfig { + + /** + * Create a test gRPC service implementation for the Simple service. + * @return the gRPC service implementation + */ + @Bean + SimpleGrpc.SimpleImplBase simpleService() { + return new SimpleGrpc.SimpleImplBase() { + + @Override + public void sayHello(HelloRequest request, io.grpc.stub.StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + request.getName()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + @Override + public void streamHello(HelloRequest request, + io.grpc.stub.StreamObserver responseObserver) { + + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + request.getName()) + .build(); + responseObserver.onNext(reply); + reply = HelloReply.newBuilder() + .setMessage("Hello Again") + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + }; + } + + } + +} diff --git a/build.gradle b/build.gradle index f1e57a748..a5c08745c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ buildscript { classpath 'org.gretty:gretty:4.1.10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.4" } } @@ -306,6 +307,7 @@ subprojects { subproject -> flexjsonVersion = '3.3' graalvmVersion = '25.0.2' groovyVersion = '5.0.3' + grpcVersion = '1.79.0' hsqldbVersion = '2.7.4' h2Version = '2.4.240' jacksonVersion = '2.21.0' @@ -325,8 +327,10 @@ subprojects { subproject -> mongoDriverVersion = '5.6.2' oracleDriverVersion = '23.26.0.0.0' postgresVersion = '42.7.9' + protobufVersion = '4.29.4' slf4jVersion = '2.0.17' springCloudVersion = '2025.1.0' + springGrpc = '1.0.2' springIntegrationVersion = '7.1.0-SNAPSHOT' set('spring-integration.version', "$springIntegrationVersion") springIntegrationSocialTwiterVersion = '1.0.1.BUILD-SNAPSHOT' @@ -877,6 +881,66 @@ project('cafe-dsl') { } } +project('grpc-client') { + description = 'gRPC client Basic Sample' + + apply plugin: 'org.springframework.boot' + apply plugin: 'com.google.protobuf' + + dependencies { + implementation 'org.springframework.boot:spring-boot-starter-integration' + implementation "org.springframework.grpc:spring-grpc-client-spring-boot-starter:$springGrpc" + implementation "org.springframework.integration:spring-integration-grpc:$springIntegrationVersion" + + implementation("com.google.protobuf:protobuf-java:$protobufVersion") + implementation("io.grpc:grpc-netty:$grpcVersion") + implementation("io.grpc:grpc-inprocess:$grpcVersion") + + //Test + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "org.springframework.grpc:spring-grpc-test:$springGrpc" + } + + + protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" + } + } + generateProtoTasks { + all()*.plugins { + grpc { + option '@generated=omit' + } + } + } + } + // Exclude generated protobuf/grpc code from checkstyle + checkstyleMain { + source = fileTree('src/main/java') + } + + checkstyleTest { + source = fileTree('src/test/java') + } + + springBoot { + mainClass = 'org.springframework.integration.samples.Application' + } + + tasks.register('run', JavaExec) { + mainClass = 'org.springframework.integration.samples.Application' + classpath = sourceSets.main.runtimeClasspath + } + + tasks.withType(JavaExec) { + standardInput = System.in + } +} project('jdbc') { description = 'JDBC Basic Sample' diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index da1d0cb5e..8bcb99eab 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -25,4 +25,5 @@ + From 02117140afd7d789ca9ea8d87041672132aa8a89 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Wed, 18 Mar 2026 16:34:22 -0400 Subject: [PATCH 2/7] Implement code review changes --- .../integration/samples/{ => grpc/client}/Application.java | 2 +- .../samples/{ => grpc/client}/GrpcClientConfiguration.java | 2 +- basic/grpc-client/src/main/proto/hello.proto | 2 +- .../samples/{ => grpc/client}/GrpcClientTests.java | 2 +- build.gradle | 7 +------ 5 files changed, 5 insertions(+), 10 deletions(-) rename basic/grpc-client/src/main/java/org/springframework/integration/samples/{ => grpc/client}/Application.java (95%) rename basic/grpc-client/src/main/java/org/springframework/integration/samples/{ => grpc/client}/GrpcClientConfiguration.java (98%) rename basic/grpc-client/src/test/java/org/springframework/integration/samples/{ => grpc/client}/GrpcClientTests.java (98%) diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java similarity index 95% rename from basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java rename to basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java index 3ac1ad24a..77da01321 100644 --- a/basic/grpc-client/src/main/java/org/springframework/integration/samples/Application.java +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.samples; +package org.springframework.integration.samples.grpc.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java similarity index 98% rename from basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java rename to basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java index e6fdd1287..53e7dfaab 100644 --- a/basic/grpc-client/src/main/java/org/springframework/integration/samples/GrpcClientConfiguration.java +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.samples; +package org.springframework.integration.samples.grpc.client; import io.grpc.ManagedChannel; import org.apache.commons.logging.Log; diff --git a/basic/grpc-client/src/main/proto/hello.proto b/basic/grpc-client/src/main/proto/hello.proto index 47b512ecc..950e549b0 100644 --- a/basic/grpc-client/src/main/proto/hello.proto +++ b/basic/grpc-client/src/main/proto/hello.proto @@ -1,7 +1,7 @@ syntax = "proto3"; option java_multiple_files = true; -option java_package = "org.springframework.integration.samples"; +option java_package = "org.springframework.integration.samples.grpc.client"; option java_outer_classname = "HelloWorldProto"; // The greeting service definition. diff --git a/basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java similarity index 98% rename from basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java rename to basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java index 25bc6f828..8e3d4b446 100644 --- a/basic/grpc-client/src/test/java/org/springframework/integration/samples/GrpcClientTests.java +++ b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.samples; +package org.springframework.integration.samples.grpc.client; import java.time.Duration; diff --git a/build.gradle b/build.gradle index a5c08745c..9e7d41152 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { classpath 'org.gretty:gretty:4.1.10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion" - classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.4" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.6" } } @@ -893,8 +893,6 @@ project('grpc-client') { implementation "org.springframework.integration:spring-integration-grpc:$springIntegrationVersion" implementation("com.google.protobuf:protobuf-java:$protobufVersion") - implementation("io.grpc:grpc-netty:$grpcVersion") - implementation("io.grpc:grpc-inprocess:$grpcVersion") //Test testImplementation 'org.springframework.boot:spring-boot-starter-test' @@ -937,9 +935,6 @@ project('grpc-client') { classpath = sourceSets.main.runtimeClasspath } - tasks.withType(JavaExec) { - standardInput = System.in - } } project('jdbc') { From 984cd450125bdd3a729378478103168e740c1003 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 19 Mar 2026 07:23:38 -0400 Subject: [PATCH 3/7] Remove version from the integration-grpc dependency Signed-off-by: Glenn Renfro --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9e7d41152..9de1f4fad 100644 --- a/build.gradle +++ b/build.gradle @@ -890,7 +890,7 @@ project('grpc-client') { dependencies { implementation 'org.springframework.boot:spring-boot-starter-integration' implementation "org.springframework.grpc:spring-grpc-client-spring-boot-starter:$springGrpc" - implementation "org.springframework.integration:spring-integration-grpc:$springIntegrationVersion" + implementation "org.springframework.integration:spring-integration-grpc" implementation("com.google.protobuf:protobuf-java:$protobufVersion") From d8bc0e5566ca6cb2732e2ab1e3b3895c4c2170b6 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 19 Mar 2026 11:00:52 -0400 Subject: [PATCH 4/7] Code review response - Config uses GrpcHeaders.SERVICE_METHOD for method def - Removed amqp dependency update - Removed changes to barrier pom.xml - spring grpc now uses bom --- basic/barrier/pom.xml | 3 +-- .../grpc/client/GrpcClientConfiguration.java | 13 ++++++++----- .../src/main/resources/application.properties | 2 +- build.gradle | 7 ++++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/basic/barrier/pom.xml b/basic/barrier/pom.xml index 322845c60..086b0b88c 100644 --- a/basic/barrier/pom.xml +++ b/basic/barrier/pom.xml @@ -73,8 +73,7 @@ org.springframework.boot - spring-boot-starter-rabbitmq - 4.1.0-SNAPSHOT + spring-boot-starter-amqp compile diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java index 53e7dfaab..1d17e8a6a 100644 --- a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.integration.samples.grpc.client; +import java.util.Collections; + import io.grpc.ManagedChannel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -30,6 +32,7 @@ import org.springframework.integration.channel.FluxMessageChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.grpc.GrpcHeaders; import org.springframework.integration.grpc.dsl.Grpc; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; @@ -47,7 +50,7 @@ public class GrpcClientConfiguration { @Bean ManagedChannel managedChannel(GrpcChannelFactory factory) { - return factory.createChannel("local"); + return factory.createChannel("spring-integration"); } /** @@ -58,8 +61,7 @@ ManagedChannel managedChannel(GrpcChannelFactory factory) { @Bean IntegrationFlow grpcOutboundFlowSingleResponse(ManagedChannel managedChannel) { return flow -> flow - .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) - .methodName("SayHello")); + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class)); } /** @@ -70,8 +72,7 @@ IntegrationFlow grpcOutboundFlowSingleResponse(ManagedChannel managedChannel) { @Bean IntegrationFlow grpcOutboundFlowStreamResponse(ManagedChannel managedChannel) { return flow -> flow - .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) - .methodName("StreamHello")); + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class)); } /** @@ -86,6 +87,7 @@ ApplicationRunner grpcClientSingleResponse( return args -> { HelloReply reply = new MessagingTemplate().convertSendAndReceive(grpcInputChannelSingleResponse, HelloRequest.newBuilder().setName("Jack").build(), + Collections.singletonMap(GrpcHeaders.SERVICE_METHOD, "SayHello"), HelloReply.class); LOGGER.info("Single response reply: " + (reply != null ? reply.getMessage() : "No reply received")); @@ -115,6 +117,7 @@ ApplicationRunner grpcClientStreamResponse( Message requestMessage = MessageBuilder .withPayload(HelloRequest.newBuilder().setName("Jane").build()) .setReplyChannel(grpcStreamOutputChannel) + .setHeader(GrpcHeaders.SERVICE_METHOD, "StreamHello") .build(); grpcInputChannel.send(requestMessage); }; diff --git a/basic/grpc-client/src/main/resources/application.properties b/basic/grpc-client/src/main/resources/application.properties index db93bd165..d1c9371d0 100644 --- a/basic/grpc-client/src/main/resources/application.properties +++ b/basic/grpc-client/src/main/resources/application.properties @@ -1,2 +1,2 @@ # gRPC Client Configuration -spring.grpc.client.channels.local.address=static://localhost:9090 +spring.grpc.client.channels.spring-integration.address=static://localhost:9090 diff --git a/build.gradle b/build.gradle index 9de1f4fad..a22236b1b 100644 --- a/build.gradle +++ b/build.gradle @@ -353,6 +353,7 @@ subprojects { subproject -> mavenBom "com.fasterxml.jackson:jackson-bom:$jacksonVersion" mavenBom "tools.jackson:jackson-bom:$jackson3Version" mavenBom "org.junit:junit-bom:$junitJupiterVersion" + mavenBom "org.springframework.grpc:spring-grpc-dependencies:$springGrpc" } } @@ -889,14 +890,14 @@ project('grpc-client') { dependencies { implementation 'org.springframework.boot:spring-boot-starter-integration' - implementation "org.springframework.grpc:spring-grpc-client-spring-boot-starter:$springGrpc" - implementation "org.springframework.integration:spring-integration-grpc" + implementation "org.springframework.grpc:spring-grpc-client-spring-boot-starter" + implementation 'org.springframework.integration:spring-integration-grpc' implementation("com.google.protobuf:protobuf-java:$protobufVersion") //Test testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation "org.springframework.grpc:spring-grpc-test:$springGrpc" + testImplementation "org.springframework.grpc:spring-grpc-test" } From 685bcb035d76fce7171b994e5df01c23704e9cab Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 19 Mar 2026 14:13:01 -0400 Subject: [PATCH 5/7] Update based on code review - One flow now sets method at flow definition time - Tests updated to not use port 9090 - Tests disable netty server as it is not needed (and opens port unnecessarily) - Readme updated - Add pom.xml - Rebased --- basic/grpc-client/README.md | 2 +- basic/grpc-client/pom.xml | 455 ++++++++++++++++++ .../grpc/client/GrpcClientConfiguration.java | 9 +- .../samples/grpc/client/GrpcClientTests.java | 16 +- 4 files changed, 475 insertions(+), 7 deletions(-) create mode 100644 basic/grpc-client/pom.xml diff --git a/basic/grpc-client/README.md b/basic/grpc-client/README.md index c3f78c734..57949fe30 100644 --- a/basic/grpc-client/README.md +++ b/basic/grpc-client/README.md @@ -29,7 +29,7 @@ This will start the Spring Boot application using the [Spring Boot Gradle Plugin The application expects a gRPC server to be available. By default, it connects to `localhost:9090`. You can configure the server address in `application.properties`: ```properties -spring.grpc.client.channels.local.address=static://localhost:9090 +spring.grpc.client.channels.spring-integration.address=static://localhost:9090 ``` ## Output diff --git a/basic/grpc-client/pom.xml b/basic/grpc-client/pom.xml new file mode 100644 index 000000000..ff9cfced6 --- /dev/null +++ b/basic/grpc-client/pom.xml @@ -0,0 +1,455 @@ + + + 4.0.0 + org.springframework.integration.samples + grpc-client + 7.1.0 + jar + https://github.com/spring-projects/spring-integration-samples + + Spring IO + https://spring.io/projects/spring-integration + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + artembilan + Artem Bilan + artem.bilan@broadcom.com + + project lead + + + + garyrussell + Gary Russell + github@gprussell.net + + project lead emeritus + + + + markfisher + Mark Fisher + mark.ryan.fisher@gmail.com + + project founder and lead emeritus + + + + cppwfs + Glenn Renfro + glenn.renfro@broadcom.com + + project committer + + + + + scm:git:scm:git:git://github.com/spring-projects/spring-integration-samples.git + scm:git:scm:git:ssh://git@github.com:spring-projects/spring-integration-samples.git + https://github.com/spring-projects/spring-integration-samples + + + GitHub + https://github.com/spring-projects/spring-integration-samples/issues + + + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.grpc + spring-grpc-client-spring-boot-starter + + + org.springframework.integration + spring-integration-grpc + + + com.google.protobuf + protobuf-java + 4.29.4 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.hamcrest + hamcrest-library + 2.2 + test + + + org.mockito + mockito-core + 5.21.0 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.integration + spring-integration-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.grpc + spring-grpc-test + test + + + org.springframework.grpc + spring-grpc-server-spring-boot-starter + test + + + org.junit.jupiter + junit-jupiter-engine + runtime + + + org.junit.platform + junit-platform-launcher + runtime + + + + + + org.springframework.integration + spring-integration-jdbc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ws + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-redis + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-r2dbc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-rsocket + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-event + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-cassandra + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-scripting + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-websocket + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-graphql + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jpa + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-test + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-cloudevents + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-http + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zookeeper + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ip + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mail + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-camel + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mongodb + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-stream + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-stomp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-file + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-syslog + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-smb + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jmx + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-webflux + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-sftp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mqtt + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ftp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-kafka + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-groovy + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-xmpp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-feed + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-debezium + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zeromq + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-hazelcast + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-test-support + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-amqp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-grpc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-xml + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-core + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zip + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jms + 7.1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-dependencies + 4.1.0-SNAPSHOT + import + pom + + + org.springframework.grpc + spring-grpc-dependencies + 1.0.2 + import + pom + + + org.junit + junit-bom + 6.0.2 + import + pom + + + tools.jackson + jackson-bom + 3.0.3 + import + pom + + + com.fasterxml.jackson + jackson-bom + 2.21.0 + import + pom + + + org.springframework + spring-framework-bom + 7.0.5 + import + pom + + + org.springframework.integration + spring-integration-bom + 7.1.0-SNAPSHOT + import + pom + + + + + 17 + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:4.29.4:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:1.70.0:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + org.springframework.boot + spring-boot-starter-parent + 4.1.0-SNAPSHOT + + + + repo.spring.io.milestone + Spring Framework Maven Milestone Repository + https://repo.spring.io/milestone + + + repo.spring.io.snapshot + Spring Framework Maven Snapshot Repository + https://repo.spring.io/snapshot + + + diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java index 1d17e8a6a..9e109ce05 100644 --- a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.integration.samples.grpc.client; -import java.util.Collections; - import io.grpc.ManagedChannel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -55,17 +53,21 @@ ManagedChannel managedChannel(GrpcChannelFactory factory) { /** * Create an integration flow for outbound gRPC requests with single responses. + * This flow specifies the method name to be used. * @param managedChannel the gRPC managed channel * @return the integration flow */ @Bean IntegrationFlow grpcOutboundFlowSingleResponse(ManagedChannel managedChannel) { return flow -> flow - .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class)); + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) + .methodName("SayHello")); } /** * Create an integration flow for outbound gRPC requests with streaming responses. + * This flow does not specify the method name to be used, this means that the method + * name is extracted from the message headers. * @param managedChannel the gRPC managed channel * @return the integration flow */ @@ -87,7 +89,6 @@ ApplicationRunner grpcClientSingleResponse( return args -> { HelloReply reply = new MessagingTemplate().convertSendAndReceive(grpcInputChannelSingleResponse, HelloRequest.newBuilder().setName("Jack").build(), - Collections.singletonMap(GrpcHeaders.SERVICE_METHOD, "SayHello"), HelloReply.class); LOGGER.info("Single response reply: " + (reply != null ? reply.getMessage() : "No reply received")); diff --git a/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java index 8e3d4b446..3003759e3 100644 --- a/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java +++ b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java @@ -26,6 +26,8 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.grpc.server.NettyGrpcServerFactory; import org.springframework.test.annotation.DirtiesContext; import static org.assertj.core.api.Assertions.assertThat; @@ -37,8 +39,8 @@ * @author Glenn Renfro */ @SpringBootTest(properties = { - "spring.grpc.server.inprocess.name=test", - "spring.grpc.client.channels.local.address=in-process:test" + "spring.grpc.test.inprocess.enabled=true", + "spring.grpc.test.inprocess.exclusive=true" }) @DirtiesContext @ExtendWith(OutputCaptureExtension.class) @@ -97,6 +99,16 @@ public void streamHello(HelloRequest request, }; } + /** + * Override the Netty server factory to return null, preventing Netty server creation. + * @return null to disable Netty server + */ + @Bean + @Primary + NettyGrpcServerFactory nettyGrpcServerFactory() { + return null; + } + } } From e3b136b0e2e3ace371859e9c3bfa8a1869faec13 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 19 Mar 2026 18:06:16 -0400 Subject: [PATCH 6/7] Remove the netty bean from test exclude netty dependencies from test --- .../samples/grpc/client/GrpcClientTests.java | 13 ------------- build.gradle | 5 +++++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java index 3003759e3..2d479852b 100644 --- a/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java +++ b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java @@ -26,8 +26,6 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.grpc.server.NettyGrpcServerFactory; import org.springframework.test.annotation.DirtiesContext; import static org.assertj.core.api.Assertions.assertThat; @@ -98,17 +96,6 @@ public void streamHello(HelloRequest request, } }; } - - /** - * Override the Netty server factory to return null, preventing Netty server creation. - * @return null to disable Netty server - */ - @Bean - @Primary - NettyGrpcServerFactory nettyGrpcServerFactory() { - return null; - } - } } diff --git a/build.gradle b/build.gradle index a22236b1b..b67943fc5 100644 --- a/build.gradle +++ b/build.gradle @@ -898,6 +898,11 @@ project('grpc-client') { //Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation "org.springframework.grpc:spring-grpc-test" + + configurations { + testImplementation.exclude group: 'io.grpc', module: 'grpc-netty' + testRuntimeOnly.exclude group: 'io.grpc', module: 'grpc-netty' + } } From b1c89ec4c08961df4e7b88a209929f882ee0aab7 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Fri, 20 Mar 2026 06:53:03 -0400 Subject: [PATCH 7/7] Remove testImplementation exclusion it was unecessary --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index b67943fc5..8be0c41b0 100644 --- a/build.gradle +++ b/build.gradle @@ -900,7 +900,6 @@ project('grpc-client') { testImplementation "org.springframework.grpc:spring-grpc-test" configurations { - testImplementation.exclude group: 'io.grpc', module: 'grpc-netty' testRuntimeOnly.exclude group: 'io.grpc', module: 'grpc-netty' } }