Error handling with gRPC
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
This article discusses error handling and gRPC:
- Built-in error handling capabilities using gRPC status codes and error messages.
- Sending complex, structured error information using rich error handling.
Built-in error handling
gRPC calls communicate success or failure with a status code. When a gRPC call completes successfully the server returns an OK
status to the client. If an error occurs, gRPC returns:
- An error status code, such as
CANCELLED
orUNAVAILABLE
. - An optional string error message.
The types commonly used with error handling are:
StatusCode
: An enumeration of gRPC status codes.OK
signals success; other values are failure.Status
: Astruct
that combines aStatusCode
and an optional string error message. The error message provides further details about what happened.RpcException
: An exception type that hasStatus
value. This exception is thrown in gRPC server methods and caught by gRPC clients.
Built-in error handling only supports a status code and string description. To send complex error information from the server to the client, use rich error handling.
Throw server errors
A gRPC server call always returns a status. The server automatically returns OK
when a method completes successfully.
public class GreeterService : GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
}
public override async Task SayHelloStreaming(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
for (var i = 0; i < 5; i++)
{
await responseStream.WriteAsync(new HelloReply { Message = $"Hello {request.Name} {i}" });
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
The preceding code:
- Implements the unary
SayHello
method that completes successfully when it returns a response message. - Implements the server streaming
SayHelloStreaming
method that completes successfully when the method finished.
Server error status
gRPC methods return an error status code by throwing an exception. When an RpcException
is thrown on the server, its status code and description is returned to the client:
public class GreeterService : GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
if (string.IsNullOrEmpty(request.Name))
{
throw new RpcException(new Status(StatusCode.InvalidArgument, "Name is required."));
}
return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
}
}
Thrown exception types that aren't RpcException
also cause the call to fail, but with an UNKNOWN
status code and a generic message Exception was thrown by handler
.
Exception was thrown by handler
is sent to the client rather than the exception message to prevent exposing potentially sensitive information. To see a more descriptive error message in a development environment, configure EnableDetailedErrors
.
Handle client errors
When a gRPC client makes a call, the status code is automatically validated when accessing the response. For example, awaiting a unary gRPC call returns the message sent by the server if the call is successful, and throws an RpcException
if there's a failure. Catch RpcException
to handle errors in a client:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex)
{
Console.WriteLine("Status code: " + ex.Status.StatusCode);
Console.WriteLine("Message: " + ex.Status.Detail);
}
The preceding code:
- Makes a unary gRPC call to the
SayHello
method. - Writes the response message to the console if it's successful.
- Catches
RpcException
and writes out the error details on failure.
Error scenarios
Errors are represented by RpcException
with an error status code and optional detail message. RpcException
is thrown in many scenarios:
- The call failed on the server and the server sent an error status code. For example, the gRPC client started a call that was missing required data from the request message and the server returns an
INVALID_ARGUMENT
status code. - An error occurred inside the client when making the gRPC call. For example, a client makes a gRPC call, can't connect to the server, and throws an error with a status of
UNAVAILABLE
. - The CancellationToken passed to the gRPC call is canceled. The gRPC call is stopped and the client throws an error with a status of
CANCELLED
. - A gRPC call exceeds its configured deadline. The gRPC call is stopped and the client throws an error with a status of
DEADLINE_EXCEEDED
.
Rich error handling
Rich error handling allows complex, structured information to be sent with error messages. For example, validation of incoming message fields that returns a list of invalid field names and descriptions. The google.rpc.Status
error model is often used to send complex error information between gRPC apps.
gRPC on .NET supports a rich error model using the Grpc.StatusProto
package. This package has methods for creating rich error models on the server and reading them by a client. The rich error model builds on top of gRPC's built-in handling capabilities and they can be used side-by-side.
Important
Errors are included in headers, and total headers in responses are often limited to 8 KB (8,192 bytes). Ensure that the headers containing errors do not exceed 8 KB.
Creating rich errors on the server
Rich errors are created from Google.Rpc.Status
. This type is different from Grpc.Core.Status
.
Google.Rpc.Status
has status, message, and details fields. The most important field is details, which is a repeating field of Any
values. Details are where complex payloads are added.
Although any message type can be used as a payload, it's recommended to use one of the standard error payloads:
BadRequest
PreconditionFailure
ErrorInfo
ResourceInfo
QuotaFailure
Grpc.StatusProto
includes the ToRpcException
a helper method to convert Google.Rpc.Status
to an error. Throw the error from the gRPC server method:
public class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
ArgumentNotNullOrEmpty(request.Name);
return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
}
public static void ArgumentNotNullOrEmpty(string value, [CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (string.IsNullOrEmpty(value))
{
var status = new Google.Rpc.Status
{
Code = (int)Code.InvalidArgument,
Message = "Bad request",
Details =
{
Any.Pack(new BadRequest
{
FieldViolations =
{
new BadRequest.Types.FieldViolation { Field = paramName, Description = "Value is null or empty" }
}
})
}
};
throw status.ToRpcException();
}
}
}
Reading rich errors by a client
Rich errors are read from the RpcException
caught in the client. Catch the exception and use helper methods provided by Grpc.StatusCode
to get its Google.Rpc.Status
instance:
var client = new Greet.GreeterClient(channel);
try
{
var reply = await client.SayHelloAsync(new HelloRequest { Name = name });
Console.WriteLine("Greeting: " + reply.Message);
}
catch (RpcException ex)
{
Console.WriteLine($"Server error: {ex.Status.Detail}");
var badRequest = ex.GetRpcStatus()?.GetDetail<BadRequest>();
if (badRequest != null)
{
foreach (var fieldViolation in badRequest.FieldViolations)
{
Console.WriteLine($"Field: {fieldViolation.Field}");
Console.WriteLine($"Description: {fieldViolation.Description}");
}
}
}
The preceding code:
- Makes a gRPC call inside a try/catch that catches
RpcException
. - Calls
GetRpcStatus()
to attempt to get the rich error model from the exception. - Calls
GetDetail<BadRequest>()
to attempt to get aBadRequest
payload from the rich error.
Additional resources
ASP.NET Core