RPC vs REST?

If you have ever sat in a design review where one person says “Just make it RESTful” and another replies “RPC is faster and simpler,” you have seen the real problem: most teams argue about style while they should be arguing about constraints. RPC and REST are not rival religions. They are different ways to model and evolve distributed systems, and the best choice depends on what you are optimizing for: discoverability, scalability, performance, developer experience, or long term maintainability.

This guide breaks down RPC vs REST in plain language, then gets practical with examples, selection criteria, and patterns you can apply to real APIs. By the end, you should be able to choose intentionally, defend the decision, and avoid the common traps that make either approach painful.

What RPC and REST Actually Mean (Beyond the Buzzwords)

Before comparing, it helps to define them in a way that matches how they behave in production.

RPC in one sentence

RPC (Remote Procedure Call) is when a client calls a named operation on a remote server as if it were a local function, passing parameters and receiving a result.

In practice, RPC APIs typically look like:

  • CreateUser(request) -> response
  • GetBalance(accountId) -> balance
  • ChargeCard(amount, token) -> receipt

RPC can be built over HTTP (common), but it does not have to be. Popular RPC frameworks include gRPC, Thrift, JSON-RPC, and XML-RPC.

REST in one sentence

REST (Representational State Transfer) is an architectural style where you model your system as resources and operate on them using standardized semantics (usually HTTP methods like GET, POST, PUT, PATCH, DELETE).

REST APIs typically look like:

  • GET /users/123
  • POST /users
  • PATCH /users/123
  • GET /accounts/123/transactions?limit=50

REST is not “JSON over HTTP.” REST is about resource identification, stateless interactions, cacheability, and a uniform interface.

A quick mental model

A useful shortcut:

  • RPC says: “Call this action.”
  • REST says: “Manipulate this resource.”

Neither is universally better. The tradeoff is where you want your complexity to live: in custom methods and contracts (RPC) or in resource modeling and HTTP semantics (REST).

API Design Philosophy: Actions vs Resources

The most visible difference in RPC vs REST API design is whether the API surface is a set of verbs or a set of nouns.

REST example: resource-oriented modeling

Imagine a simple task management service.

REST endpoints:

  • POST /tasks to create a task
  • GET /tasks/{id} to fetch a task
  • PATCH /tasks/{id} to update fields
  • DELETE /tasks/{id} to remove it

Requests and responses are typically JSON:

POST /tasks
Content-Type: application/json

{
  "title": "Write RPC vs REST article",
  "dueDate": "2026-01-20"
}
201 Created
Location: /tasks/42
Content-Type: application/json

{
  "id": 42,
  "title": "Write RPC vs REST article",
  "dueDate": "2026-01-20",
  "status": "open"
}

This style tends to be intuitive for public APIs and for systems where resources have stable identities and lifecycles.

RPC example: action-oriented modeling

Now model the same domain with operations:

POST /TaskService/CreateTask
{
  "title": "Write RPC vs REST article",
  "due_date": "2026-01-20"
}
{
  "task_id": 42
}

RPC can be extremely straightforward when your domain is naturally action-heavy (billing, orchestration, ML inference, or internal microservice calls).

Where teams get stuck: “REST with verbs”

A common compromise is a REST-ish URL plus RPC semantics:

  • POST /tasks/42/close
  • POST /users/123/reset-password
  • POST /invoices/987/refund

This is not automatically wrong. Some actions do not map cleanly to CRUD. The key is to be consistent and intentional:

  • If the action creates a new resource, model it as a resource.
  • If it triggers a process with side effects, treat it as an operation endpoint, and document idempotency and retries carefully.

Protocols, Performance, and Tooling (gRPC vs REST in the Real World)

A big reason “RPC vs REST” debates get heated is that people conflate REST with “HTTP + JSON” and RPC with “gRPC + Protobuf.” Those pairings are common, but not required. Still, the common stacks have practical implications.

Serialization and payload size

  • REST commonly uses JSON, which is human-readable but verbose.
  • gRPC commonly uses Protocol Buffers, which are compact and faster to parse.

If you are moving large volumes of data or you have tight latency budgets, binary formats can help. If you are building a public developer platform, JSON’s readability is often worth the overhead.

HTTP semantics and intermediaries

REST embraces HTTP features:

  • Caching via Cache-Control, ETag, Last-Modified
  • Content negotiation
  • Standard status codes and semantics
  • Better compatibility with proxies, gateways, and debugging tools

RPC over HTTP can still use these, but many RPC APIs treat HTTP as a transport and ignore the semantics, which can make caching and observability harder unless you build those layers yourself.

Streaming and real-time behavior

gRPC has first-class streaming:

  • Server streaming for feeds
  • Client streaming for uploads
  • Bidirectional streaming for interactive workflows

REST can do streaming via SSE, WebSockets, or chunked responses, but it is less standardized across typical REST stacks.

Tooling and developer experience

What tends to matter in practice:

  • REST: easy to call from curl, browsers, Postman; easy onboarding; great for external integrations.
  • gRPC/RPC: strong type safety, code generation, schema evolution patterns, and consistent contracts for internal teams.

A practical way to frame it:

  • If you expect your API to be consumed by many unknown clients, REST’s loose coupling and ergonomics shine.
  • If you control both ends, RPC can reduce ambiguity and speed up development.

Versioning, Compatibility, and Change Management

Most API failures are not about whether you picked REST or RPC. They are about how you handle change.

REST versioning patterns

REST APIs often version at the URL or header:

  • GET /v1/users/123
  • Accept: application/vnd.company.v1+json

The best REST designs aim for evolution without frequent version bumps by:

  • Adding optional fields instead of changing meanings
  • Avoiding breaking changes to existing fields
  • Using PATCH for partial updates
  • Returning stable identifiers and consistent error shapes

RPC versioning patterns

With gRPC and Protobuf, you typically evolve schemas by:

  • Adding new fields with new field numbers
  • Never reusing field numbers
  • Keeping old fields around even if deprecated
  • Using oneof for variant payloads

RPC can be excellent for safe evolution if the team follows schema discipline. The downside is that a “simple change” often requires regenerating clients, updating shared libraries, and coordinating rollouts across services.

Actionable advice: decide your compatibility contract

Regardless of style, write down what is guaranteed:

  • Can clients rely on unknown fields being ignored?
  • Are fields ever removed or renamed?
  • Are enum values append-only?
  • Are errors stable and machine-parseable?
  • Are requests idempotent for retries?

If your org has multiple teams, these rules matter more than choosing between RPC and REST.

Practical Examples: Same Feature, Two API Styles

Let’s implement a “transfer money” capability, which is a great test because it is not pure CRUD.

REST approach: model transfers as resources

Transfers have identity, state, and lifecycle, so treating them as a resource is natural.

POST /transfers
Content-Type: application/json
Idempotency-Key: 9f3b9a7b-2c91-4a37-8f4b-3b8d4f1c2a11

{
  "fromAccountId": "A123",
  "toAccountId": "B456",
  "amount": 2500,
  "currency": "USD"
}
202 Accepted
Content-Type: application/json

{
  "id": "T789",
  "status": "pending"
}

Then the client checks status:

GET /transfers/T789

Benefits:

  • Easy to retry with idempotency keys.
  • Naturally supports asynchronous processing.
  • Great auditability and traceability.

RPC approach: expose a transfer operation

service Payments {
  rpc Transfer(TransferRequest) returns (TransferResponse);
}

message TransferRequest {
  string from_account_id = 1;
  string to_account_id = 2;
  int64 amount = 3;
  string currency = 4;
  string idempotency_key = 5;
}

message TransferResponse {
  string transfer_id = 1;
  string status = 2;
}

Benefits:

  • Very explicit intent.
  • Strong typing and generated clients.
  • Easy to express complex operations without forcing them into CRUD.

Both can be designed well. The real question is what your ecosystem needs: public integration friendliness (REST) or internal contract rigor and performance (RPC).

Choosing Between RPC and REST: A Decision Checklist

Instead of asking “Which is better?”, ask “Which fits this system’s constraints?” Use the checklist below to make the decision concrete.

Choose REST when…

  • You are building a public API or partner integration where clients are diverse and not controlled by you.
  • You benefit from HTTP semantics like caching, conditional requests, and standardized status codes.
  • Your domain maps well to resources with stable identifiers.
  • You want excellent debuggability using common tools like curl and browser devtools.

REST tends to reduce friction for external adopters and aligns well with API gateways, CDNs, and standard security tooling.

Choose RPC when…

  • You mostly have service-to-service communication inside an organization.
  • You want strong contracts, code generation, and compile-time safety.
  • You need low latency or high throughput and prefer binary serialization.
  • You want built-in streaming patterns (especially with gRPC).

RPC tends to shine in microservices where teams control both ends and want consistency and speed of delivery.

Watch-outs for REST

  • Resource modeling takes effort, especially for action-heavy domains.
  • Without discipline, you get inconsistent endpoints and unclear semantics.
  • Over-fetching and under-fetching can happen if resources are not shaped carefully. You may end up inventing custom query parameters that mimic RPC.

Watch-outs for RPC

  • Debugging can be harder without the right tooling, especially for binary payloads.
  • Tight coupling can sneak in if clients rely on internal details.
  • Cross-language and edge-client support can be tricky, particularly for browsers (gRPC-Web exists, but it adds complexity).

A hybrid approach often wins

Many mature systems do this:

  • REST for external APIs and developer-facing platforms.
  • gRPC/RPC for internal microservices where performance and strong contracts matter.
  • Shared concepts: consistent auth, rate limits, observability, error formats, idempotency, and tracing across both.

If you go hybrid, do not let it become “anything goes.” Establish conventions so teams do not reinvent patterns per service.

Implementation Tips That Pay Off Immediately

Whether you land on REST or RPC, these practices prevent common production issues.

Define idempotency for write operations

Retries happen because networks fail, clients time out, and queues redeliver. Make writes safe:

  • REST: support an Idempotency-Key header for POST operations that create resources or trigger payments.
  • RPC: include an idempotency_key field and store request fingerprints server-side.

Standardize error handling

Clients need errors that are consistent and actionable.

For REST, a structured JSON error is common:

{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Account A123 has insufficient funds",
    "details": {
      "available": 1200,
      "required": 2500
    }
  }
}

For gRPC, use status codes plus rich error details (for example, Google’s error details model) and document them.

Document latency and SLO expectations

The API style will not save you from unclear expectations. Publish:

  • Typical and worst-case latency
  • Rate limits
  • Timeout recommendations
  • Whether operations are synchronous or asynchronous
  • Retry guidance, including which errors are safe to retry

Treat naming as part of usability

  • REST: keep resource names plural and consistent (/users, /orders, /transfers).
  • RPC: name methods with clear verbs (CreateUser, ListOrders, TransferFunds) and avoid vague names like Execute or Process.

Clear naming reduces support tickets more than almost any framework choice.

Conclusion: Pick the Constraints, Not the Camp

The most useful way to think about RPC vs REST is that REST optimizes for a uniform interface and broad compatibility, while RPC optimizes for explicit operations and strong contracts. REST often wins for public APIs and resource-centric domains. RPC often wins for internal services, high performance needs, and streaming.

If you are deciding right now, do this:

  • List your top three constraints (public consumers, latency, caching, streaming, team autonomy, tooling).
  • Choose the style that reduces risk against those constraints.
  • Write down compatibility rules, error standards, and idempotency requirements so the API stays reliable as it evolves.

If you want a second opinion, map one real feature in both styles (like the transfer example) and compare the operational realities: retries, monitoring, client updates, and documentation burden. That quick exercise usually makes the right answer obvious.