Skip to content

Make the default gRPC client stub factory configurable via application properties #250

@ch200203

Description

@ch200203

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

  1. New properties

    • Add default-stub-factory (String/enum-like) and optional default-stub-factory-class (FQCN) to the client properties under spring.grpc.client.*.
  2. Wiring in ClientScanConfiguration

    • Read the new property via Binder and map it to a StubFactory class.
    • Keep per-client explicit configuration (if any) taking precedence.

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=coroutineCoroutineStubFactory is used for scanned clients.
    • FQCN override wins over the named option.
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions