Contents

Overview

The Helidon gRPC server provides a framework for creating gRPC applications. While it allows you to deploy any standard gRPC service that implements io.grpc.BindableService interface, including services generated from the Protobuf IDL files (and even allows you to customize them to a certain extent), using Helidon gRPC framework to implement your services has a number of benefits:

  • It allows you to define both HTTP and gRPC services using a similar programming model, simplifying the learning curve for developers.

  • It provides a number of helper methods that make service implementation significantly simpler.

  • It allows you to configure some of the Helidon value-added features, such as security and metrics collection down to the method level.

  • It allows you to easily specify custom marshallers for requests and responses if Protobuf does not satisfy your needs.

  • It provides built-in support for health checks.

Maven Coordinates

To enable gRPC Server add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.grpc</groupId>
    <artifactId>helidon-grpc-server</artifactId>
</dependency>
Copied

If gRPC server security is required as described in the section, add the following dependency to your project’s pom.xml:

<dependency>
    <groupId>io.helidon.security.integration</groupId>
    <artifactId>helidon-security-integration-grpc</artifactId>
</dependency>
Copied

Usage

gRPC Server Routing

Unlike the webserver, which allows you to route requests based on path expression and the HTTP verb, the gRPC server always routes requests based on the service and method name. This makes routing configuration somewhat simpler — all you need to do is register your services:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .register(new GreetService(config)) 
            .register(new EchoService())        
            .register(new MathService())        
            .build();
}
Copied
  • Register GreetService instance.
  • Register EchoService instance.
  • Register MathService instance.

Both "standard" gRPC services that implement io.grpc.BindableService interface (typically implemented by extending the generated server-side stub and overriding its methods), and Helidon gRPC services that implement io.helidon.grpc.server.GrpcService interface can be registered. The difference is that Helidon gRPC services allow you to customize behavior down to the method level, and provide a number of useful helper methods that make service implementation easier, as we’ll see in a moment.

Customizing Service Definitions

When registering a service, regardless of its type, you can customize its descriptor by providing a configuration consumer as a second argument to the register method.

This is particularly useful when registering standard BindableService instances, as it allows you to add certain Helidon-specific behaviors, such as health checks and metrics to them:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .register(new GreetService(config))
            .register(new EchoService(), service -> {
                service.healthCheck(CustomHealthChecks::echoHealthCheck)  
                       .metered();                                        
            })
            .build();
}
Copied
  • Add custom health check to the service.
  • Specify that all the calls to service methods should be metered.

Specifying Global Interceptors

GrpcRouting also allows you to specify custom interceptors that will be applied to all registered services.

This is useful to configure features such as tracing, security and metrics collection, and we provide built-in interceptors for those purposes that you can simply register with the routing definition:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .intercept(GrpcMetrics.timed())     
            .register(new GreetService(config))
            .register(new EchoService())
            .register(new MathService())
            .build();
}
Copied
  • Register GrpcMetrics interceptor that will collect timers for all methods of all services (but can be overridden at the individual service or even method level).

Service Implementation

At the very basic level, all you need to do in order to implement a Helidon gRPC service is create a class that implements the io.helidon.grpc.server.GrpcService interface and define one or more methods for the service:

class EchoService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.marshallerSupplier(new JsonbMarshaller.Supplier()) 
             .unary("Echo", this::echo); 
    }

    /**
     * Echo the message back to the caller.
     *
     * @param request   the echo request containing the message to echo
     * @param observer  the response observer
     */
    public void echo(String request, StreamObserver<String> observer) {  
        complete(observer, request);  
    }
}
Copied
  • Specify a custom marshaller to marshall requests and responses.
  • Define unary method Echo and map it to the this::echo handler.
  • Create a handler for the Echo method.
  • Send the request string back to the client by completing response observer.

The complete method shown in the example above is just one of many helper methods available in the GrpcService class. See the full list here.

The example above implements a service with a single unary method which will be exposed at the `EchoService/Echo' endpoint. The service explicitly defines a marshaller for requests and responses, so this implies that you will have to implement clients by hand and configure them to use the same marshaller as the server. Obviously, one of the major selling points of gRPC is that it makes it easy to generate clients for a number of languages (as long as you use Protobuf for marshalling), so let’s see how we would implement Protobuf enabled Helidon gRPC service.

Implementing Protobuf Services

In order to implement Protobuf-based service, you would follow the official instructions on the gRPC web site, which boil down to the following:

Define the Service IDL

For this example, we will re-implement the EchoService above as a Protobuf service in echo.proto file.

syntax = "proto3";
option java_package = "org.example.services.echo";

service EchoService {
  rpc Echo (EchoRequest) returns (EchoResponse) {}
}

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
}
Copied

Based on this IDL, the gRPC compiler will generate message classes (EchoRequest and EchoResponse), client stubs that can be used to make RPC calls to the server, as well as the base class for the server-side service implementation.

We can ignore the last one, and implement the service using Helidon gRPC framework instead.

Implement the Service

The service implementation will be very similar to our original implementation:

class EchoService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.proto(Echo.getDescriptor())  
             .unary("Echo", this::echo);   
    }

    /**
     * Echo the message back to the caller.
     *
     * @param request   the echo request containing the message to echo
     * @param observer  the response observer
     */
    public void echo(Echo.EchoRequest request, StreamObserver<Echo.EchoResponse> observer) {  
        String message = request.getMessage();  
        Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build();  
        complete(observer, response);  
    }
}
Copied
  • Specify the proto descriptor in order to provide necessary type information and enable Protobuf marshalling.
  • Define unary method Echo and map it to the this::echo handler.
  • Create a handler for the Echo method, using Protobuf message types for request and response.
  • Extract message string from the request.
  • Create the response containing extracted message.
  • Send the response back to the client by completing response observer.

Interceptors

Helidon gRPC allows you to configure standard interceptors using io.grpc.ServerInterceptor.

For example, you could implement an interceptor that logs each RPC call:

class LoggingInterceptor implements ServerInterceptor {   

    private static final Logger LOG = Logger.getLogger(LoggingInterceptor.class.getName());

    @Override
    public <ReqT, ResT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, ResT> call,
                                                                 Metadata metadata,
                                                                 ServerCallHandler<ReqT, ResT> handler) {

        LOG.info(() -> "CALL: " + call.getMethodDescriptor());  
        return handler.startCall(call, metadata);               
    }
}
Copied
  • Implement the interceptor class using io.grpc.ServerInterceptor.
  • Implement the logging logic.
  • The intercepted call is started.

Registering Interceptors

You can register interceptors globally, in which case they will be applied to all methods of all services, by simply adding them to the GrpcRouting instance:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .intercept(new LoggingInterceptor())  
            .register(new GreetService(config))
            .register(new EchoService())
            .build();
}
Copied
  • Adds LoggingInterceptor to all methods of GreetService and EchoService.

You can also register an interceptor for a specific service, either by implementing GrpcService.update method:

public class MyService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.intercept(new LoggingInterceptor())   
                .unary("MyMethod", this::myMethod);
    }

    private <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> observer) {
        // do something
    }
}
Copied
  • Adds LoggingInterceptor to all methods of MyService.

Or by configuring ServiceDescriptor externally, when creating GrpcRouting, which allows you to add interceptors to plain io.grpc.BindableService services as well:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .register(new GreetService(config), cfg -> cfg.intercept(new LoggingInterceptor()))  
            .register(new EchoService())
            .build();
}
Copied
  • Adds LoggingInterceptor to all methods of GreetService only.

Finally, you can also register an interceptor at the method level:

public class MyService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.unary("MyMethod",
                     this::myMethod,
                     cfg -> cfg.intercept(new LoggingInterceptor()));  
    }

    private <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> observer) {
        // do something
    }
}
Copied
  • Adds LoggingInterceptor to MyService::MyMethod only.

Service Health Checks

Helidon gRPC services provide built-in support for Helidon Health Checks.

Unless a custom health check is implemented by the service developer, each service deployed to the gRPC server will be provisioned with a default health check, which always returns status of UP.

This allows all services, including the ones that don’t have a meaningful health check, to show up in the health report (or to be queried for health) without service developer having to do anything.

However, services that do need custom health checks can easily define one, directly within GrpcService implementation:

public class MyService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.unary("MyMethod", this::myMethod)
                .healthCheck(this::healthCheck);  
    }

    private HealthC
heckResponse healthCheck() {
        boolean fUp = isMyServiceUp();            
        return HealthCheckResponse
                .named(name())                    
                .state(fUp)                       
                .withData("ts", System.currentTimeMillis())  
                .build();
    }

    private <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> observer) {
        // do something
    }
}
Copied
  • Configure a custom health check for the service.
  • Determine the service status.
  • Use service name as a health check name for consistency.
  • Set the determined service status.
  • Optionally provide additional metadata.

You can also define custom health checks for an existing service, including plain io.grpc.BindableService implementations, using a service configurer inside the GrpcRouting definition:

private static GrpcRouting createRouting() {
    return GrpcRouting.builder()
            .register(new EchoService(), cfg -> cfg.healthCheck(MyCustomHealthChecks::echoHealthCheck))  
            .build();
}
Copied
  • Configure custom health check for an existing or legacy service.

Exposing Health Checks

All gRPC service health checks are managed by the Helidon gRPC server, and are automatically exposed to the gRPC clients using a custom implementation of the standard gRPC HealthService API.

However, they can also be exposed to REST clients via the standard Helidon/Microprofile /health endpoint:

        GrpcServer grpcServer = GrpcServer.create(grpcServerConfig(), createRouting(config));  
        grpcServer.start();                                                                    

        HealthSupport health = HealthSupport.builder()
                .add(grpcServer.healthChecks())     
                .build();

        Routing routing = Routing.builder()
                .register(health)                   
                .build();

        WebServer.create(webServerConfig(), routing).start();   
Copied
  • Create the GrpcServer instance.
  • Start the gRPC server which will deploy all the services and register default and custom health checks.
  • Add gRPC server managed health checks to HealthSupport instance.
  • Add HealthSupport to the web server routing definition.
  • Create and start the web server.

All gRPC health checks will now be available via the /health REST endpoint, in addition to the standard gRPC HealthService

Service Metrics

The Helidon gRPC server has built-in support for metrics capture, which allows service developers to easily enable application-level metrics for their services.

Enabling Metrics Capture

By default, the gRPC server only captures two vendor-level metrics: grpc.request.count and grpc.request.meter. These metrics provide an aggregate view of requests across all services, and serve as an indication of the overall server load.

However, users can enable more fine-grained metrics by simply configuring a built-in GrpcMetrics interceptor within the routing:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .intercept(GrpcMetrics.timed())       
            .register(new GreetService(config))
            .register(new EchoService())
            .build();
}
Copied
  • Capture the metrics for all methods of all services as a timer.

In the example above we have chosen to create and keep a timer metric type for each method of each service. Alternatively, we could’ve chosen to use a counter, meter or a histogram instead.

Overriding Metrics Capture

While global metrics capture is certainly useful, it is not always sufficient. Keeping a separate timer for each gRPC method may be excessive, so the user could decide to use a lighter-weight metric type, such as a counter or a meter.

However, the user may still want to enable a histogram or a timer for some services, or even only some methods of some services.

This can be easily accomplished by overriding the type of the captured metric at either the service or the method level:

private static GrpcRouting createRouting(Config config) {
    return GrpcRouting.builder()
            .intercept(GrpcMetrics.counted())  
            .register(new MyService())
            .build();
}

public static class MyService implements GrpcService {

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules
            .intercept(GrpcMetrics.metered())  
            .unary("MyMethod", this::myMethod,
                       cfg -> cfg.intercept(GrpcMetrics.timer())); 
    }

    private <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> observer) {
        // do something
    }
}
Copied
  • Use counter for all methods of all services, unless overridden.
  • Use meter for all methods of MyService.
  • Use timer for MyService::MyMethod.

Exposing Metrics Externally

Collected metrics are stored in the standard Helidon metric registries, such as the vendor and application registries, and can be exposed via the standard /metrics REST API.

Routing routing = Routing.builder()
        .register(MetricsSupport.create())    
        .build();

WebServer.create(webServerConfig(), routing)  
         .start()
Copied
  • Add the MetricsSupport instance to web server routing.
  • Create and start the Helidon web server.

See Helidon Metrics documentation for more details.

Specifying Metric Metadata

Helidon metrics contain metadata such as tags, a description, units etc. It is possible to add this additional metadata when specifying the metrics.

Adding Tags

To add tags to a metric, a Map of key/value tags can be supplied.

Map<String, String> tagMap = new HashMap<>();
tagMap.put("keyOne", "valueOne");
tagMap.put("keyTwo", "valueTwo");

GrpcRouting routing = GrpcRouting.builder()
        .intercept(GrpcMetrics.counted().tags(tagMap))   
        .register(new MyService())
        .build();
Copied
  • The tags() method is used to add the Map of tags to the metric.
Adding a Description

A meaningful description can be added to a metric.

GrpcRouting routing = GrpcRouting.builder()
        .intercept(GrpcMetrics.counted().description("Something useful")) 
        .register(new MyService())
        .build();
Copied
  • The description() method is used to add the description to the metric.
Adding Metric Units

A units value can be added to a metric.

GrpcRouting routing = GrpcRouting.builder()
        .intercept(GrpcMetrics.timed().units(MetricUnits.SECONDS)) 
        .register(new MyService())
        .build();
Copied
  • The units() method is used to specify the metric units, the value of which is one of the constants from the org.eclipse.microprofile.metrics.MetricUnits class.

Overriding the Metric Name

By default, the metric name is the gRPC service name followed by a dot ('.') followed by the method name. It is possible to supply a function that can be used to override the default behaviour.

The function should implement the io.helidon.grpc.metrics.GrpcMetrics.NamingFunction interface.

@FunctionalInterface
public interface NamingFunction {
    /**
     * Create a metric name.
     *
     * @param service    the service descriptor
     * @param methodName the method name
     * @param metricType the metric type
     * @return the metric name
     */
    String createName(ServiceDescriptor service, String methodName, MetricType metricType);
}
Copied

This is a functional interface so a lambda expression can be used too.

GrpcRouting routing = GrpcRouting.builder()
        .intercept(GrpcMetrics.counted()
                .nameFunction((svc, method, metric) -> "grpc." + service.name() + '.' + method) 
Copied
  • The NamingFunction is just a lambda that returns the concatenated service name and method name with the prefix grpc.. So for a service "Foo" and method "bar", the above example would produce a name "grpc.Foo.bar".

Security

To enable server security, refer to the earlier section about Security maven coordinates for guidance on what dependency to add in the project’s pom.xml.

Bootstrapping

There are two steps to configure security with the gRPC server:

  1. Create the security instance and register it the with server.
  2. Protect the gRPC services of the server with various security features.
Example using builders
// gRPC server's routing
GrpcRouting.builder()
    // This is step 1 - register security instance with gRPC server processing
    // security - instance of security either from config or from a builder
    // securityDefaults - default enforcement for each service that has a security definition
    .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
    // this is step 2 - protect a service
    // register and protect this service with authentication (from defaults) and role "user"
    .register(greetService, GrpcSecurity.rolesAllowed("user"))
    .build();
Copied
Example using builders for more fine grained method level security
// create the service descriptor
ServiceDescriptor greetService = ServiceDescriptor.builder(new GreetService())
        // Add an instance of gRPC security that will apply to all methods of
        // the service - in this case require the "user" role
        .intercept(GrpcSecurity.rolesAllowed("user"))
        // Add an instance of gRPC security that will apply to the "SetGreeting"
        // method of the service - in this case require the "admin" role
        .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin"))
        .build();

// Create the gRPC server's routing
GrpcRouting.builder()
    // This is step 1 - register security instance with gRPC server processing
    // security - instance of security either from config or from a builder
    // securityDefaults - default enforcement for each service that has a security definition
    .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
    // this is step 2 - add the service descriptor
    .register(greetService)
    .build();
Copied
Example using configuration
GrpcRouting.builder()
    // helper method to load both security and gRPC server security from configuration
    .intercept(GrpcSecurity.create(config))
    // continue with gRPC server route configuration...
    .register(new GreetService())
    .build();
Copied
Example using configuration - configuration (HOCON)
# This may change in the future - to align with gRPC server configuration,
# once it is supported
security
  grpc-server:
    # Configuration of integration with gRPC server
    defaults:
        authenticate: true
    # Configuration security for individual services
    services:
    - name: "GreetService"
      defaults:
      roles-allowed: ["user"]
      # Configuration security for individual methods of the service
      methods:
      - name: "SetGreeting"
        roles-allowed: ["admin"]
Copied
Client security

When using the Helidon SE gRPC client, API security can be configured for a gRPC service or at the individual method level. The client API has a custom CallCredentials implementation that integrates with the Helidon security APIs.

Example configuring client security for a service
Security security = Security.builder()  
        .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
        .build();

GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) 
        .property(EndpointConfig.PROPERTY_OUTBOUND_ID, user)
        .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, password)
        .build();

ClientServiceDescriptor descriptor = ClientServiceDescriptor 
        .builder(StringService.class)
        .unary("Lower")
        .callCredentials(clientSecurity)                     
        .build();

GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); 

String response = client.blockingUnary("Lower", "ABCD"); 
Copied
  • Create the Helidon Security instance which, in this case, will use the basic auth provider.
  • Create the GrpcClientSecurity gRPC CallCredentials adding the user and password property expected by the basic auth provider.
  • Create the gRPC ClientServiceDescriptor for the StringService gRPC service.
  • Set the GrpcClientSecurity instance as the call credentials for all methods of the service.
  • Create a GrpcServiceClient that will allow methods to be called on the service.
  • Call the "Lower" method which will use the configured basic auth credentials.
Example configuring client security for a specific method
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) 
        .property(EndpointConfig.PROPERTY_OUTBOUND_ID, user)
        .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, password)
        .build();

ClientServiceDescriptor descriptor = ClientServiceDescriptor 
        .builder(StringService.class)
        .unary("Lower")
        .unary("Upper", rules -> rules.callCredentials(clientSecurity)) 
        .build();
Copied
  • Create the GrpcClientSecurity call credentials in the same way as above.
  • Create the ClientServiceDescriptor, this time with two unary methods, "Lower" and "Upper".
  • The "Upper" method is configured to use the GrpcClientSecurity call credentials, the "Lower" method will be called without any credentials.
Outbound security

Outbound security covers three scenarios:

  • Calling a secure gRPC service from inside a gRPC service method handler.

  • Calling a secure gRPC service from inside a web server method handler.

  • Calling a secure web endpoint from inside a gRPC service method handler.

Within each scenario, credentials can be propagated if the gRPC/http method handler is executing within a security context or credentials can be overridden to provide a different set of credentials to use for calling the outbound endpoint.

Example calling a secure gRPC service from inside a gRPC service method handler
// Obtain the SecurityContext from the current gRPC call Context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();

// Create a gRPC CallCredentials that will use the current request's
// security context to configure outbound credentials
GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(securityContext);

// Create the gRPC stub using the CallCredentials
EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);
Copied
Example calling a secure gRPC service from inside a web server method handler
private static void propagateCredentialsWebRequest(ServerRequest req, ServerResponse res) {
    try {
        // Create a gRPC CallCredentials that will use the current request's
        // security context to configure outbound credentials
        GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req);

        // Create the gRPC stub using the CallCredentials
        EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);

        String message = req.queryParams().first("message").orElse(null);
        Echo.EchoResponse echoResponse = stub.echo(Echo.EchoRequest.newBuilder().setMessage(message).build());
        res.send(echoResponse.getMessage());
    } catch (StatusRuntimeException e) {
        res.status(GrpcHelper.toHttpResponseStatus(e)).send();
    } catch (Throwable thrown) {
        res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send();
    }
}
Copied
Example calling a secure web endpoint from inside a gRPC service method handler
// Obtain the SecurityContext from the gRPC call Context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();

// Use the SecurityContext as normal to make a http request
Response webResponse = client.target(url)
        .path("/test")
        .request()
        .property(ClientSecurity.PROPERTY_CONTEXT, securityContext)
        .get();
Copied

Marshalling

Default Marshalling Support

Helidon gRPC supports Protobuf out of the box. The Protobuf marshaller will be used by default for any request and response classes that extend com.google.protobuf.MessageLite, which is the case for all classes generated from a proto file using protoc compiler.

That means that you don’t need any special handling or configuration in order to support Protobuf serialization of requests and responses.

Custom Marshalling

Helidon makes the use of custom marshallers trivial and provides one custom implementation, JsonbMarshaller, out of the box.

You can also easily implement your own marshaller to support serialization formats that are not supported natively by Helidon, by implementing Marshaller and MarshallerSupplier interfaces. As an example, check out the source code of the built-in marshaller: JsonbMarshaller.java.

Furthermore, Oracle Coherence CE provides a marshaller for a highly optimized, binary, platform independent Portable Object Format (POF). You can find more information about POF in Coherence documentation

Setting the custom marshaller

You can implement the update method on your service’s class and set the custom marshaller supplier via the ServiceDescriptor.Rules.marshallerSupplier() method:

Sample code for setting the marshaller on the gRPC service
public class GreetServiceJava
        implements GrpcService {
    private String greeting;


    public GreetServiceJava(Config config) {
        this.greeting = config.get("app.greeting").asString().orElse("Ciao");
    }

    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.marshallerSupplier(new JsonbMarshaller.Supplier())  
                .unary("Greet", this::greet)
                .unary("SetGreeting", this::setGreeting);
    }

    // Implement Service methods
}
Copied
  • Specify the custom marshaller to use.

Configuration

Configure the gRPC server using the Helidon configuration framework, either programmatically or via a configuration file.

Configuring the gRPC Server in the Code

The easiest way to configure the gRPC server is in the application code.

GrpcServerConfiguration configuration = GrpcServerConfiguration.builder()
                                                       .port(8080)
                                                       .build();
GrpcServer grpcServer = GrpcServer.create(configuration, routing);
Copied

See all configuration options here.

Configuring the gRPC Server in a Configuration File

You can also define the gRPC server configuration in a file.

Type: io.helidon.grpc.server.GrpcServerConfiguration

Configuration Options

Optional configuration options
keytypedefault valuedescription
name

string

grpc.server

Set the name of the gRPC server.

Configuration key: `name`
native

boolean

false

Specify if native transport should be used.

port

int

1408

Sets server port. If port is 0 or less then any available ephemeral port will be used.

Configuration key: `port`
workers

int

Number of processors available to the JVM

Sets a count of threads in pool used to process HTTP requests. Default value is CPU_COUNT * 2.

Configuration key: `workers`
GrpcServer configuration file example using application.yaml
grpc:
  port: 3333
Copied

Then, in your application code, load the configuration from that file.

GrpcServer initialization using the application.conf file located on the classpath
GrpcServerConfiguration configuration = GrpcServerConfiguration.create(
        Config.builder()
              .sources(classpath("application.conf"))
              .build());

GrpcServer grpcServer = GrpcServer.create(configuration, routing);
Copied

Examples

Quick Start

Here is the code for a minimalist gRPC application that runs on a default port (1408):

public static void main(String[] args) throws Exception {
    GrpcServer grpcServer = GrpcServer
            .create(GrpcRouting.builder()
                            .register(new HelloService()) 
                            .build())
            .start() 
            .toCompletableFuture()
            .get(10, TimeUnit.SECONDS); // Implement the simplest possible gRPC service. 

    System.out.println("gRPC server started at: http://localhost:" + grpcServer.port()); 
}

static class HelloService implements GrpcService { 
    @Override
    public void update(ServiceDescriptor.Rules rules) {
        rules.marshallerSupplier(new JsonbMarshaller.Supplier()) 
             .unary("SayHello", ((request, responseObserver) -> complete(responseObserver, "Hello " + request))); 
    }
}
Copied
  • Register the gRPC service.
  • Start the server.
  • Wait for the server to start while throwing possible errors as exceptions.
  • The server is bound to a default port (1408).
  • Implement the simplest possible gRPC service.
  • Specify a custom marshaller using the built-in JsonB marshaller to marshall requests and responses.
  • Add unary method HelloService/SayHello to the service definition.

Additional gRPC Server Examples