Skip to content

Add gRPC server and client samples with Spring Integration#377

Closed
cppwfs wants to merge 4 commits intospring-projects:mainfrom
cppwfs:SIS-add-grpc-samples
Closed

Add gRPC server and client samples with Spring Integration#377
cppwfs wants to merge 4 commits intospring-projects:mainfrom
cppwfs:SIS-add-grpc-samples

Conversation

@cppwfs
Copy link
Contributor

@cppwfs cppwfs commented Feb 12, 2026

Implement gRPC examples demonstrating Spring Integration's gRPC support for both server and client scenarios.

Server implementation:

  • Add GrpcInboundGateway with service routing for multiple RPC patterns
  • Implement unary RPC and server streaming streaming methods
  • Configure HelloWorldService with message routing based on service method headers

Client implementation:

  • Add GrpcOutboundGateway for unary and streaming response handling
  • Implement ApplicationRunner beans to demonstrate both communication patterns

@rishiraj88
Copy link

Looks promising. Thanks.

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also would be great to have tests for these new examples.
This way the samples project would serve a role of smoke tests for core project.
Thanks

springBootVersion=4.1.0-SNAPSHOT
protobufVersion=4.29.4
protobufPluginVersion=0.9.4
grpcVersion=1.78.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not where we keep versions.
See ext block in the build.gradle.
And I guess we don't need protobufPluginVersion variable since it can simply go to the plugin definition itself.

build.gradle Outdated

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation "org.springframework.integration:spring-integration-grpc:7.1.0-SNAPSHOT"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need explicit SI version here?
More over there is a dedicated variable in the ext block.

build.gradle Outdated
//Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.integration:spring-integration-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need this, then it probably should go to the common deps section for all the projects.

build.gradle Outdated

//Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.integration:spring-integration-test'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one has to be in the common deps section.
If that is not the case, we should consider to improve the project.

build.gradle Outdated

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation "org.springframework.integration:spring-integration-grpc:7.1.0-SNAPSHOT"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you fix the server sample, I guess same has to be done for the client one, too.

@@ -0,0 +1 @@
spring.grpc.server.port=9090 No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New line in the end of each file.
I believe there supposed to be an option in IntelliJ IDEA.

MessageChannel grpcInputChannelStreamResponse,
FluxMessageChannel grpcStreamOutputChannel) {
//TODO: Need advice, there has to be a better way than what I have below (or is this a bug in the
// GrpcOutboundGateway?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Let's debug it together to see what is going on!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need this anymore.
Plus, the gateway could be created from the Grpc DSL factory.

MessageChannel grpcInputChannelStreamResponse,
FluxMessageChannel grpcStreamOutputChannel) {
//TODO: Need advice, there has to be a better way than what I have below (or is this a bug in the
// GrpcOutboundGateway?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need this anymore.
Plus, the gateway could be created from the Grpc DSL factory.

* @author Glenn Renfro
*/
@SpringBootTest(properties = {
"spring.main.web-application-type=none",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't use web, then this looks suspicious.
As well as the next line.
Why do we need to override some bean?


@Bean
@Primary
public ApplicationRunner grpcClientStreamResponse() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we need to override these beans for test?
Feels like the sample is very complex, not something what users would expect coming here for beginner's knowledge

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, elaborate why these bean overrides were not removed.

/**
* @author Glenn Renfro
*/
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure what web is doing in these samples...

@@ -1,5 +1,6 @@
version=7.1.0
springBootVersion=4.1.0-SNAPSHOT
protobufVersion=4.29.4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How that happened that grpcVersion has gone to ext, but not this?

@BeforeEach
void setUp() {
Object server = ReflectionTestUtils.getField(this.grpcServerLifecycle, "server");
this.grpcServerPort = ReflectionTestUtils.invokeMethod(server, "getPort");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something more convenient has to be Spring gRPC, rather than such a hack with reflection.

private static final Log LOG = LogFactory.getLog(ClientHelloWorldConfiguration.class);

/**
* Creates a managed gRPC channel for communication with the server.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not imperative?
This is a method and this is a sample.
Why give readers of these samples a wrong impression about Spring code style and consistency?
Everything what we do here is for community and Spring newcomers.
Would be great to teach them a right lesson and don't overcomplicate the sample code scary impression reasons.

So, see also about those blank lines in method Javadocs.

*/
@Bean(destroyMethod = "shutdownNow")
ManagedChannel managedChannel(@Value("${grpc.server.host:localhost}") String host,
@Value("${grpc.server.port:9090}") int port) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked not one time: if method declaration is multi-line, a blank line before before method body.

I also wonder if there are some Spring gRPC auto-configuration for clients.
If there is really a reason to have our own ManagedChannel and those props with hard-coded defaults in the bean definition.

*
* @param grpcInputChannelStreamResponse the message channel for streaming response requests
* @param grpcStreamOutputChannel channel that contains the responses
* @param replyTimeout the time in seconds to await for the response. Defaults to 1 second.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no consistency?
The previous example talks about milliseconds instead.

.doOnSubscribe(subscription -> {
HelloRequest request = HelloRequest.newBuilder().setName("Jack").build();
Message<?> requestMessage = MessageBuilder.withPayload(request).build();
grpcInputChannelStreamResponse.send(requestMessage);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we do setReplyChannel(replyChannel), this should go outside of the Flux handling.
We can subscribe() and then send: exactly the way how it is recommended by Project Reactor.


package integration.grpc.hello;

option java_package = "org.springframework.integration.grpc.proto";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to double check if these packages have a proper name.
Not sure if without a sample sub-package it is OK to deliver.


@Bean
@Primary
public ApplicationRunner grpcClientStreamResponse() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, elaborate why these bean overrides were not removed.


@DynamicPropertySource
static void grpcServerProperties(DynamicPropertyRegistry registry) throws IOException {
mockGrpcServer = ServerBuilder.forPort(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not correct place to declare server instance.
Plus I wonder if it really should be a socket-based, but not InProcessServerBuilder, like we do in Spring Integration tests.
And another thought: I wonder if there is some Spring gRPC auto-configuration for testing to avoid a lot of boilerplate code like this to confuse end-users.

@@ -0,0 +1 @@
spring.grpc.server.port=9090
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that a default value?
Why do we need to declare this property here at all?
Probably mention Spring gRPC docs in the README to this sample should be enough.


@BeforeEach
void setUp() {
this.grpcServerPort = this.grpcServerLifecycle.getPort();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like we need the port as a property.
More over, I believe there should be some channel auto-configuration in Spring gRPC to avoid duplication.
Plus stubbing also can be done by Spring gRPC.
See their docs for more info.

cppwfs added 4 commits March 2, 2026 09:40
Implement gRPC examples demonstrating Spring Integration's
gRPC support for both server and client scenarios.

Server implementation:
- Add `GrpcInboundGateway` with service routing for multiple RPC patterns
- Implement unary RPC and server streaming streaming methods
- Configure HelloWorldService with message routing based on service method headers

Client implementation:
- Add `GrpcOutboundGateway` for unary and streaming response handling
- Implement ApplicationRunner beans to demonstrate both communication patterns
- Removed POC experimental code
Replace manual channel/server setup in gRPC client and server
samples with Spring gRPC's `GrpcChannelFactory` and in-process
testing utilities to improve alignment with Spring conventions
and reduce boilerplate.

- Add `@EnableIntegration` to `ClientHelloWorldConfiguration`
- Remove unused bidirectional and client-streaming RPC definitions
  from `hello.proto` in both client and server modules
- Refactor `GrpcClientTests` to use an in-process gRPC server
@cppwfs cppwfs force-pushed the SIS-add-grpc-samples branch from 8dbc2d6 to 25637a1 Compare March 2, 2026 15:13
Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!
Very close, but still there are some questions.

@SpringBootApplication
public final class Application {

private Application() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was not addressed.

*/
@Bean
ManagedChannel managedChannel(GrpcChannelFactory factory) {
return factory.createChannel("local");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there an auto-configuration in Spring gRPC on the matter?

syntax = "proto3";
import "types.proto";

package integration.grpc.hello;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to revise this package as well?

/**
* @author Glenn Renfro
*/
@SpringBootTest(properties = {"spring.grpc.server.enabled=false"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this instead of auto-configuration suggested by Spring gRPC?


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation "org.springframework.integration:spring-integration-grpc:$springIntegrationVersion"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need an explicit version for Spring Integration dependencies since we rely on Spring Boot management.

@SpringBootApplication
public final class Application {

private Application() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also suspicious ctor.
How it happened here?
I never saw start.spring.io does that for us.


@Bean
GrpcInboundGateway helloWorldService() {
GrpcInboundGateway gateway = new GrpcInboundGateway(HelloWorldServiceGrpc.HelloWorldServiceImplBase.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is what we want here in the test.
I believe just regular gRPC HelloWorldServiceImplBase implementation as a separate class in this test scope would be enough.
I mean that is fine that we verify Spring Integration server side, but that should not be a goal of the test in the sample for client side.

return flow -> flow
.handle(Grpc.outboundGateway(managedChannel, HelloWorldServiceGrpc.class)
.methodName("StreamSayHello"))
.channel(grpcStreamOutputChannel);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you do .setReplyChannel(grpcStreamOutputChannel) in the respective ApplicationRunner impl.
So, this one must not be here, but just fully similar to the grpcOutboundFlowSingleResponse configuration.


```properties
grpc.server.host=localhost
grpc.server.port=9090
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May it worth to have such an application.properties file in our sample?


$ gradlew :grpc-server:bootRun

The server will start on port 9090 by default (configured in `application.properties`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we do have such a file in the sample, but it is empty.
Should we add a respective property to let end-user to override it to something they want to play with?

@cppwfs
Copy link
Contributor Author

cppwfs commented Mar 2, 2026

I'm going to close this PR. I want to tackle it from a different angle from the current solution.

@cppwfs cppwfs closed this Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants