diff --git a/examples/webserver/grpc-random/README.md b/examples/webserver/grpc-random/README.md
new file mode 100644
index 00000000..f7bee41f
--- /dev/null
+++ b/examples/webserver/grpc-random/README.md
@@ -0,0 +1,21 @@
+# Helidon gRPC SE Randome Example
+
+
+This example shows a simple _Random_ service written with the Helidon gRPC SE
+API. See `RandomService` for the service implementation and `RandomServiceTest`
+for how to use the Helidon's `WebClient` to access the service. .
+
+The gRPC service definition is found in the `random.proto` file which is compiled
+using `protoc` at build time.
+
+## Build and run tests
+
+```shell
+mvn package
+```
+
+## Run the app
+
+```shell
+java -jar target/helidon-examples-webserver-grpc-random.jar
+```
diff --git a/examples/webserver/grpc-random/pom.xml b/examples/webserver/grpc-random/pom.xml
new file mode 100644
index 00000000..b470ed81
--- /dev/null
+++ b/examples/webserver/grpc-random/pom.xml
@@ -0,0 +1,135 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.2.0-SNAPSHOT
+
+
+
+ io.helidon.examples.webserver
+ helidon-examples-webserver-grpc-random
+ 1.0.0-SNAPSHOT
+ Helidon Examples WebServer gRPC Random
+
+
+ Application demonstrates the use of gRPC with a service that returns random numbers.
+
+
+
+ io.helidon.examples.webserver.grpc.random.Main
+
+
+
+
+ io.grpc
+ grpc-api
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+
+
+ io.helidon.webserver
+ helidon-webserver-grpc
+
+
+ io.helidon.logging
+ helidon-logging-jul
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ io.helidon.webclient
+ helidon-webclient-grpc
+
+
+
+ javax.annotation
+ javax.annotation-api
+ provided
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${version.plugin.os}
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+
+
+ compile
+ compile-custom
+
+
+
+
+ com.google.protobuf:protoc:${version.lib.google-protobuf}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java
new file mode 100644
index 00000000..81376ada
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * 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
+ *
+ * http://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 io.helidon.examples.webserver.grpc.random;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.grpc.GrpcRouting;
+
+class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ // initialize global config from default configuration
+ Config config = Config.create();
+ Config.global(config);
+ Config serverConfig = config.get("server");
+
+ // start server and register gRPC routing and health check
+ WebServer.builder()
+ .config(serverConfig)
+ .addRouting(GrpcRouting.builder().service(new RandomService()))
+ .build()
+ .start();
+ }
+}
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java
new file mode 100644
index 00000000..973f6286
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * 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
+ *
+ * http://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 io.helidon.examples.webserver.grpc.random;
+
+import java.util.Random;
+
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.examples.webserver.grpc.random.Random.RandomMessage;
+import io.helidon.webserver.grpc.GrpcService;
+
+import com.google.protobuf.Descriptors;
+import io.grpc.stub.StreamObserver;
+
+class RandomService implements GrpcService {
+
+ private final Random random = new Random();
+
+ @Override
+ public Descriptors.FileDescriptor proto() {
+ return io.helidon.examples.webserver.grpc.random.Random.getDescriptor();
+ }
+
+ @Override
+ public void update(Routing router) {
+ router.unary("RandomSingle", this::randomSingle)
+ .bidi("RandomMany", this::randomMany);
+ }
+
+ private void randomSingle(ParamMessage request, StreamObserver response) {
+ int bound = request.getNumber();
+ response.onNext(newRandomMessage(random.nextInt(bound)));
+ response.onCompleted();
+ }
+
+ private StreamObserver randomMany(StreamObserver response) {
+ return new StreamObserver<>() {
+
+ private int bound = Integer.MIN_VALUE;
+ private int count = Integer.MIN_VALUE;
+
+ @Override
+ public void onNext(ParamMessage paramMessage) {
+ // collect bound and count, in that order
+ if (bound == Integer.MIN_VALUE) {
+ bound = paramMessage.getNumber();
+ } else if (count == Integer.MIN_VALUE) {
+ count = paramMessage.getNumber();
+ } else {
+ onError(new IllegalStateException("Received extra input params"));
+ }
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ response.onError(throwable);
+ }
+
+ @Override
+ public void onCompleted() {
+ // verify input params received
+ if (bound == Integer.MIN_VALUE || count == Integer.MIN_VALUE) {
+ onError(new IllegalStateException("Did not receive all input params"));
+ }
+
+ // send stream of random numbers
+ for (int i = 0; i < count; i++) {
+ response.onNext(newRandomMessage(random.nextInt(bound)));
+ }
+ response.onCompleted();
+ }
+ };
+ }
+
+ private static RandomMessage newRandomMessage(int random) {
+ return RandomMessage.newBuilder().setNumber(random).build();
+ }
+}
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java
new file mode 100644
index 00000000..69084065
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+/**
+ * Example of gRPC in webserver.
+ */
+package io.helidon.examples.webserver.grpc.random;
diff --git a/examples/webserver/grpc-random/src/main/proto/random.proto b/examples/webserver/grpc-random/src/main/proto/random.proto
new file mode 100644
index 00000000..559d6952
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/proto/random.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+
+syntax = "proto3";
+option java_package = "io.helidon.examples.webserver.grpc.random";
+
+service RandomService {
+ rpc RandomSingle (ParamMessage) returns (RandomMessage) {}
+ rpc RandomMany (stream ParamMessage) returns (stream RandomMessage) {}
+}
+
+message ParamMessage {
+ int32 number = 1;
+}
+
+message RandomMessage {
+ int32 number = 1;
+}
diff --git a/examples/webserver/grpc-random/src/main/resources/application.yaml b/examples/webserver/grpc-random/src/main/resources/application.yaml
new file mode 100644
index 00000000..327fc590
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/resources/application.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# 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
+#
+# http://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.
+#
+
+server:
+ port: 8080
+ tls:
+ trust:
+ keystore:
+ passphrase: "password"
+ trust-store: true
+ resource:
+ resource-path: "server.p12"
+ private-key:
+ keystore:
+ passphrase: "password"
+ resource:
+ resource-path: "server.p12"
diff --git a/examples/webserver/grpc-random/src/main/resources/client.p12 b/examples/webserver/grpc-random/src/main/resources/client.p12
new file mode 100644
index 00000000..4eb3b832
Binary files /dev/null and b/examples/webserver/grpc-random/src/main/resources/client.p12 differ
diff --git a/examples/webserver/grpc-random/src/main/resources/logging.properties b/examples/webserver/grpc-random/src/main/resources/logging.properties
new file mode 100644
index 00000000..344ba708
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/resources/logging.properties
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# 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
+#
+# http://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.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+.level=INFO
+#io.helidon.webclient.grpc.level=FINEST
diff --git a/examples/webserver/grpc-random/src/main/resources/server.p12 b/examples/webserver/grpc-random/src/main/resources/server.p12
new file mode 100644
index 00000000..ff8e4ddf
Binary files /dev/null and b/examples/webserver/grpc-random/src/main/resources/server.p12 differ
diff --git a/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java
new file mode 100644
index 00000000..312d9ab3
--- /dev/null
+++ b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * 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
+ *
+ * http://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 io.helidon.examples.webserver.grpc.random;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import io.helidon.common.configurable.Resource;
+import io.helidon.common.tls.Tls;
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webclient.grpc.GrpcClient;
+import io.helidon.webserver.Router;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.grpc.GrpcRouting;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.examples.webserver.grpc.random.Random.RandomMessage;
+import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc;
+import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc.RandomServiceBlockingStub;
+
+import io.grpc.stub.StreamObserver;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+/**
+ * Tests gRPC Strings service using {@link io.helidon.webclient.api.WebClient}.
+ */
+@ServerTest
+class RandomServiceTest {
+ private static final long TIMEOUT_SECONDS = 10;
+ private static final int BOUND = 100;
+ private static final int COUNT = 10;
+
+ private final WebClient webClient;
+
+ private RandomServiceTest(WebServer server) {
+ Tls clientTls = Tls.builder()
+ .trust(trust -> trust
+ .keystore(store -> store
+ .passphrase("password")
+ .trustStore(true)
+ .keystore(Resource.create("client.p12"))))
+ .build();
+ this.webClient = WebClient.builder()
+ .tls(clientTls)
+ .baseUri("https://localhost:" + server.port())
+ .build();
+ }
+
+ @SetUpRoute
+ static void routing(Router.RouterBuilder> router) {
+ router.addRouting(GrpcRouting.builder().service(new RandomService()));
+ }
+
+ @Test
+ void testRandomSingle() {
+ GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL);
+ RandomServiceBlockingStub service = RandomServiceGrpc.newBlockingStub(grpcClient.channel());
+ RandomMessage res = service.randomSingle(newParamMessage(BOUND));
+ assertThat(res.getNumber(), is(lessThan(BOUND)));
+ }
+
+ @Test
+ void testRandomMany() throws InterruptedException, TimeoutException, ExecutionException {
+ GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL);
+ RandomServiceGrpc.RandomServiceStub service = RandomServiceGrpc.newStub(grpcClient.channel());
+ CompletableFuture> future = new CompletableFuture<>();
+ StreamObserver req = service.randomMany(multiStreamObserver(future));
+ req.onNext(newParamMessage(BOUND));
+ req.onNext(newParamMessage(COUNT));
+ req.onCompleted();
+ Iterator res = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ int n = 0;
+ for (; res.hasNext(); n++) {
+ assertThat(res.next().getNumber(), is(lessThan(BOUND)));
+ }
+ assertThat(n, is(COUNT));
+ }
+
+ static ParamMessage newParamMessage(int n) {
+ return ParamMessage.newBuilder().setNumber(n).build();
+ }
+
+ static StreamObserver multiStreamObserver(CompletableFuture> future) {
+ return new StreamObserver<>() {
+ private final List value = new ArrayList<>();
+
+ @Override
+ public void onNext(ResT value) {
+ this.value.add(value);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ future.completeExceptionally(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ future.complete(value.iterator());
+ }
+ };
+ }
+}
diff --git a/examples/webserver/grpc-random/src/test/resources/application.yaml b/examples/webserver/grpc-random/src/test/resources/application.yaml
new file mode 100644
index 00000000..00b3aef2
--- /dev/null
+++ b/examples/webserver/grpc-random/src/test/resources/application.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# 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
+#
+# http://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.
+#
+
+server:
+ port: 0
+ tls:
+ trust:
+ keystore:
+ passphrase: "password"
+ trust-store: true
+ resource:
+ resource-path: "server.p12"
+ private-key:
+ keystore:
+ passphrase: "password"
+ resource:
+ resource-path: "server.p12"
diff --git a/examples/webserver/pom.xml b/examples/webserver/pom.xml
index 84c6b219..1e75eb90 100644
--- a/examples/webserver/pom.xml
+++ b/examples/webserver/pom.xml
@@ -39,6 +39,7 @@
echo
fault-tolerance
grpc
+ grpc-random
imperative
multiport
mutual-tls