From spring
Implement protobuf-first gRPC servers and clients in Spring with generated stubs, configured channels, interceptors, and explicit deadlines, metadata, and reflection. Use this skill when implementing protobuf-first gRPC servers or clients in a Spring application, generating stubs, configuring channels, applying interceptors, and controlling deadlines, metadata, and reflection.
npx claudepluginhub ririnto/sinon --plugin springThis skill uses the workspace's default tool permissions.
Use this skill when implementing protobuf-first gRPC servers or clients in a Spring application, generating stubs, configuring channels, applying interceptors, and controlling deadlines, metadata, and reflection.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill when implementing protobuf-first gRPC servers or clients in a Spring application, generating stubs, configuring channels, applying interceptors, and controlling deadlines, metadata, and reflection.
The latest stable Spring gRPC starter line is 1.0.3. The official reference is currently ahead on the 1.1.0-M1 line, where Spring Boot autoconfiguration and starters move into Spring Boot itself, so the ordinary path in this skill stays on the published 1.0.3 starter artifacts unless the project is intentionally opting into the milestone branch.
Use spring-grpc for gRPC transport, generated protobuf stubs, Spring-managed gRPC services, channel configuration, request metadata, and gRPC-specific error handling.
The ordinary Spring gRPC job is:
.proto contract first and generate Java stubs before writing Spring code.@ImportGrpcClients for the ordinary path, and fall back to explicit @Bean stub creation only when the channel or stub needs custom construction.| Situation | Stay here or open a branch |
|---|---|
| Stable server or client implementation using published starters | Stay in SKILL.md |
| Project explicitly adopts the 1.1.0-M1 milestone line | Use the milestone branch below and re-verify Boot ownership of starters |
| Unary request-response is enough | Stay in SKILL.md |
| Streaming RPCs or async stubs are required | Open references/streaming-and-async-stubs.md |
| TLS, mTLS, bearer tokens, or OAuth2 are the blocker | Open references/security-tls-mtls.md |
| Situation | Use |
|---|---|
| Application only serves gRPC | server starter |
| Application only calls another gRPC service | client starter |
| Same application serves and calls gRPC | both starters |
| Operators or local tooling need descriptor discovery | add reflection support |
Keep reflection and optional support services out of the default path unless a concrete runtime need exists.
Use only the starter set the application actually needs on the stable 1.0.3 line.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-dependencies</artifactId>
<version>1.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Keep starter coordinates versionless underneath the BOM.
<dependencies>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-server-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-client-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-client-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
</dependency>
</dependencies>
Add grpc-services only when reflection, health, or other optional gRPC support services are needed.
Generate Java types from .proto files before implementing services or clients.
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:4.30.2:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.72.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Check generated sources into the ordinary build output, not into hand-maintained source folders. If the project intentionally tracks generated sources in VCS, keep the .proto contract and generated stubs in the same reviewed change.
The 1.1.0-M1 BOM and spring-grpc-core are published, but the dedicated server and client starter artifacts stop at 1.0.3 because the 1.1 line moves starters and autoconfiguration into Spring Boot. Treat that milestone path as an explicit compatibility branch, not the default path for this skill.
./mvnw test -Dtest=GreeterServiceIntegrationTests
./gradlew test --tests GreeterServiceIntegrationTests
spring:
grpc:
server:
port: 9090
spring:
grpc:
client:
channels:
greeter:
address: static://localhost:9090
Start with explicit static addresses in local development. Add service discovery or advanced channel customization only when the deployment actually needs it.
| Situation | Use |
|---|---|
| Standard blocking stubs from generated types | @ImportGrpcClients |
| Need to target one generated stub explicitly | @ImportGrpcClients(types = GreeterGrpc.GreeterBlockingStub.class) |
| Need custom bean naming or multiple variants | @ImportGrpcClients(prefix = "secure", ...) or explicit @Bean creation |
| Need custom channel construction or per-stub tuning | explicit @Bean with GrpcChannelFactory |
The default client path is importing generated blocking stubs into the Spring context. Use manual stub beans only when the client setup needs more control than the import path provides.
.proto file first and keep field numbers stable once the contract is published.@GlobalServerInterceptor and @GlobalClientInterceptor, then add per-service interceptors with @GrpcService(interceptors = ..., blendWithGlobalInterceptors = true) only when one service needs extra behavior.@GrpcService classes thin and delegate to application services for real work.GrpcExceptionHandler or @GrpcExceptionHandler when several handlers need the same status-mapping rule.| Situation | Use |
|---|---|
| Simple request-response boundary | unary RPC with a blocking stub |
| One request returns many messages | server streaming |
| Client uploads many items before one response | client streaming |
| Both sides need a long-lived conversation | bidirectional streaming |
| Caller must overlap many remote calls | future or async stub |
Unary request-response is the ordinary path. Open the streaming reference only when the contract or caller model genuinely needs a non-unary RPC style.
| Situation | Guidance |
|---|---|
| Invalid client input | map to Status.INVALID_ARGUMENT |
| Missing or denied access | map to the matching gRPC status instead of generic runtime exceptions |
| Cross-service call | set an explicit deadline |
| Correlation or tracing data | attach metadata at the transport boundary |
| Same exception rule across multiple handlers | centralize it in GrpcExceptionHandler or @GrpcExceptionHandler |
| One service needs extra transport policy | use @GrpcService(interceptors = ...) instead of copying logic into business code |
| Situation | Use |
|---|---|
| Server should report serving state | server health support |
| Actuator health should include selected gRPC services | spring.grpc.server.health.actuator.health-indicator-paths |
| Client should stop calling an unhealthy upstream | per-channel client health checks |
| Actuator is already on the classpath | use the autoconfigured observability interceptor |
Server health and client health are separate concerns. Server health publishes service state, while client health decides whether a channel should keep using an upstream endpoint.
When Spring Boot Actuator is already present, prefer the framework-provided gRPC observability integration over hand-rolled interceptors for metrics or tracing.
management:
endpoints:
web:
exposure:
include: health, metrics
Keep custom interceptors for correlation ids, authorization, or request policy. Do not duplicate observability behavior in a second interceptor chain unless the deployment has a concrete requirement that the default integration cannot satisfy.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.grpc";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
@GrpcService(interceptors = LoggingInterceptor.class)
class GreeterService extends GreeterGrpc.GreeterImplBase {
private final GreetingApplicationService greetingService;
GreeterService(GreetingApplicationService greetingService) {
this.greetingService = greetingService;
}
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage(greetingService.greet(request.getName()))
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
@SpringBootApplication
@ImportGrpcClients(types = GreeterGrpc.GreeterBlockingStub.class)
class GrpcApplication {
}
@Configuration
class GrpcClientConfig {
@Bean
GreeterGrpc.GreeterBlockingStub greeterStub(GrpcChannelFactory channels) {
return GreeterGrpc.newBlockingStub(channels.createChannel("greeter"));
}
}
@Service
class GreetingClient {
private final GreeterGrpc.GreeterBlockingStub greeter;
GreetingClient(GreeterGrpc.GreeterBlockingStub greeter) {
this.greeter = greeter;
}
String greet(String name) {
HelloReply reply = greeter
.withDeadlineAfter(2, TimeUnit.SECONDS)
.sayHello(HelloRequest.newBuilder().setName(name).build());
return reply.getMessage();
}
}
@Bean
@GlobalServerInterceptor
ServerInterceptor correlationInterceptor() {
return new ServerInterceptor() {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
String correlationId = headers.get(Metadata.Key.of("x-correlation-id", Metadata.ASCII_STRING_MARSHALLER));
return next.startCall(call, headers);
}
};
}
@Bean
GrpcExceptionHandler<IllegalArgumentException, HelloRequest> invalidArgumentHandler() {
return (exception, request) -> Status.INVALID_ARGUMENT
.withDescription(exception.getMessage());
}
throw Status.INVALID_ARGUMENT
.withDescription("name must not be blank")
.asRuntimeException();
spring:
grpc:
client:
channels:
greeter:
address: static://localhost:9090
greeter.withDeadlineAfter(2, TimeUnit.SECONDS)
spring:
grpc:
server:
reflection:
enabled: true
Keep this as an opt-in local tooling shape rather than the ordinary default.
spring:
grpc:
server:
health:
enabled: true
actuator:
health-indicator-paths: Greeter
health-indicator-paths is the list of gRPC service paths the Actuator health bridge should report. Keep it intentional instead of publishing every service by default.
spring:
grpc:
client:
channels:
greeter:
address: static://localhost:9090
health:
enabled: true
Client health checks are optional and should be enabled only when the upstream exposes the health service and the deployment wants the channel to react to serving state.
If the upstream does not publish the gRPC health service, leave client health disabled and rely on explicit deadlines, retries, or transport-level failure handling instead.
@Bean
@Lazy
GreeterGrpc.GreeterBlockingStub greeterStub(GrpcChannelFactory channels, @LocalGrpcPort int port) {
return GreeterGrpc.newBlockingStub(channels.createChannel("static://localhost:" + port));
}
.proto compilation produces the generated stubs used by the server and client code..proto contract stay aligned.@GrpcExceptionHandler, or different behavior per service.@LocalGrpcPort, or explicit test-only channel wiring.