-
Notifications
You must be signed in to change notification settings - Fork 59
Description
Summary
When auto-registering gRPC client stubs, Spring gRPC currently defaults to a blocking stub factory. It would be helpful to make the global default stub factory configurable via application.properties
/application.yml
, aligning with Spring Boot’s convention-over-configuration approach. This lets users switch the default to a coroutine
/reactive
/future
... stub without additional bean wiring. The project already provides automatic client configuration and client properties for channels; this proposal adds one more property to control the default stub type.
Motivation
Projects often prefer a non-blocking model by default (e.g., Kotlin Coroutine stubs or Reactor stubs). Today, changing the default typically requires custom beans or explicit per-client configuration. A property-based global default would:
- Reduce boilerplate and improve DX
- Make behavior more discoverable
- Stay consistent with existing
spring.grpc.client.*
properties.
Current Behavior
Client auto-registration scans packages and registers client stubs with a default (blocking) stub factory, hard-coded in ClientScanConfiguration. There is no documented property to change this global default; users must override with custom beans or per-stub configuration.
Code Reference (current)
In ClientScanConfiguration.java
, the default factory is hardcoded as BlockingStubFactory (note the inline TODO already hints at making this configurable):
@Override
protected GrpcClientRegistrationSpec[] collect(AnnotationMetadata meta) {
Binder binder = Binder.get(this.environment);
boolean hasDefaultChannel = binder.bind("spring.grpc.client.default-channel", ChannelConfig.class)
.isBound();
if (hasDefaultChannel) {
List<String> packages = new ArrayList<>();
if (AutoConfigurationPackages.has(this.beanFactory)) {
packages.addAll(AutoConfigurationPackages.get(this.beanFactory));
}
// TODO: change global default factory type in properties maybe?
return new GrpcClientRegistrationSpec[] { GrpcClientRegistrationSpec.of("default")
.factory(BlockingStubFactory.class)
.packages(packages.toArray(new String[0])) };
}
return new GrpcClientRegistrationSpec[0];
}
Proposed Change
Introduce a new property to select the global default client stub factory:
# Suggested options
spring.grpc.client.default-stub-factory=blocking | async | future | coroutine | reactor #(sample)
# Optional escape hatch for custom factories (overrides the enum-like setting)
spring.grpc.client.default-stub-factory-class=com.example.CustomStubFactory
- Default:
blocking
(preserves current behavior). - Scope: Used when a stub type is not explicitly specified and when auto-registration builds
GrpcClientRegistrationSpec
entries from scans.
Implementation Sketch
-
New properties
- Add
default-stub-factory
(String/enum-like) and optionaldefault-stub-factory-class
(FQCN) to the client properties underspring.grpc.client.*
.
- Add
-
Wiring in
ClientScanConfiguration
- Read the new property via
Binder
and map it to aStubFactory
class. - Keep per-client explicit configuration (if any) taking precedence.
- Read the new property via
Backwards Compatibility
- Default remains
blocking
. - Only users opting in via property see different behavior.
Example Usage
# Kotlin-first projects
spring.grpc.client.default-stub-factory=coroutine
spring:
grpc:
client:
default-stub-factory: reactor
Tests
-
Auto-configuration tests:
- Absent property →
BlockingStubFactory
. default-stub-factory=coroutine
→CoroutineStubFactory
is used for scanned clients.- FQCN override wins over the named option.
- Absent property →
-
Property binding and failure cases (invalid FQCN → fallback to default).
-
A small sample demonstrating property-driven default.
If this proposal sounds reasonable, I’d be happy to implement it and open a PR for review.