gRPC vs REST: A Decision Framework for .NET APIs

TL;DR Performance comparison, streaming patterns, browser support, and decision criteria for choosing between gRPC and REST in ASP.NET Core applications.

REST has been the default choice for APIs for over a decade. gRPC offers better performance but comes with tradeoffs.

The right choice depends on your clients, your team, and your constraints. This article gives you a framework for deciding.

Common questions this answers

  • When should I use gRPC instead of REST?
  • How much faster is gRPC than REST?
  • Can browsers call gRPC services?
  • How do I implement streaming with gRPC?
  • Can I use both gRPC and REST in the same application?

Definition (what this means in practice)

gRPC is a high-performance Remote Procedure Call framework that uses Protocol Buffers for serialization and HTTP/2 for transport. REST is an architectural style for APIs that typically uses JSON over HTTP.

In practice, gRPC excels for service-to-service communication where performance matters, while REST remains the standard for browser-accessible public APIs.

Terms used

  • Protocol Buffers (protobuf): a binary serialization format that is smaller and faster than JSON.
  • HTTP/2: a protocol that supports multiplexing, streaming, and binary framing.
  • Streaming: sending multiple messages over a single connection (server streaming, client streaming, or bidirectional).
  • gRPC-Web: a browser-compatible variant of gRPC that works over HTTP/1.1.
  • Contract-first: defining the API schema before implementation (using .proto files).

Reader contract

This article is for:

  • Architects choosing between gRPC and REST for new services.
  • Teams evaluating gRPC for microservices communication.
  • Developers implementing high-performance APIs.

You will leave with:

  • A scoring rubric for choosing between gRPC and REST.
  • Implementation patterns for both approaches.
  • Understanding of when hybrid approaches make sense.

This is not for:

  • GraphQL comparisons (different problem space).
  • Message queue or event-driven architecture decisions.

Quick start (10 minutes)

If you do nothing else, understand this decision tree:

Use REST when:

  • Browsers need direct access (public APIs)
  • Human readability matters (debugging, documentation)
  • Team is unfamiliar with gRPC
  • Simple CRUD operations with standard HTTP semantics

Use gRPC when:

  • Service-to-service communication (microservices)
  • Low latency and high throughput required
  • Streaming data (real-time updates, large file transfers)
  • Bandwidth is constrained (mobile, IoT)
  • Multiple languages need to communicate

Feature comparison

Feature gRPC REST with JSON
Contract Required (.proto) Optional (OpenAPI)
Protocol HTTP/2 HTTP/1.1 or HTTP/2
Payload format Protobuf (binary) JSON (text)
Message size Smaller Larger
Browser support Requires gRPC-Web Native
Streaming Client, server, bidirectional Limited
Code generation Built-in Third-party tools
Human readable No Yes
Tooling maturity Growing Mature

Performance comparison

Message size

Protobuf messages are often smaller than equivalent JSON. Depending on the message shape, Microsoft notes gRPC payloads can be 60-80% smaller than JSON.

// JSON (89 bytes)
{"userId":12345,"name":"John Doe","email":"john@example.com","active":true}

// Protobuf (approximately 45-50 bytes)
// Binary representation of the same data

For high-volume APIs, this difference compounds significantly.

Serialization speed

Protobuf serialization is faster than JSON because:

  • Binary format requires no parsing of text
  • Schema is known at compile time
  • No reflection needed for strongly-typed languages

Depending on message complexity, Microsoft notes gRPC can be up to 8x faster than JSON serialization.

HTTP/2 benefits

gRPC requires HTTP/2, which provides:

  • Multiplexing: multiple requests share one TCP connection
  • Header compression: reduces overhead for repeated headers
  • Binary framing: more efficient than text-based HTTP/1.1

REST APIs can also use HTTP/2, but gRPC is designed around its features.

When performance matters

The performance difference is most significant when:

  • Message volume is high (thousands of requests per second)
  • Payload sizes are large
  • Network bandwidth is constrained
  • Latency requirements are strict (sub-millisecond)

For typical web applications with moderate traffic, the performance difference may not justify the added complexity.

Streaming patterns

gRPC supports four communication patterns:

Unary (request-response)

Same as REST. Client sends one message, server responds with one message:

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
}

Server streaming

Client sends one request, server sends multiple responses:

service OrderService {
  rpc GetOrderUpdates(GetOrderRequest) returns (stream OrderUpdate);
}

Use case: real-time price feeds, log streaming, progress updates.

// Server implementation
public override async Task GetOrderUpdates(
    GetOrderRequest request,
    IServerStreamWriter<OrderUpdate> responseStream,
    ServerCallContext context)
{
    while (!context.CancellationToken.IsCancellationRequested)
    {
        var update = await GetLatestUpdate(request.OrderId);
        await responseStream.WriteAsync(update);
        await Task.Delay(1000);
    }
}

// Client usage
using var call = client.GetOrderUpdates(new GetOrderRequest { OrderId = 123 });
await foreach (var update in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"Order status: {update.Status}");
}

Client streaming

Client sends multiple messages, server responds once:

service UploadService {
  rpc UploadFile(stream FileChunk) returns (UploadResult);
}

Use case: file uploads, batch processing, aggregating data.

Bidirectional streaming

Both client and server send multiple messages:

service ChatService {
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

Use case: chat applications, collaborative editing, real-time synchronization.

Browser support

Browsers cannot make native gRPC calls because they lack the required HTTP/2 control. Two solutions exist:

gRPC-Web

A browser-compatible protocol that works over HTTP/1.1:

// Program.cs - Enable gRPC-Web
app.UseGrpcWeb();
app.MapGrpcService<GreeterService>().EnableGrpcWeb();

Limitations:

  • No client streaming or bidirectional streaming
  • Requires gRPC-Web client library in the browser
  • Slight overhead compared to native gRPC

gRPC JSON transcoding

Available in .NET 7+, automatically exposes gRPC services as REST APIs:

// Program.cs
builder.Services.AddGrpc().AddJsonTranscoding();
import "google/api/annotations.proto";

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/greeter/{name}"
    };
  }
}

Now the same service is accessible via:

  • gRPC: grpc://localhost:5001
  • REST: GET /v1/greeter/World

This is ideal for services that need both internal gRPC efficiency and external REST accessibility.

Error handling differences

gRPC status codes

gRPC uses a fixed set of status codes:

Code Meaning
OK (0) Success
CANCELLED (1) Operation cancelled
UNKNOWN (2) Unknown error
INVALID_ARGUMENT (3) Client sent invalid data
NOT_FOUND (5) Resource not found
ALREADY_EXISTS (6) Resource already exists
PERMISSION_DENIED (7) Caller lacks permission
UNAUTHENTICATED (16) No valid credentials
DEADLINE_EXCEEDED (4) Operation timed out

REST HTTP status codes

REST uses HTTP status codes (200, 400, 404, 500, etc.) with more granularity but less consistency across implementations.

Deadlines and cancellation

gRPC has built-in deadline propagation:

// Client sets deadline
var deadline = DateTime.UtcNow.AddSeconds(5);
var response = await client.GetOrderAsync(
    request,
    deadline: deadline);

// Server checks deadline
if (context.CancellationToken.IsCancellationRequested)
{
    throw new RpcException(new Status(StatusCode.Cancelled, "Deadline exceeded"));
}

REST requires manual timeout handling with HTTP client configuration.

Implementation patterns

gRPC service setup

// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();

var app = builder.Build();

app.MapGrpcService<OrderService>();

app.Run();
// Protos/order.proto
syntax = "proto3";

option csharp_namespace = "MyApp.Grpc";

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc GetOrderUpdates(GetOrderRequest) returns (stream OrderUpdate);
}

message GetOrderRequest {
  int32 order_id = 1;
}

message CreateOrderRequest {
  string product_id = 1;
  int32 quantity = 2;
}

message Order {
  int32 id = 1;
  string product_id = 2;
  int32 quantity = 3;
  string status = 4;
}

message OrderUpdate {
  int32 order_id = 1;
  string status = 2;
  string timestamp = 3;
}

gRPC client with factory

// Program.cs - Register client
builder.Services.AddGrpcClient<OrderService.OrderServiceClient>(options =>
{
    options.Address = new Uri("https://orders-service:5001");
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new SocketsHttpHandler
    {
        EnableMultipleHttp2Connections = true,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
    };
    return handler;
});

// Usage in a service
public class CheckoutService(OrderService.OrderServiceClient orderClient)
{
    public async Task<Order> CreateOrderAsync(string productId, int quantity)
    {
        var request = new CreateOrderRequest
        {
            ProductId = productId,
            Quantity = quantity
        };

        return await orderClient.CreateOrderAsync(request);
    }
}

Decision framework

Score each criterion based on your requirements (1 = not important, 3 = critical):

Criterion Favors gRPC Favors REST Your Score
Browser clients needed X
Service-to-service only X
Low latency required X
Streaming needed X
Team gRPC experience X
Human-readable debugging X
Existing REST infrastructure X
Bandwidth constrained X
Public API consumers X
Polyglot environment X

Scoring:

  • gRPC score > REST score: Use gRPC
  • REST score > gRPC score: Use REST
  • Scores similar: Consider hybrid approach

Common scenarios

Microservices backend: gRPC for internal communication, REST or gRPC-Web for external APIs.

Mobile application: gRPC for efficiency, especially on slow networks.

Public API platform: REST for accessibility, consider gRPC for premium/partner APIs.

Real-time dashboard: gRPC streaming for live updates.

Simple CRUD API: REST, unless specific performance requirements exist.

Hybrid approaches

Many systems use both gRPC and REST:

Pattern 1: Internal gRPC, external REST

Browser/Mobile -> REST API Gateway -> gRPC Microservices

The API gateway handles REST-to-gRPC translation.

Pattern 2: gRPC JSON transcoding

Single service exposes both protocols:

builder.Services.AddGrpc().AddJsonTranscoding();

// Service is accessible via both gRPC and REST
app.MapGrpcService<OrderService>();

Pattern 3: Protocol per client type

Internal services -> gRPC
Web browsers -> REST
Mobile apps -> gRPC (native) or REST (web views)

Copy/paste artifact: gRPC service setup

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add gRPC services
builder.Services.AddGrpc(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16 MB
    options.MaxSendMessageSize = 16 * 1024 * 1024;
});

// Optional: Add gRPC-Web support for browsers
builder.Services.AddGrpcWeb();

// Optional: Add JSON transcoding for REST compatibility
builder.Services.AddGrpc().AddJsonTranscoding();

var app = builder.Build();

// Enable gRPC-Web
app.UseGrpcWeb();

// Map gRPC services
app.MapGrpcService<OrderService>().EnableGrpcWeb();

app.Run();
<!-- Project file -->
<ItemGroup>
  <PackageReference Include="Grpc.AspNetCore" Version="2.*" />
  <PackageReference Include="Grpc.AspNetCore.Web" Version="2.*" />
  <Protobuf Include="Protos\*.proto" GrpcServices="Server" />
</ItemGroup>

Copy/paste artifact: gRPC vs REST checklist

gRPC vs REST Decision Checklist

1. Client requirements
   - [ ] Browser access needed? -> Favor REST or add gRPC-Web
   - [ ] Mobile native apps? -> gRPC viable
   - [ ] Service-to-service only? -> gRPC preferred

2. Performance requirements
   - [ ] Sub-millisecond latency? -> gRPC
   - [ ] High message volume? -> gRPC
   - [ ] Bandwidth constrained? -> gRPC
   - [ ] Moderate traffic, no strict latency? -> Either works

3. Feature requirements
   - [ ] Streaming needed? -> gRPC
   - [ ] Bidirectional communication? -> gRPC
   - [ ] Simple CRUD? -> REST sufficient

4. Team and tooling
   - [ ] Team familiar with gRPC? -> gRPC viable
   - [ ] Existing REST infrastructure? -> Consider staying REST
   - [ ] Need human-readable debugging? -> REST easier

5. API consumers
   - [ ] Public third-party developers? -> REST
   - [ ] Internal teams only? -> gRPC viable
   - [ ] Multiple languages? -> gRPC (good codegen)

Common failure modes

  1. Using gRPC for public APIs without fallback: external developers expect REST. Provide gRPC-Web or JSON transcoding.
  2. Creating new HTTP/2 connections per request: reuse channels. Use gRPC client factory.
  3. Ignoring deadlines: gRPC has built-in deadline support. Use it to prevent hung requests.
  4. Large messages without streaming: for payloads over a few MB, use streaming to avoid memory pressure.
  5. Blocking on async calls: always use async/await with gRPC. Blocking causes thread pool starvation.

Checklist

  • Client types identified (browser, mobile, service).
  • Performance requirements quantified.
  • Streaming needs assessed.
  • Team gRPC experience evaluated.
  • Hybrid approach considered if requirements mixed.
  • gRPC-Web or JSON transcoding planned for browser access.

FAQ

Is gRPC always faster than REST?

For equivalent operations, gRPC is typically faster due to binary serialization and HTTP/2. However, the difference may be negligible for simple, low-volume APIs. Measure your specific use case.

Can I migrate from REST to gRPC incrementally?

Yes. Use gRPC JSON transcoding to expose both protocols from the same service. Migrate clients gradually while maintaining REST compatibility.

Do I need to learn Protocol Buffers?

Yes, but the basics are straightforward. Proto files define messages and services. Code generation handles the rest. Most developers learn enough in a few hours.

What about versioning?

Both support versioning. gRPC uses package names and field numbers (adding fields is backward compatible). REST uses URL versioning or headers. Neither is inherently better.

Should I use gRPC for CRUD APIs?

For simple CRUD with browser clients, REST is usually simpler. For high-performance CRUD between services, gRPC offers benefits. Evaluate based on your specific requirements.

How do I debug gRPC calls?

Use gRPC server reflection and tools like grpcurl. Enable detailed errors in development. For production, use structured logging and distributed tracing.

What to do next

Identify your highest-volume service-to-service communication. Evaluate whether gRPC would provide meaningful performance benefits. If browser access is required, plan for gRPC-Web or JSON transcoding from the start.

For more on building resilient service communication, read Resilience Patterns with Polly: Circuit Breakers, Retries, and Timeouts.

If you want help evaluating gRPC for your architecture, reach out via Contact.

References

Author notes

Decisions:

  • Present as decision framework, not recommendation. Rationale: the right choice depends heavily on context.
  • Include hybrid approaches prominently. Rationale: most real systems use both protocols.
  • Focus on .NET implementation patterns. Rationale: audience is ASP.NET Core developers.

Observations:

  • Teams often choose REST by default without evaluating gRPC for internal services.
  • gRPC adoption increases significantly in microservices architectures.
  • Browser support is the most common blocker for gRPC adoption.
  • JSON transcoding in .NET 7+ makes hybrid approaches much simpler.