diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java
index 39ee2273c9d..9b1a6e59c4c 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java
@@ -161,6 +161,9 @@ public class AntoraAsciidocAttributes {
});
attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion"));
attributes.put("version-graal", (String) this.projectProperties.get("graalVersion"));
+ attributes.put("version-protobuf-gradle-plugin",
+ (String) this.projectProperties.get("protobufGradlePluginVersion"));
+ addDependencyVersion(attributes, "grpc-api", "io.grpc:grpc-api");
addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations");
addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core");
addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind");
diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties
index f64a46c277c..1797a4028fd 100644
--- a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties
+++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties
@@ -26,6 +26,7 @@ url-gradle-docs-java-plugin={url-gradle-docs}/java_plugin.html
url-gradle-docs-war-plugin={url-gradle-docs}/war_plugin.html
url-gradle-dsl=https://docs.gradle.org/current/dsl
url-gradle-javadoc=https://docs.gradle.org/current/javadoc
+url-grpc-api-javadoc=https://javadoc.io/doc/io.grpc/grpc-api/{version-grpc-api}
url-kotlin-docs-kotlin-plugin={url-kotlin-docs}/using-gradle.html
url-micrometer-docs-concepts={url-micrometer-docs}/concepts
url-micrometer-docs-implementations={url-micrometer-docs}/implementations
@@ -82,6 +83,8 @@ url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-d
url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml}
url-jackson2-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson2-databind}
+https://javadoc.io/doc/io.grpc/grpc-api/latest/io/grpc/package-summary.html
+
# === Javadoc Locations ===
javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc}
@@ -104,6 +107,7 @@ javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc}
javadoc-location-tools-jackson-core={url-jackson-core-javadoc}
javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc}
javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc}
+javadoc-location-io-grpc={url-grpc-api-javadoc}
# === API References ===
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java
index b8506232ba7..ed228c2bf72 100644
--- a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java
@@ -286,6 +286,7 @@ class AntoraAsciidocAttributesTests {
addMockJacksonCoreVersion(versions, "jackson-core", version);
addMockJacksonCoreVersion(versions, "jackson-databind", version);
addMockJacksonCoreVersion(versions, "jackson-databind", version);
+ versions.put("io.grpc:grpc-api", version);
versions.put("org.apache.pulsar:pulsar-client-api", version);
versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version);
return versions;
diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle
index 2d739b45907..cf6ab55c302 100644
--- a/documentation/spring-boot-docs/build.gradle
+++ b/documentation/spring-boot-docs/build.gradle
@@ -149,6 +149,8 @@ dependencies {
implementation("ch.qos.logback:logback-classic")
implementation("com.redis:testcontainers-redis")
implementation("com.zaxxer:HikariCP")
+ implementation("io.grpc:grpc-stub")
+ implementation("io.grpc:grpc-netty")
implementation("io.micrometer:micrometer-jakarta9")
implementation("io.micrometer:micrometer-tracing")
implementation("io.micrometer:micrometer-registry-graphite")
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc
new file mode 100644
index 00000000000..1289ead9474
--- /dev/null
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc
@@ -0,0 +1,743 @@
+[[io.grpc]]
+= gRPC
+
+Google Remote Procedure Call (gRPC) is a high-performance RPC framework that enables client-server communication using binary messages.
+Spring Boot include support for developing and testing both client and server gRPC applications.
+
+The underling message format used by gRPC is Protocol Buffers which allow messages to be created and consumed by a wide variety of programming languages.
+
+
+
+[[io.grpc.servicedefinitions]]
+== Service Definitions
+
+To develop a gRPC application you first need a Protocol Buffers service definition file.
+A `.proto` file defines the services and messages that your application can consume or provide.
+
+Here's an example of a typical `.proto` file that uses https://protobuf.dev/programming-guides/proto3/[the `proto3` revision] of the protocol buffers language:
+
+[,protobuf]
+----
+syntax = "proto3";
+
+option java_package = "com.example.grpc.proto";
+option java_multiple_files = true;
+
+service HelloWorld {
+ rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloReply {
+ string message = 1;
+}
+----
+
+This file defines a `HelloWorld` service with a single method that accepts a `HelloReqest` message and return a `HelloReply` message.
+The `HelloReqest` message contains a `name` string field.
+The `HelloReply` message contains a `message` string field.
+
+With the exception of a the `java_package` and `java_multiple_files` options, there is nothing in the `.proto` file that is specific to the Java programming langage.
+
+
+
+[[io.grpc.servicedefinitions.generatingjavacode]]
+=== Generating Java Code
+
+Since `.proto` files are language agnostic, we need a process to convert them into usable Java code.
+We can then use the generated code to either make a remote procedure call to running service, or implement the service ourselves so that others may call it.
+
+The exact process you use to generate code will depend on your build system.
+Spring Boot supports for both Maven and Gradle protobuf plugins, but you are free to use whatever solution works best for you.
+
+
+
+[[io.grpc.servicedefinitions.generatingjavacode.maven]]
+==== Using the Maven Plugin
+
+Spring Boot include dependency management for the `io.github.ascopes:protobuf-maven-plugin` Maven plugin.
+If you are using the the `spring-boot-starter-parent` POM, you'll also get sensible out-of-the-box configuration.
+
+The following shows a typical Maven POM file that uses the plugin:
+
+[source,xml,subs="verbatim,attributes"]
+----
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ {version-spring-boot}
+
+ com.example
+ myproject
+ 0.0.1-SNAPSHOT
+
+
+
+
+ io.github.ascopes
+ protobuf-maven-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"]
+
+
+
+ spring-snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+
+
+ spring-snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+endif::[]
+
+----
+
+Since POM above extends `spring-boot-starter-parent`, you'll get the following:
+
+* Configuration of the `protoc` version.
+* Configuration of the `binary-maven` plugin.
+* Execution configuration for the `generate` goal.
+
+The `.proto` files should be added to `src/main/proto`.
+
+TIP: If you don't use `spring-boot-starter-parent`, or you want to configure the plugin directly, refer to the https://ascopes.github.io/protobuf-maven-plugin[protobuf-maven-plugin documentation].
+If you use Spring Boot's dependency management the `${protobuf-java.version}` and `${grpc-java.version}` properties will be useful.
+
+
+
+[[io.grpc.servicedefinitions.generatingjavacode.gradle]]
+==== Using the Gradle Plugin
+
+Spring Boot include dependency management for the `com.google.protobuf:protobuf-gradle-plugin` Gradle plugin.
+In addition, the `spring-boot-gradle-plugin` will react to the presence of the protobuf plugin and configure it appropriately.
+
+The following shows a typical Gradle file that uses the plugin:
+
+[source,gradle,subs="verbatim,attributes"]
+----
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '{version-spring-boot}'
+ id 'io.spring.dependency-management' version '{version-dependency-management-plugin}'
+ id 'com.google.protobuf' version '{version-protobuf-gradle-plugin}'
+}
+
+group = 'com.example'
+version = '0.0.1-SNAPSHOT'
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
+repositories {
+ mavenCentral()
+ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"]
+ // you don't need this if you are using a release version
+ maven { url 'https://repo.spring.io/snapshot' }
+endif::[]
+}
+----
+
+Since this gradle file uses both the `org.springframework.boot` and `com.google.protobuf` plugins, you'll get the following:
+
+* Configuration of the `protoc` version.
+* Configuration of the `protoc-gen-grpc-java` version.
+
+The `.proto` files should be added to `src/main/proto`.
+
+TIP: If you don't use `org.springframework.boot` plugin, or you want to configure the plugin directly, refer to the https://github.com/google/protobuf-gradle-plugin[protobuf-gradle-plugin documentation].
+
+
+
+[[io.grpc.server]]
+== Writing a gRPC Server Application
+
+Spring Boot provides a `spring-boot-grpc-server` module and a `spring-boot-starter-grpc-server` starter POM that you can use for server applications.
+
+In order to write the actual server code, you'll need to extended one or more of base classes generated from your `.proto` file and expose them as Spring beans.
+Spring gRPC will automatically expose any bean that implements javadoc:io.grpc.BindableService[] as a gRPC server.
+Since all `.proto` generated classes implement javadoc:io.grpc.BindableService[], adding them as beans is enough to expose them over gRPC.
+
+TIP: For more details see {url-spring-grpc-docs}/server.html#_create_a_grpc_service[the Spring gRPC documentation].
+
+The following example shows how the `HelloWorld` service from the `.proto` file above could be implemented.
+In this example, we're using the javadoc:org.springframework.grpc.server.service.GrpcService[format=annotation] annotation and assuming that the code is in a package that will be picked up by component scanning:
+
+include-code::MyHelloWorldService[]
+
+If the application makes used of `spring-boot-starter-grpc-server`, then Netty will be used as the server implementation listening on port `9090`.
+
+You can test your application using https://github.com/fullstorydev/grpcurl[grpcurl]:
+
+[source,shell]
+----
+$ grpcurl -d '{"name":"Spring"}' -plaintext localhost:9090 HelloWorld.SayHello
+----
+
+[source,json]
+----
+{
+ "message": "Hello 'Spring'"
+}
+----
+
+
+
+[[io.grpc.server.netty-shaded]]
+=== Switching to a Netty Shaded Server
+
+If you find that the version of Netty provided by the `spring-boot-starter-grpc-server` starter POM isn't compatible with other libraries you use, you can switch to a "`shaded`" version.
+
+To switch, you can excluded `io.grpc:grpc-netty` and include `io.grpc:grpc-netty-shaded`.
+For example:
+
+
+[tabs]
+======
+
+Maven::
++
+[source,xml]
+----
+
+ org.springframework.boot
+ spring-boot-starter-grpc-server
+
+
+
+ io.grpc
+ grpc-netty
+
+
+
+
+
+ io.grpc
+ grpc-netty-shaded
+
+----
++
+
+Gradle::
++
+[source,gradle]
+----
+dependencies {
+ implementation('org.springframework.boot:spring-boot-starter-grpc-server') {
+ // Exclude the gRPC Netty dependency
+ exclude group: 'io.grpc', module: 'grpc-netty'
+ }
+ // Use gRPC Netty Shaded instead
+ implementation "io.grpc:grpc-netty-shaded"
+}
+----
++
+======
+
+
+
+[[io.grpc.server.servlet]]
+=== Switching to a Servlet Container
+
+It's possible to expose gRPC services using a regular Servlet Container such as Tomcat rather than using Netty.
+To do so, your Servlet Container must be configured to support HTTP/2.
+
+To switch to the Servlet gRPC implementation, you can exclude `io.grpc:grpc-netty` and include `io.grpc:grpc-servlet-jakarta`.
+For example:
+
+[tabs]
+======
+
+Maven::
++
+[source,xml]
+----
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+ org.springframework.boot
+ spring-boot-starter-grpc-server
+
+
+
+ io.grpc
+ grpc-netty
+
+
+
+
+
+ io.grpc
+ grpc-servlet-jakarta
+
+----
++
+
+Gradle::
++
+[source,gradle]
+----
+dependencies {
+ implementation('org.springframework.boot:spring-boot-starter-webmvc') {
+ implementation('org.springframework.boot:spring-boot-starter-grpc-server') {
+ // Exclude the gRPC Netty dependency
+ exclude group: 'io.grpc', module: 'grpc-netty'
+ }
+ // Use gRPC Servlet Jakarta instead
+ implementation "io.grpc:grpc-servlet-jakarta"
+}
+----
++
+======
+
+TIP: Remember to include a Servlet Container dependency, for example using `spring-boot-starter-tomcat`, and to set `server.http2.enabled` to `true`.
+
+NOTE: When using a servlet container, certain gRPC server configuration properties are not relevant and will be ignored.
+For example, configprop:spring.grpc.server.port[] is ignored since configprop:server.port[] used used to set a web server port.
+
+
+
+[[io.grpc.server.ssl]]
+=== SSL Support
+
+SSL can be configured for both `grpc-netty` and `grpc-netty-shaded` servers using SSL bundles.
+See the xref:features/ssl.adoc[SSL core documentation] for details on how to declare an SSL bundle.
+
+Once your bundle has been defined, you can use the following properties in your gRPC server application to use it:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ server:
+ ssl:
+ bundle: mysslbundle
+----
+
+Client authentication can also be configured by setting configprop:spring.grpc.server.ssl.client-auth[] to `optional` or `require`.
+
+TIP: To temporarily disable server SSL support, for example to aid with testing, you can set configprop:spring.grpc.server.ssl.enabled[] to `false`.
+
+
+
+[[io.grpc.server.in-process]]
+=== Using an In-Process Server
+
+You can run an in-process server by including the `io.grpc:grpc-inprocess` dependency on your classpath and defining a configprop:spring.grpc.server.inprocess.name[] property.
+In this mode, the in-process server factory is auto-configured in addition to the regular server factory.
+
+The name you provide can be used as a client channel target using the form `in-process:`.
+
+
+
+[[io.grpc.server.reflection]]
+=== Reflection
+
+When it's available, Spring Boot will auto-configure the https://grpc.io/docs/guides/reflection/[gRPC Reflection service].
+This allows clients to browse the metadata of your services and download their `.proto` files.
+
+The reflection service resides in the `io.grpc:grpc-services` library, which is an optional dependency.
+You will need to add the dependency to your project in order for auto-configuration to apply.
+
+TIP: If you have the `io.grpc:grpc-services` library but prefer that reflection isn't auto-configured, you can set configprop:spring.grpc.server.reflection.enabled[] to `false`.
+
+
+
+[[io.grpc.server.health]]
+=== Server Health
+
+A gRPC server can provide health information using a standard service API (https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto[health/v1]).
+This allows clients to check on the health of your server services a route traffic appropriately.
+
+Spring Boot provides a bridge between its own `spring-boot-health` module and the standard gRPC health service.
+Health information is provided whenever the `io.grpc:grpc-services` and `org.springframework.boot:spring-boot-health` modules are on your classpath.
+
+TIP: If you don't want health indicators to be exposed, you can set configprop:spring.grpc.server.health.enabled[] to `false`.
+
+
+
+[[io.grpc.server.health.service-mappings]]
+==== Service Specific Health Mappings
+
+By default, health information is provided for the overall server status (`""`) using all available health indicators.
+
+It is also possible to provide fine-grained health information for specific services by including only a sub-set of health indicators.
+Custom mapping and ordering rules can also be defined on a per-service basis.
+
+For example, the following configuration will provide health for "`myservice`" using only the `db` and `redis` indicators.
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ server:
+ health:
+ service:
+ myservice:
+ include:
+ - db
+ - redis
+----
+
+TIP: You can set configprop:spring.grpc.server.health.include-overall-health[] to `false` to disable the overall server status health if you only want to provide service-specific health.
+
+
+
+[[io.grpc.server.health.push]]
+==== Push Configuration
+
+Unlike web-based health checks, gRPC health information is periodically pushed rather than pulled.
+By default, the first health push happens 5 seconds after the application starts and then every subsequent 5 seconds.
+
+To fine-tune this, you can use the following properties:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ server:
+ health:
+ schedule:
+ period: 5m
+ delay: 2s
+----
+
+TIP: You can also set configprop:spring.grpc.server.health.schedule.enabled[] to `false` if want to send health updates in some other way.
+
+
+
+[[io.grpc.server.security]]
+=== Securing gRPC Server Applications
+
+
+
+[[io.grpc.server.security.netty]]
+==== Netty Based Servers
+
+Spring gRPC includes features that allow you to secure your Netty based server applications declaratively using Spring Security.
+This follows similar patterns to those you would use to secure a regular web application.
+
+Spring Boot provides auto-configuration for both javadoc:org.springframework.grpc.server.security.GrpcSecurity[] and javadoc:org.springframework.grpc.server.security.SecurityGrpcExceptionHandler[] beans.
+Typically gRPC application are then secured using javadoc:org.springframework.security.access.prepost.PreAuthorize[format=annotation] annotations on your gRPC service beans, or a javadoc:org.springframework.grpc.server.security.AuthenticationProcessInterceptor[] bean.
+
+For more details, please see {url-spring-grpc-docs}/server.html#_declarative_security_with_spring_security[the Spring gRPC documentation].
+
+
+
+[[io.grpc.server.security.servlet]]
+==== Servlet Container Based Servers
+
+If your gRPC server is running xref:web/servlet.adoc[within a standard Servlet Container], you can use typical xref:web/spring-security.adoc#web.security.spring-mvc[web security configuration] to secure your application.
+Spring Boot will auto-configre javadoc:org.springframework.boot.grpc.server.autoconfigure.GrpcServerExecutorProvider[] and javadoc:org.springframework.grpc.server.security.SecurityContextServerInterceptor[] beans to ensure that Spring Security works correctly.
+
+Cross-Site Request Forgery (CSRF) protection is incompatible with the gRPC protocol and will be disabled by default for all gRPC requests.
+If you prefer to configure your own CSRF protection, you can switch this off by setting configprop:spring.grpc.server.security.csrf.enabled[] to `false`.
+
+To help with manual Security configuration, Spring Boot provides request matchers for gRPC services.
+Matches are available for both servlet and reactive stacks.
+For example, the following will include all gRPC services with the exception of "`special`".
+
+include-code::MySecurityConfiguration[]
+
+NOTE: For reactive matches use javadoc:org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest[format=full].
+
+
+
+[[io.grpc.server.security.oauth-resource-server]]
+==== OAuth2 Resource Server
+
+https://oauth.net/2/[OAuth2] is a widely used authorization framework.
+Spring Boot's OAuth2 Resource Server support in compatible with gRPC and may be configured in the usual way.
+
+For details of how to configure an OAuth2 Resource Server to use with your gRPC server application, see the xref:security/oauth2.adoc#security.oauth2.server["`OAuth2`" section] of under "`Security`".
+
+
+
+[[io.grpc.client]]
+== Writing a gRPC Client Application
+
+Spring Boot provides a `spring-boot-grpc-client` module and a `spring-boot-starter-grpc-client` starter POM that you can use for client applications.
+
+Clients can call remote gRPC services by importing one or more of the "`stub`" classes generated from their `.proto` file.
+You can use the javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] annotation to import the stub classes you want to use.
+
+Each import includes a `target` which can either be a logical channel name, or the base URL of the remote server.
+We typically recommend using channel names rather than hard-coding targets.
+
+Here's a typical example:
+
+include-code::MyApplication[]
+
+NOTE: If you don't specify a `target` then "`default`" is used.
+
+TIP: You can use the `basePackageClasses` or `basePackages` attribute of javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] to import all stubs in given package.
+
+
+
+[[io.grpc.client.channel-properties]]
+=== Channel Properties
+
+When the `target` attribute of javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] uses a logical channel name, you'll need to provide some properties so that Spring Boot can find the actual gRPC server to call.
+
+To do that, you can add an entry using `spring.grpc.channel..*` properties.
+Typically, you'll configure the actual real `target`, along with any other settings that are unique to the channel.
+
+For example, the following will configure `myservice` to use the real target of `static://grpc.example.com:9090`.
+It also changes the keep-alive timeout and the maximum message size permitted:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ client:
+ channel:
+ myservice:
+ target: static://grpc.example.com:9090
+ inbound:
+ keepalive:
+ timeout: 40s
+ message:
+ max-size: 8MB
+----
+
+
+[[io.grpc.client.stub-beans]]
+=== Using Stub Beans
+
+With your javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] annotations in place, and your properties written, you can use stubs as you would any other bean.
+
+For example, here's the `HelloWorldStub` being injected into a `ApplicationRunner` bean:
+
+include-code::MyApplicationRunner[]
+
+
+
+[[io.grpc.client.netty-shaded]]
+=== Switching to Netty Shaded Client Transport
+
+Under the hood, remote gRPC network calls are made using Netty.
+If you find that the version of Netty provided by the `spring-boot-starter-grpc-client` starter POM isn't compatible with other libraries you use, you can switch to a "`shaded`" version.
+
+To switch, you can excluded `io.grpc:grpc-netty` and include `io.grpc:grpc-netty-shaded`.
+For example:
+
+
+[tabs]
+======
+
+Maven::
++
+[source,xml]
+----
+
+ org.springframework.boot
+ spring-boot-starter-grpc-client
+
+
+
+ io.grpc
+ grpc-netty
+
+
+
+
+
+ io.grpc
+ grpc-netty-shaded
+
+----
++
+
+Gradle::
++
+[source,gradle]
+----
+dependencies {
+ implementation('org.springframework.boot:spring-boot-starter-grpc-client') {
+ // Exclude the gRPC Netty dependency
+ exclude group: 'io.grpc', module: 'grpc-netty'
+ }
+ // Use gRPC Netty Shaded instead
+ implementation "io.grpc:grpc-netty-shaded"
+}
+----
++
+======
+
+
+[[io.grpc.client.ssl]]
+=== SSL Support
+
+Client gRPC applications can connect to gRPC services using SSL/TSL encrypted connections.
+You can configure gRPC connections to use standard one-way-TLS, or mutual TLS
+
+
+
+[[io.grpc.client.ssl.one-way]]
+==== Standard one-way TLS
+
+To use standard one-way TLS, you can set the `ssl.enabled` property to `true` in your channel properties.
+For example, the following will enabled an SSL/TLS connection for the `myservice` channel:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ client:
+ channel:
+ myservice:
+ target: static://grpc.example.com:9090
+ ssl:
+ enabled: true
+----
+
+
+
+[[io.grpc.client.ssl.mutual]]
+==== Mutual TLS
+
+Mutual TLS (mTLS) is a security protocol that requires both the client and the server to present certificates to each other.
+To use mutual TLS, you can set the `ssl.bundle` property in your channel properties.
+See the xref:features/ssl.adoc[SSL core features documentation] for details on how to declare an SSL bundle.
+
+Here is an example the configures the `myservice` channel to use the `mybundle` bundle for mutual TLS:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ client:
+ channel:
+ myservice:
+ target: static://grpc.example.com:9090
+ ssl:
+ bundle: mybundle
+----
+
+[TIP]
+====
+To temporarily disable client SSL support, for example to aid with testing, you can set `bypass-certificate-validation` to `true` on your channel config:
+
+[configprops,yaml]
+----
+spring:
+ grpc:
+ client:
+ channel:
+ myservice:
+ bypass-certificate-validation: true
+----
+====
+
+
+
+[[io.grpc.client.in-process]]
+=== Using In-Process Channels
+
+You can communicate with an xref:io/grpc.adoc#io.grpc.server.in-process[in-process server] (i.e. not listening on a network port) by including the `io.grpc.grpc-inprocess` dependency on your classpath.
+
+In this mode, the in-process channel factory is auto-configured in addition to the regular channel factories (e.g. Netty).
+To prevent users from having to deal with multiple channel factories, a composite channel factory is configured as the primary channel factory bean.
+The composite consults its composed factories to find the first one that supports the channel target.
+
+To use the in-process server the channel target must be set to `in-process:`
+
+TIP: To disable the in-process channel factory, you can set the configprop:spring.grpc.client.inprocess.enabled[] property to `false`.
+
+
+
+[[io.grpc.client.observability]]
+=== Observability
+
+Spring Boot provides auto-configuration of the javadoc:io.micrometer.core.instrument.binder.grpc.ObservationGrpcClientInterceptor[] whenever Micrometer is available.
+This interceptor provides observability into your gRPC client applications.
+
+TIP: If you use Micrometer, but prefer to not to use it for gRPC, you can set configprop:spring.grpc.client.observation.enabled[] to `false`.
+
+
+
+[[io.grpc.client.channel-customization]]
+=== Channel Customization
+
+If you need to customize your gRPC channel beyond the basic properties, you can use a javadoc:org.springframework.grpc.client.GrpcChannelBuilderCustomizer[].
+Each customizer is called with the logical target name and the javadoc:io.grpc.ManagedChannelBuilder[] that will build the channel.
+There's also a convenient `matching(String pattern)` factory method that will limit customizations to targets that match the given regex pattern.
+
+A common customizer use-case is to add {url-spring-grpc-docs}/client.html#_http_headers[security interceptors] to the builder.
+For example, here we're adding the javadoc:org.springframework.grpc.client.interceptor.security.BearerTokenAuthenticationInterceptor[] to the target matching "`hello`":
+
+include-code::MyGrpcConfiguration[]
+
+
+
+[[io.grpc.testing]]
+== Testing gRPC Applications
+
+To help test your gRPC client and server applications you can use the `spring-boot-grpc-test` module or the `spring-boot-starter-grpc-client-test` / `spring-boot-starter-grpc-server-test` starter POMs.
+
+
+[[io.grpc.testing.test-transport]]
+=== Using In-Process Test Transport
+
+The javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] annotation allows you to quickly replace gRPC communication channels with in-process channels specifically designed for testing.
+Unlike regular in-process channels, these test channels to not require any configuration.
+
+Using test gRPC transport means that you don't need to actually listen on a network port to start your application.
+This allows your tests to run quickly, whilst still ensuring that your application works as expected.
+
+By default, using javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] will:
+
+* Configure test javadoc:org.springframework.grpc.server.GrpcServerFactory[] / javadoc:org.springframework.grpc.client.GrpcChannelFactory[] beans
+* Disable any gRPC servelt registration.
+* Disable javadoc:org.springframework.grpc.server.GrpcServerFactory[] bean auto-configuration.
+* Disable javadoc:org.springframework.grpc.client.GrpcChannelFactory[] bean auto-configuration.
+
+The following example shows how you can use javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] to test a gRPC server application:
+
+include-code::MyGrpcTests[]
+
+
+
+[[io.grpc.testing.local-server-port]]
+=== Testing With a Running Server
+
+If you prefer to test your gRPC application by starting the real server and using the actual network connection, we recommend that you use random ports.
+This will ensure that you can run your tests in any environment, and that you won't accidentally call real services.
+
+To start a gRPC server using a random port, set configprop:spring.grpc.server.port[] to `0`.
+You can use the javadoc:org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort[format=annotation] annotation to obtain the actual port that the server started on.
+
+Here's an example test:
+
+include-code::MyGrpcIntegrationTests[]
+
+
+
+// FIXME https://github.com/spring-projects/spring-grpc/issues/397
\ No newline at end of file
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc
index 3f1235532fd..8b7e069f3cd 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc
@@ -40,6 +40,7 @@
** xref:reference:io/index.adoc[]
*** xref:reference:io/caching.adoc[]
*** xref:reference:io/spring-batch.adoc[]
+*** xref:reference:io/grpc.adoc[]
*** xref:reference:io/hazelcast.adoc[]
*** xref:reference:io/quartz.adoc[]
*** xref:reference:io/email.adoc[]
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc
index 97b9bfe20c7..f21ceb0be5f 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc
@@ -155,7 +155,7 @@ Open your favorite text editor and add the following:
plugins {
id 'java'
id 'org.springframework.boot' version '{version-spring-boot}'
- id 'io.spring.dependency-management' version '{version-dependency-management-plugin}'
+ id 'io.spring.dependency-management' version '{version-dependency-management-plugin}'
}
group = 'com.example'
@@ -170,7 +170,7 @@ java {
repositories {
mavenCentral()
ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"]
- // you don't need this if you are using a release version
+ // you don't need this if you are using a release version
maven { url 'https://repo.spring.io/snapshot' }
endif::[]
}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java
new file mode 100644
index 00000000000..a9d29503cdc
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client;
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java
new file mode 100644
index 00000000000..ed692948df8
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.grpc.client.ImportGrpcClients;
+
+@SpringBootApplication(proxyBeanMethods = false)
+@ImportGrpcClients(target = "hello", types = HelloWorldGrpc.HelloWorldBlockingStub.class)
+public class MyApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MyApplication.class, args);
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java
new file mode 100644
index 00000000000..5d64753c609
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client.channelcustomization;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
+import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor;
+
+@Configuration(proxyBeanMethods = false)
+public class MyGrpcConfiguration {
+
+ @Bean
+ GrpcChannelBuilderCustomizer> helloChannelCustomizer() {
+ return GrpcChannelBuilderCustomizer.matching("hello",
+ (builder) -> builder.intercept(new BasicAuthenticationInterceptor("user", "password")));
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java
new file mode 100644
index 00000000000..5fd2437dda2
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client.stubbeans;
+
+interface HelloReply {
+
+ String getMessage();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setMessage(String message);
+
+ HelloReply build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java
new file mode 100644
index 00000000000..baf2f88e52d
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client.stubbeans;
+
+interface HelloRequest {
+
+ String getName();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setName(String name);
+
+ HelloRequest build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java
new file mode 100644
index 00000000000..f86094051b8
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client.stubbeans;
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ HelloReply sayHello(HelloRequest request);
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java
new file mode 100644
index 00000000000..772749a648e
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.client.stubbeans;
+
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.docs.io.grpc.client.stubbeans.HelloWorldGrpc.HelloWorldBlockingStub;
+import org.springframework.stereotype.Component;
+
+@Component
+class MyApplicationRunner implements ApplicationRunner {
+
+ private final HelloWorldBlockingStub helloStub;
+
+ MyApplicationRunner(HelloWorldGrpc.HelloWorldBlockingStub helloStub) {
+ this.helloStub = helloStub;
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ HelloRequest request = HelloRequest.newBuilder().setName("Spring").build();
+ HelloReply reply = this.helloStub.sayHello(request);
+ System.out.println(reply.getMessage());
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java
new file mode 100644
index 00000000000..d951f2b874f
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.server;
+
+interface HelloReply {
+
+ String getMessage();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setMessage(String message);
+
+ HelloReply build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java
new file mode 100644
index 00000000000..f0bb4dcf6df
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.server;
+
+interface HelloRequest {
+
+ String getName();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setName(String name);
+
+ HelloRequest build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java
new file mode 100644
index 00000000000..de1595401c8
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.server;
+
+import io.grpc.stub.StreamObserver;
+
+class HelloWorldGrpc {
+
+ abstract static class HelloWorldImplBase {
+
+ abstract void sayHello(HelloRequest request, StreamObserver responseObserver);
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java
new file mode 100644
index 00000000000..70de5ff2d98
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.server;
+
+import io.grpc.stub.StreamObserver;
+
+import org.springframework.grpc.server.service.GrpcService;
+
+@GrpcService
+public class MyHelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
+
+ @Override
+ public void sayHello(HelloRequest request, StreamObserver responseObserver) {
+ String message = "Hello '%s'".formatted(request.getName());
+ HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java
new file mode 100644
index 00000000000..d2d9a64929f
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.server.security.servlet;
+
+import org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+
+@Configuration(proxyBeanMethods = false)
+public class MySecurityConfiguration {
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) {
+ http.securityMatcher(GrpcRequest.toAnyService().excluding("special"));
+ http.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("GRPC_ADMIN"));
+ http.httpBasic(withDefaults());
+ return http.build();
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java
new file mode 100644
index 00000000000..46ff563e859
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.localserverport;
+
+interface HelloReply {
+
+ String getMessage();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setMessage(String message);
+
+ HelloReply build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java
new file mode 100644
index 00000000000..b2ed9386480
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.localserverport;
+
+interface HelloRequest {
+
+ String getName();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setName(String name);
+
+ HelloRequest build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java
new file mode 100644
index 00000000000..db8994d4aad
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.localserverport;
+
+import io.grpc.ManagedChannel;
+
+interface HelloWorldGrpc {
+
+ static HelloWorldBlockingStub newBlockingStub(ManagedChannel channel) {
+ throw new IllegalStateException();
+ }
+
+ interface HelloWorldBlockingStub {
+
+ HelloReply sayHello(HelloRequest request);
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java
new file mode 100644
index 00000000000..5fa4304daf3
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.localserverport;
+
+import io.grpc.ManagedChannel;
+import io.grpc.netty.NettyChannelBuilder;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.docs.io.grpc.testing.localserverport.HelloWorldGrpc.HelloWorldBlockingStub;
+import org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = "spring.grpc.server.port=0")
+class MyGrpcIntegrationTests {
+
+ @LocalGrpcServerPort
+ private int port;
+
+ @Test
+ void sayHello() {
+ String target = "localhost:%s".formatted(this.port);
+ ManagedChannel channel = NettyChannelBuilder.forTarget(target).usePlaintext().build();
+ try {
+ HelloWorldBlockingStub hello = HelloWorldGrpc.newBlockingStub(channel);
+ HelloRequest request = HelloRequest.newBuilder().setName("Spring").build();
+ assertThat(hello.sayHello(request).getMessage()).isEqualTo("Hello 'Spring'");
+ }
+ finally {
+ channel.shutdown();
+ }
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java
new file mode 100644
index 00000000000..4a15ad45cea
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.testtransport;
+
+interface HelloReply {
+
+ String getMessage();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setMessage(String message);
+
+ HelloReply build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java
new file mode 100644
index 00000000000..8d520fc6605
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.testtransport;
+
+interface HelloRequest {
+
+ String getName();
+
+ static Builder newBuilder() {
+ throw new IllegalStateException();
+ }
+
+ interface Builder {
+
+ Builder setName(String name);
+
+ HelloRequest build();
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java
new file mode 100644
index 00000000000..b5d680b5fc7
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.testtransport;
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ HelloReply sayHello(HelloRequest request);
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java
new file mode 100644
index 00000000000..178192a4716
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-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.boot.docs.io.grpc.testing.testtransport;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.grpc.client.ImportGrpcClients;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@AutoConfigureTestGrpcTransport
+@ImportGrpcClients(types = HelloWorldGrpc.HelloWorldBlockingStub.class)
+class MyGrpcTests {
+
+ @Autowired
+ private HelloWorldGrpc.HelloWorldBlockingStub helloStub;
+
+ @Test
+ void sayHello() {
+ HelloRequest request = HelloRequest.newBuilder().setName("Spring").build();
+ HelloReply reply = this.helloStub.sayHello(request);
+ assertThat(reply.getMessage()).isEqualTo("Hello 'Spring'");
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt
new file mode 100644
index 00000000000..f4da5896b39
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt
@@ -0,0 +1,9 @@
+package org.springframework.boot.docs.io.grpc.client
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt
new file mode 100644
index 00000000000..7b5c0a369f3
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt
@@ -0,0 +1,14 @@
+package org.springframework.boot.docs.io.grpc.client
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.docs.features.springapplication.MyApplication
+import org.springframework.boot.runApplication
+import org.springframework.grpc.client.ImportGrpcClients
+
+@SpringBootApplication(proxyBeanMethods = false)
+@ImportGrpcClients(target = "hello", types = [HelloWorldGrpc.HelloWorldBlockingStub::class])
+class MyApplication
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt
new file mode 100644
index 00000000000..36c019233b4
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt
@@ -0,0 +1,20 @@
+package org.springframework.boot.docs.io.grpc.client.channelcustomization
+
+import io.grpc.ManagedChannelBuilder
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.grpc.client.GrpcChannelBuilderCustomizer
+import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor
+import java.util.function.Consumer
+
+@Configuration(proxyBeanMethods = false)
+class MyGrpcConfiguration {
+
+ @Bean
+ fun helloChannelCustomizer(): GrpcChannelBuilderCustomizer<*> {
+ return GrpcChannelBuilderCustomizer.matching("hello", { builder ->
+ builder.intercept(BasicAuthenticationInterceptor("user", "password"))
+ })
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt
new file mode 100644
index 00000000000..b9e136cb17c
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.client.stubbeans
+
+interface HelloReply {
+
+ fun getMessage(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setMessage(message: String): Builder
+
+ fun build(): HelloReply
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt
new file mode 100644
index 00000000000..70c921637bc
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.client.stubbeans
+
+interface HelloRequest {
+
+ fun getName(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setName(name: String): Builder
+
+ fun build(): HelloRequest
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt
new file mode 100644
index 00000000000..2bb2103ab8d
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt
@@ -0,0 +1,11 @@
+package org.springframework.boot.docs.io.grpc.client.stubbeans
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ fun sayHello(request: HelloRequest): HelloReply
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt
new file mode 100644
index 00000000000..f441809cdc4
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt
@@ -0,0 +1,15 @@
+package org.springframework.boot.docs.io.grpc.client.stubbeans
+
+import org.springframework.boot.ApplicationArguments
+import org.springframework.boot.ApplicationRunner
+
+
+class MyApplicationRunner(val helloStub: HelloWorldGrpc.HelloWorldBlockingStub) : ApplicationRunner {
+
+ override fun run(args: ApplicationArguments) {
+ val request = HelloRequest.newBuilder().setName("Spring").build()
+ val reply: HelloReply = helloStub.sayHello(request)
+ println(reply.getMessage())
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt
new file mode 100644
index 00000000000..6ea3efc39c0
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.server
+
+interface HelloReply {
+
+ fun getMessage(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setMessage(message: String): Builder
+
+ fun build(): HelloReply
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt
new file mode 100644
index 00000000000..2fedd32fa64
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.server
+
+interface HelloRequest {
+
+ fun getName(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setName(name: String): Builder
+
+ fun build(): HelloRequest
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt
new file mode 100644
index 00000000000..858880344fe
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt
@@ -0,0 +1,14 @@
+package org.springframework.boot.docs.io.grpc.server
+
+import io.grpc.stub.StreamObserver
+
+class HelloWorldGrpc {
+
+ abstract class HelloWorldImplBase {
+ abstract fun sayHello(
+ request: HelloRequest,
+ responseObserver: StreamObserver
+ )
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt
new file mode 100644
index 00000000000..5a6d57d41a4
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt
@@ -0,0 +1,16 @@
+package org.springframework.boot.docs.io.grpc.server
+
+import io.grpc.stub.StreamObserver
+import org.springframework.grpc.server.service.GrpcService
+
+@GrpcService
+class MyHelloWorldService : HelloWorldGrpc.HelloWorldImplBase() {
+
+ override fun sayHello(request: HelloRequest, responseObserver: StreamObserver) {
+ val message = "Hello '${request.getName()}'"
+ val reply = HelloReply.newBuilder().setMessage(message).build()
+ responseObserver.onNext(reply)
+ responseObserver.onCompleted()
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt
new file mode 100644
index 00000000000..d8af99d1b9b
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt
@@ -0,0 +1,21 @@
+package org.springframework.boot.docs.io.grpc.server.security.servlet
+
+import org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.Customizer.withDefaults
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.SecurityFilterChain
+
+@Configuration(proxyBeanMethods = false)
+class MySecurityConfiguration {
+
+ @Bean
+ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+ http.securityMatcher(GrpcRequest.toAnyService().excluding("special"))
+ http.authorizeHttpRequests { requests -> requests.anyRequest().hasRole("GRPC_ADMIN") }
+ http.httpBasic(withDefaults())
+ return http.build()
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt
new file mode 100644
index 00000000000..fc4df9a7d98
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.testing.localserverport
+
+interface HelloReply {
+
+ fun getMessage(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setMessage(message: String): Builder
+
+ fun build(): HelloReply
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt
new file mode 100644
index 00000000000..0dec210254a
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.testing.localserverport
+
+interface HelloRequest {
+
+ fun getName(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setName(name: String): Builder
+
+ fun build(): HelloRequest
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt
new file mode 100644
index 00000000000..d19c4cadf93
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt
@@ -0,0 +1,19 @@
+package org.springframework.boot.docs.io.grpc.testing.localserverport
+
+import io.grpc.ManagedChannel
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ fun sayHello(request: HelloRequest): HelloReply
+
+ }
+
+ companion object {
+ fun newBlockingStub(channel: ManagedChannel): HelloWorldBlockingStub {
+ throw IllegalStateException()
+ }
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt
new file mode 100644
index 00000000000..f53cbaccdb2
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt
@@ -0,0 +1,28 @@
+package org.springframework.boot.docs.io.grpc.testing.localserverport
+
+import io.grpc.ManagedChannel
+import io.grpc.netty.NettyChannelBuilder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort
+import org.springframework.boot.test.context.SpringBootTest
+
+@SpringBootTest(properties = ["spring.grpc.server.port=0"])
+class MyGrpcIntegrationTests {
+
+ @LocalGrpcServerPort
+ var port = 0
+
+ @Test
+ fun sayHello() {
+ val target = "localhost:${port}"
+ val channel: ManagedChannel = NettyChannelBuilder.forTarget(target).usePlaintext().build()
+ try {
+ val hello: HelloWorldGrpc.HelloWorldBlockingStub = HelloWorldGrpc.newBlockingStub(channel)
+ val request = HelloRequest.newBuilder().setName("Spring").build()
+ assertThat(hello.sayHello(request).getMessage()).isEqualTo("Hello 'Spring'")
+ } finally {
+ channel.shutdown()
+ }
+ }
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt
new file mode 100644
index 00000000000..44a56925ac5
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.testing.testtransport
+
+interface HelloReply {
+
+ fun getMessage(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setMessage(message: String): Builder
+
+ fun build(): HelloReply
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt
new file mode 100644
index 00000000000..718318c7c95
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.testing.testtransport
+
+interface HelloRequest {
+
+ fun getName(): String
+
+ companion object {
+
+ fun newBuilder(): Builder {
+ throw IllegalStateException()
+ }
+
+ }
+
+ interface Builder {
+
+ fun setName(name: String): Builder
+
+ fun build(): HelloRequest
+
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt
new file mode 100644
index 00000000000..24fe86509b1
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt
@@ -0,0 +1,19 @@
+package org.springframework.boot.docs.io.grpc.testing.testtransport
+
+import io.grpc.ManagedChannel
+
+class HelloWorldGrpc {
+
+ interface HelloWorldBlockingStub {
+
+ fun sayHello(request: HelloRequest): HelloReply
+
+ }
+
+ companion object {
+ fun newBlockingStub(channel: ManagedChannel): HelloWorldBlockingStub {
+ throw IllegalStateException()
+ }
+ }
+
+}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt
new file mode 100644
index 00000000000..3a89499cc61
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt
@@ -0,0 +1,23 @@
+package org.springframework.boot.docs.io.grpc.testing.testtransport
+
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.grpc.client.ImportGrpcClients
+
+@SpringBootTest
+@AutoConfigureTestGrpcTransport
+@ImportGrpcClients(types = [HelloWorldGrpc.HelloWorldBlockingStub::class])
+class MyGrpcTests(@Autowired val helloStub: HelloWorldGrpc.HelloWorldBlockingStub) {
+
+ @Test
+ fun sayHello() {
+ val request = HelloRequest.newBuilder().setName("Spring").build()
+ val reply = helloStub.sayHello(request)
+ assertThat(reply.getMessage()).isEqualTo("Hello 'Spring'")
+ }
+
+}
diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle
index 73cdab167d7..0849893be56 100644
--- a/platform/spring-boot-dependencies/build.gradle
+++ b/platform/spring-boot-dependencies/build.gradle
@@ -2597,7 +2597,7 @@ bom {
github("https://github.com/spring-projects/spring-grpc")
javadoc(version -> "https://docs.spring.io/spring-grpc/docs/%s/api"
.formatted(version.forMajorMinorGeneration()), "org.springframework.grpc")
- docs(version -> "https://docs.spring.io/spring-grpc/docs/%s/reference"
+ docs(version -> "https://docs.spring.io/spring-grpc/reference/%s"
.formatted(version.forMajorMinorGeneration()))
releaseNotes("https://github.com/spring-projects/spring-grpc/releases/tag/v{version}")
}
diff --git a/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle b/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle
index b647f93a872..0a79058693f 100644
--- a/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle
+++ b/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle
@@ -24,7 +24,7 @@ description = "Spring Boot gRPC server netty shaded smoke test"
dependencies {
implementation(project(":starter:spring-boot-starter-grpc-server")) {
- exclude(group: "io.grpc", module: "grpc-netty-shaded")
+ exclude(group: "io.grpc", module: "grpc-netty")
}
implementation("io.grpc:grpc-netty-shaded")
diff --git a/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle b/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle
index 4d7901ea632..6b71b0d1890 100644
--- a/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle
+++ b/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle
@@ -25,7 +25,7 @@ description = "Spring Boot gRPC server servlet smoke test"
dependencies {
implementation(project(":starter:spring-boot-starter-grpc-server")) {
- exclude(group: "io.grpc", module: "grpc-netty-shaded")
+ exclude(group: "io.grpc", module: "grpc-netty")
}
implementation(project(":starter:spring-boot-starter-tomcat"))
implementation("io.grpc:grpc-servlet-jakarta")
diff --git a/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java b/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java
index e8d5de4c01e..eabf6ba5ef8 100644
--- a/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java
+++ b/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java
@@ -23,10 +23,10 @@ import smoketest.grpcserver.proto.HelloReply;
import smoketest.grpcserver.proto.HelloRequest;
import smoketest.grpcserver.proto.HelloWorldGrpc;
-import org.springframework.stereotype.Service;
+import org.springframework.grpc.server.service.GrpcService;
import org.springframework.util.Assert;
-@Service
+@GrpcService
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
private static Log logger = LogFactory.getLog(HelloWorldService.class);