Skip to content

gRPC

What is gRPC?

High-performance RPC framework developed by Google, using HTTP/2 and Protocol Buffers.

gRPC Architecture


Protocol Buffers

Basic Syntax

// order.proto
syntax = "proto3";

package ecommerce;

option java_package = "com.example.ecommerce";
option java_multiple_files = true;

import "google/protobuf/timestamp.proto";

// Message definitions
message Order {
  string id = 1;
  string customer_id = 2;
  OrderStatus status = 3;
  repeated OrderItem items = 4;
  Money total = 5;
  Address shipping_address = 6;
  google.protobuf.Timestamp created_at = 7;
  map<string, string> metadata = 8;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  Money unit_price = 3;
}

message Money {
  int64 amount = 1;  // Amount in cents
  string currency = 2;
}

message Address {
  string line1 = 1;
  string line2 = 2;
  string city = 3;
  string state = 4;
  string postal_code = 5;
  string country = 6;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_DELIVERED = 4;
  ORDER_STATUS_CANCELLED = 5;
}

Field Types

// Scalar types
string name = 1;
int32 count = 2;       // 32-bit signed
int64 big_count = 3;   // 64-bit signed
uint32 positive = 4;   // 32-bit unsigned
uint64 big_positive = 5;
float price = 6;       // 32-bit float
double precise = 7;    // 64-bit float
bool active = 8;
bytes data = 9;        // Arbitrary bytes

// Collections
repeated string tags = 10;              // List
map<string, int32> scores = 11;         // Map

// Optional (proto3 - all fields optional by default)
optional string nickname = 12;          // Explicit optional

// Oneof (only one field can be set)
oneof payment_method {
  CreditCard credit_card = 13;
  BankAccount bank_account = 14;
  string paypal_email = 15;
}

// Nested messages
message Customer {
  string id = 1;
  string name = 2;

  message ContactInfo {
    string email = 1;
    string phone = 2;
  }
  ContactInfo contact = 3;
}

Field Numbers & Evolution

Protocol Buffer Evolution


Service Definitions

Service Types

service OrderService {
  // Unary RPC - single request, single response
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc CreateOrder(CreateOrderRequest) returns (Order);

  // Server streaming - single request, stream of responses
  rpc ListOrders(ListOrdersRequest) returns (stream Order);

  // Client streaming - stream of requests, single response
  rpc BatchCreateOrders(stream CreateOrderRequest) returns (BatchCreateResponse);

  // Bidirectional streaming
  rpc OrderUpdates(stream OrderUpdateRequest) returns (stream OrderUpdate);
}

message GetOrderRequest {
  string order_id = 1;
}

message ListOrdersRequest {
  string customer_id = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  Address shipping_address = 3;
}

message BatchCreateResponse {
  repeated Order orders = 1;
  int32 success_count = 2;
  int32 failure_count = 3;
}

message OrderUpdateRequest {
  string order_id = 1;
  OrderStatus new_status = 2;
}

message OrderUpdate {
  string order_id = 1;
  OrderStatus status = 2;
  google.protobuf.Timestamp updated_at = 3;
}

Communication Patterns

gRPC Communication Patterns


Java Implementation

Server Implementation

// Generated from proto
public class OrderServiceImpl extends OrderServiceGrpc.OrderServiceImplBase {

    private final OrderRepository orderRepository;

    // Unary RPC
    @Override
    public void getOrder(GetOrderRequest request,
                        StreamObserver<Order> responseObserver) {
        try {
            Order order = orderRepository.findById(request.getOrderId())
                .orElseThrow(() -> Status.NOT_FOUND
                    .withDescription("Order not found: " + request.getOrderId())
                    .asRuntimeException());

            responseObserver.onNext(order);
            responseObserver.onCompleted();
        } catch (Exception e) {
            responseObserver.onError(Status.INTERNAL
                .withDescription(e.getMessage())
                .asException());
        }
    }

    // Server streaming
    @Override
    public void listOrders(ListOrdersRequest request,
                          StreamObserver<Order> responseObserver) {
        orderRepository.findByCustomerId(request.getCustomerId())
            .forEach(responseObserver::onNext);
        responseObserver.onCompleted();
    }

    // Client streaming
    @Override
    public StreamObserver<CreateOrderRequest> batchCreateOrders(
            StreamObserver<BatchCreateResponse> responseObserver) {

        List<Order> createdOrders = new ArrayList<>();

        return new StreamObserver<>() {
            @Override
            public void onNext(CreateOrderRequest request) {
                Order order = createOrder(request);
                createdOrders.add(order);
            }

            @Override
            public void onError(Throwable t) {
                responseObserver.onError(t);
            }

            @Override
            public void onCompleted() {
                BatchCreateResponse response = BatchCreateResponse.newBuilder()
                    .addAllOrders(createdOrders)
                    .setSuccessCount(createdOrders.size())
                    .build();
                responseObserver.onNext(response);
                responseObserver.onCompleted();
            }
        };
    }

    // Bidirectional streaming
    @Override
    public StreamObserver<OrderUpdateRequest> orderUpdates(
            StreamObserver<OrderUpdate> responseObserver) {

        return new StreamObserver<>() {
            @Override
            public void onNext(OrderUpdateRequest request) {
                // Process update and send response
                OrderUpdate update = processUpdate(request);
                responseObserver.onNext(update);
            }

            @Override
            public void onError(Throwable t) {
                log.error("Error in stream", t);
            }

            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        };
    }
}

// Server setup
public class GrpcServer {
    public static void main(String[] args) throws Exception {
        Server server = ServerBuilder.forPort(9090)
            .addService(new OrderServiceImpl(orderRepository))
            .addService(new HealthServiceImpl())
            .intercept(new AuthInterceptor())
            .intercept(new LoggingInterceptor())
            .build();

        server.start();
        server.awaitTermination();
    }
}

Client Implementation

public class OrderClient {

    private final OrderServiceGrpc.OrderServiceBlockingStub blockingStub;
    private final OrderServiceGrpc.OrderServiceStub asyncStub;

    public OrderClient(ManagedChannel channel) {
        this.blockingStub = OrderServiceGrpc.newBlockingStub(channel);
        this.asyncStub = OrderServiceGrpc.newStub(channel);
    }

    // Unary call (blocking)
    public Order getOrder(String orderId) {
        GetOrderRequest request = GetOrderRequest.newBuilder()
            .setOrderId(orderId)
            .build();

        return blockingStub.getOrder(request);
    }

    // Unary call (async)
    public void getOrderAsync(String orderId, Consumer<Order> callback) {
        GetOrderRequest request = GetOrderRequest.newBuilder()
            .setOrderId(orderId)
            .build();

        asyncStub.getOrder(request, new StreamObserver<>() {
            @Override
            public void onNext(Order order) {
                callback.accept(order);
            }

            @Override
            public void onError(Throwable t) {
                log.error("Error getting order", t);
            }

            @Override
            public void onCompleted() {
                // Done
            }
        });
    }

    // Server streaming (blocking iterator)
    public List<Order> listOrders(String customerId) {
        ListOrdersRequest request = ListOrdersRequest.newBuilder()
            .setCustomerId(customerId)
            .build();

        Iterator<Order> orders = blockingStub.listOrders(request);
        List<Order> result = new ArrayList<>();
        orders.forEachRemaining(result::add);
        return result;
    }

    // Client streaming
    public BatchCreateResponse batchCreate(List<CreateOrderRequest> requests) {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<BatchCreateResponse> responseRef = new AtomicReference<>();

        StreamObserver<CreateOrderRequest> requestObserver =
            asyncStub.batchCreateOrders(new StreamObserver<>() {
                @Override
                public void onNext(BatchCreateResponse response) {
                    responseRef.set(response);
                }

                @Override
                public void onError(Throwable t) {
                    latch.countDown();
                }

                @Override
                public void onCompleted() {
                    latch.countDown();
                }
            });

        // Send all requests
        requests.forEach(requestObserver::onNext);
        requestObserver.onCompleted();

        latch.await();
        return responseRef.get();
    }
}

// Client setup with configuration
public class GrpcClientConfig {

    public ManagedChannel createChannel() {
        return ManagedChannelBuilder
            .forAddress("localhost", 9090)
            .usePlaintext()  // For development
            // .useTransportSecurity()  // For production
            .keepAliveTime(30, TimeUnit.SECONDS)
            .keepAliveTimeout(10, TimeUnit.SECONDS)
            .idleTimeout(5, TimeUnit.MINUTES)
            .intercept(new AuthInterceptor())
            .build();
    }
}

Error Handling

gRPC Status Codes

Code Description
OK (0) Success
CANCELLED (1) Operation cancelled by caller
UNKNOWN (2) Unknown error
INVALID_ARGUMENT Client specified invalid argument
DEADLINE_EXCEEDED Deadline expired
NOT_FOUND (5) Resource not found
ALREADY_EXISTS Resource already exists
PERMISSION_DENIED No permission for operation
RESOURCE_EXHAUSTED Rate limit, quota exceeded
FAILED_PRECONDITION Operation rejected (state issue)
ABORTED (10) Concurrency conflict
OUT_OF_RANGE Value out of valid range
UNIMPLEMENTED Operation not implemented
INTERNAL (13) Internal server error
UNAVAILABLE (14) Service unavailable (retry)
DATA_LOSS (15) Unrecoverable data loss
UNAUTHENTICATED Authentication required

Error Handling in Server

@Override
public void getOrder(GetOrderRequest request,
                    StreamObserver<Order> responseObserver) {
    try {
        if (request.getOrderId().isEmpty()) {
            throw Status.INVALID_ARGUMENT
                .withDescription("Order ID is required")
                .asRuntimeException();
        }

        Order order = orderRepository.findById(request.getOrderId())
            .orElseThrow(() -> Status.NOT_FOUND
                .withDescription("Order not found: " + request.getOrderId())
                .asRuntimeException());

        responseObserver.onNext(order);
        responseObserver.onCompleted();

    } catch (StatusRuntimeException e) {
        responseObserver.onError(e);
    } catch (Exception e) {
        responseObserver.onError(Status.INTERNAL
            .withDescription("Internal error")
            .withCause(e)
            .asException());
    }
}

// Rich error details
import com.google.rpc.BadRequest;
import com.google.rpc.ErrorInfo;
import io.grpc.protobuf.StatusProto;

public void createOrder(CreateOrderRequest request,
                       StreamObserver<Order> responseObserver) {

    List<BadRequest.FieldViolation> violations = validate(request);
    if (!violations.isEmpty()) {
        BadRequest badRequest = BadRequest.newBuilder()
            .addAllFieldViolations(violations)
            .build();

        com.google.rpc.Status status = com.google.rpc.Status.newBuilder()
            .setCode(Code.INVALID_ARGUMENT.getNumber())
            .setMessage("Validation failed")
            .addDetails(Any.pack(badRequest))
            .build();

        responseObserver.onError(StatusProto.toStatusRuntimeException(status));
    }
}

Error Handling in Client

public Order getOrder(String orderId) {
    try {
        return blockingStub
            .withDeadlineAfter(5, TimeUnit.SECONDS)
            .getOrder(GetOrderRequest.newBuilder()
                .setOrderId(orderId)
                .build());

    } catch (StatusRuntimeException e) {
        Status status = e.getStatus();

        switch (status.getCode()) {
            case NOT_FOUND:
                throw new OrderNotFoundException(orderId);
            case INVALID_ARGUMENT:
                throw new InvalidRequestException(status.getDescription());
            case DEADLINE_EXCEEDED:
                throw new TimeoutException("Request timed out");
            case UNAVAILABLE:
                // Could retry
                throw new ServiceUnavailableException();
            default:
                throw new RuntimeException("gRPC error: " + status);
        }
    }
}

Interceptors

Server Interceptor

public class LoggingInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            ServerCall<ReqT, RespT> call,
            Metadata headers,
            ServerCallHandler<ReqT, RespT> next) {

        String method = call.getMethodDescriptor().getFullMethodName();
        long startTime = System.currentTimeMillis();

        log.info("gRPC call started: {}", method);

        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(
            next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
                @Override
                public void close(Status status, Metadata trailers) {
                    long duration = System.currentTimeMillis() - startTime;
                    log.info("gRPC call completed: {} - {} - {}ms",
                        method, status.getCode(), duration);
                    super.close(status, trailers);
                }
            }, headers)) {
        };
    }
}

public class AuthInterceptor implements ServerInterceptor {

    private static final Metadata.Key<String> AUTH_KEY =
        Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            ServerCall<ReqT, RespT> call,
            Metadata headers,
            ServerCallHandler<ReqT, RespT> next) {

        String token = headers.get(AUTH_KEY);

        if (token == null || !validateToken(token)) {
            call.close(Status.UNAUTHENTICATED
                .withDescription("Invalid or missing token"),
                new Metadata());
            return new ServerCall.Listener<>() {};
        }

        // Add user context
        Context context = Context.current()
            .withValue(USER_CONTEXT_KEY, extractUser(token));

        return Contexts.interceptCall(context, call, headers, next);
    }
}

Client Interceptor

public class AuthClientInterceptor implements ClientInterceptor {

    private final String token;

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
            MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions,
            Channel next) {

        return new ForwardingClientCall.SimpleForwardingClientCall<>(
            next.newCall(method, callOptions)) {

            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                headers.put(
                    Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
                    "Bearer " + token
                );
                super.start(responseListener, headers);
            }
        };
    }
}

gRPC vs REST

Aspect gRPC REST
Protocol HTTP/2 HTTP/1.1 or HTTP/2
Format Protocol Buffers JSON/XML
Contract Strict (.proto) Loose (OpenAPI opt.)
Streaming Bidirectional Limited
Browser Support Needs proxy Native
Performance Faster Slower
Human Readable No Yes
Code Generation Required Optional
Tooling Growing Mature

When to use gRPC: - Internal microservice communication - Low latency requirements - Streaming needed - Polyglot environments

When to use REST: - Public APIs - Browser clients - Simple CRUD operations - Human debugging needed


Common Interview Questions

  1. What is Protocol Buffers?
  2. Binary serialization format, schema-defined
  3. Smaller, faster than JSON
  4. Backward/forward compatible

  5. gRPC streaming types?

  6. Unary, server streaming, client streaming, bidirectional

  7. How does gRPC handle errors?

  8. Status codes (NOT_FOUND, INVALID_ARGUMENT, etc.)
  9. Can include rich error details

  10. gRPC vs REST performance?

  11. gRPC: Binary, smaller payload, HTTP/2 multiplexing
  12. 5-10x faster for many use cases

  13. Proto schema evolution?

  14. Add fields with new numbers
  15. Reserve deleted field numbers
  16. Don't change existing field types