Conditional Response Types in gRPC Services

gRPC supports returning different messsage types based on runtime conditions through server-side logic and Protocol Buffers' type system. Two primary approaches enable this functionality:

1. Polymorphic Responses via oneof

The Protocol Buffers oneof feature allows defining a response message that may contain one of several predefined field types. This creates a type-safe union for conditional returns.

syntax = "proto3";

package storage;

message DataFetchResponse {
  oneof content {
    FileData file = 1;
    AccessError error = 2;
  }
}

message FileData {
  bytes payload = 1;
  string checksum = 2;
}

message AccessError {
  int32 status_code = 1;
  string reason = 2;
}

Server implementation (Go):

type StorageService struct {
    pb.UnimplementedStorageServer
}

func (s *StorageService) FetchFile(ctx context.Context, req *pb.FileRequest) (*pb.DataFetchResponse, error) {
    if req.AccessKey == "" {
        return &pb.DataFetchResponse{
            Content: &pb.DataFetchResponse_Error{
                Error: &pb.AccessError{StatusCode: 403, Reason: "Invalid credentials"},
            },
        }, nil
    }

    return &pb.DataFetchResponse{
        Content: &pb.DataFetchResponse_File{
            File: &pb.FileData{Payload: []byte("file_content"), Checksum: "a1b2c3"},
        },
    }, nil
}

2. Distinct Response Messages

Services can define multiple response types and return them conditionally through separate RPC method signatures:

syntax = "proto3";

package auth;

service AuthService {
  rpc ValidateToken(TokenRequest) returns (AuthResult);
}

message TokenRequest {
  string token = 1;
}

message AuthResult {
  oneof outcome {
    ValidationSuccess success = 1;
    ValidationFailure failure = 2;
  }
}

message ValidationSuccess {
  string user_id = 1;
  int64 expires_at = 2;
}

message ValidationFailure {
  int32 error_code = 1;
  string description = 2;
}

Server implementation (Python):

class AuthServicer(auth_pb2_grpc.AuthServiceServicer):
    def ValidateToken(self, request, context):
        if len(request.token) < 10:
            return auth_pb2.AuthResult(
                outcome=auth_pb2.AuthResult(
                    failure=auth_pb2.ValidationFailure(
                        error_code=401, 
                        description="Invalid token format"
                    )
                )
            )
            
        return auth_pb2.AuthResult(
            outcome=auth_pb2.AuthResult(
                success=auth_pb2.ValidationSuccess(
                    user_id="usr_123", 
                    expires_at=1710000000
                )
            )
        )

Both methods leverage Protocol Buffers' type system to enable conditional response structures. The oneof approach provides explicit type constraints within a single message, while distinct message definitions offer structural separation for complex scenarios.

Tags: gRPC ProtocolBuffers oneof MessagePolymorphism rpc

Posted on Mon, 21 Sep 2026 16:56:08 +0000 by Swede78