EM
Emmanuel Maneswa
All Posts
API Design

The Future of Banking APIs: gRPC vs REST in Core Financial Systems

A deep dive into gRPC and REST across five architectural pillars: serialization efficiency, HTTP/2 multiplexing, contract-first governance, streaming patterns, and mTLS security. Contract-first gRPC server and client code across seven backend stacks, plus a decision matrix for Open Banking versus internal microservices.

EM

Emmanuel Maneswa

Full Stack Software Engineer

1 June 2026·15 min read
#gRPC#REST#Microservices#API Design#Core Banking#Performance#C##Java

The Future of Banking APIs: gRPC vs REST in Core Financial Systems

Two Protocols, Two Trust Boundaries: Choosing the Right One for Every Banking API

Every bank now runs two API surfaces that look nothing alike. On the outside: Open Banking under PSD2 and the UK's Open Banking Standard, public third-party integrations, and mobile clients, all expecting REST over JSON because that's what the entire web understands. On the inside: core banking, fraud detection, and settlement engines calling each other thousands of times per second, where the cost of a verbose, un-typed, text-parsed payload compounds with every hop.

REST and gRPC are not competitors fighting for the same job. They're two protocols built for two different trust boundaries, and the architectural mistake I see most often is picking one and forcing it across both. This post is a working reference for when each one is the right call, backed by the network physics that actually drive the decision, not just protocol preference.


The Evolution & Problem Statement in Financial APIs

Open Banking regulation created a genuine requirement for REST: PSD2 in the EU and the UK's Open Banking Standard both mandate JSON-over-HTTPS APIs so that third-party providers, budgeting apps, payment initiation services, account aggregators, can integrate without needing anything beyond a standard HTTP client. This is a deliberate, correct choice: the API's consumers are external, numerous, and unknown at design time, and REST's universal tooling and human-readable payloads lower the integration bar for everyone.

Internally, the picture is different. A single customer-facing balance check might fan out into calls to the core banking ledger, a fraud scoring service, and a risk engine, all before the response reaches the client. Each of these is an internal microservice with a known, versioned contract and a latency budget measured in single-digit milliseconds. This is where REST's overhead starts to matter in a way it never does for a one-off public API call.

The real cost is network physics, not developer preference. JSON is a text format: every field name is repeated in every message, every number is parsed as a string and converted, and every HTTP/1.1 request typically opens (or reuses from a limited pool) its own TCP connection. At the volume a settlement engine or a fraud pipeline runs at, thousands of internal calls per second, the CPU cycles spent on JSON serialization and parsing, and the connection overhead of HTTP/1.1, become a measurable fraction of total latency. gRPC, built on HTTP/2 and Protocol Buffers, was designed specifically for this: binary serialization, a single multiplexed connection per service, and a schema contract enforced at compile time rather than hoped for at runtime.


Request Flow: REST Serial Calls vs gRPC Multiplexed Streams

The diagram makes the physical difference concrete. Under REST/HTTP1.1, each downstream call, balance, then risk score, opens its own connection and completes serially from the gateway's perspective. Under gRPC/HTTP2, both calls travel as independent streams over the same multiplexed TCP connection and can be issued concurrently. The gateway isn't doing anything architecturally different in either case, the transport protocol itself is what changes the shape of the request timeline.


Five Architectural Pillars

1. Serialization & Payload Efficiency

Protocol Buffers encode messages as binary, with fields identified by number rather than repeated string keys, and typed at the schema level (int64, a structured decimal, a nested message) rather than left to runtime string parsing. JSON, by contrast, repeats every field name in every message and represents every number as a string that must be parsed and validated on receipt.

MetricJSON (REST)Protocol Buffers (gRPC)
EncodingUTF-8 text, human-readableBinary, field-tagged
Field identificationRepeated string keys per messageNumeric field tags, defined once in the schema
Typical payload size (a balance response)Larger — field names repeated, numbers as stringsCommonly 60–80% smaller for the same data
Serialization/deserialization costHigher — string parsing and type coercion on every fieldLower — binary decode against a known schema, no runtime type guessing

At low request volumes, this difference is invisible. At the volume a real-time settlement or fraud pipeline runs at, it is a measurable, compounding cost, both in server CPU spent on serialization and in bytes moved across the network.

2. Transport Protocol & Concurrency

HTTP/1.1 has head-of-line blocking on a per-connection basis: a slow response blocks everything queued behind it on that connection, which is why HTTP/1.1 clients open connection pools to compensate. HTTP/2, which gRPC is built on, multiplexes many independent streams over a single TCP connection, so a slow response on one stream never blocks another. gRPC also supports server push and native streaming (covered below), none of which REST/HTTP1.1 offers without workarounds like long-polling or Server-Sent Events.

3. API Governance & Contract-First Development

REST APIs are typically documented after the fact, or alongside development, with OpenAPI/Swagger specs that describe the contract but don't enforce it, nothing stops a service from returning a field the spec doesn't mention, or omitting one it promises. gRPC inverts this: the .proto file is the contract, and both client and server stubs are generated from it. A field renamed or removed in the .proto is a compile error in every consuming service, not a runtime surprise discovered when a payment fails to parse in production. In a multi-team banking integration estate, this is the difference between schema drift being caught in code review and schema drift being caught by an incident.

4. Streaming Capabilities

gRPC defines four communication patterns; REST, built on request-response, has none of them natively.

PatternDescriptionBanking Example
UnaryOne request, one response — the REST-equivalent shapeGetBalance(accountId) → Balance
Server StreamingOne request, a stream of responsesReal-time account balance or market-data feed pushed to a client as it changes
Client StreamingA stream of requests, one responseBulk batch payment upload, where the client streams thousands of payment records and receives a single summary result
Bidirectional StreamingBoth sides stream independently over the same callReal-time order-book matching, where price updates and order submissions flow concurrently in both directions

Server-streaming and bidirectional streaming in particular have no clean REST equivalent; approximating them requires polling, Server-Sent Events, or WebSockets bolted on beside the REST API rather than a native part of the protocol.

5. Security, Authentication & Observability

Both protocols support TLS, but gRPC's ecosystem treats mutual TLS (mTLS) as a first-class deployment pattern for service-to-service authentication, each side presents and verifies a certificate, not just the server. Authentication tokens (OAuth2 bearer tokens) and tracing data (correlation IDs) travel as gRPC metadata, attached via client and server interceptors that run on every call without the application code needing to remember to add a header each time. REST achieves the same outcome with HTTP headers and middleware, functionally similar, but gRPC's interceptor model makes it structurally harder to forget, since interceptors are configured once at the channel level rather than per-request.


Hybrid API Architecture

This is the pattern that resolves the REST-vs-gRPC debate in practice: don't choose one, segment by trust boundary. REST serves the edge, mobile apps and the Open Banking portal, where universal HTTP compatibility matters more than raw throughput. An Envoy gateway (or an equivalent edge proxy) translates that inbound REST/JSON traffic into gRPC/ProtoBuf calls against internal services. Everything behind the gateway, core banking, the risk engine, the ledger, speaks gRPC to everything else, including service-to-service calls that never touch the edge at all.


Comprehensive Comparison

REST vs gRPC: Comprehensive Comparison

Metric / FeatureREST (JSON / HTTP/1.1)gRPC (ProtoBuf / HTTP/2)Architectural Tradeoff / Verdict
Serialization SpeedText parsing, slower at scaleBinary encoding, 3–10x fastergRPC wins for high-frequency internal calls
Payload SizeVerbose, field names repeatedCompact, field numbers not namesgRPC typically 60–80% smaller on the wire
Schema EnforcementOptional (OpenAPI, often drifts)Mandatory (.proto contract)gRPC prevents schema drift by construction
Browser CompatibilityNative, universalRequires a grpc-web proxyREST wins for direct browser clients
Streaming CapabilitiesPolling or SSE workaroundsNative unary/server/client/bidi streaminggRPC wins for real-time feeds
Latency / ThroughputHigher — text parsing, per-call connectionsLower — multiplexed HTTP/2, binary framesgRPC wins under high concurrency
Learning CurveLow — ubiquitous toolingHigher — protoc toolchain, codegenREST wins for onboarding speed
Ideal Banking Use CaseOpen Banking, public APIs, web clientsCore banking microservices, settlement, streamingUse both, segmented by trust boundary

Read this table the same way as the pattern comparison in a companion post on this site: it's a decision aid tied to trust boundaries, not a universal ranking. Every row where gRPC wins is a row where the caller and callee are both under your control and can share a generated stub. Every row where REST wins is a row where the caller is external, browser-based, or unknown at design time.


Tools, Software & Middleware Ecosystem

API Gateways & Edge Proxies. Envoy is the de facto standard for REST-to-gRPC translation at the edge, with native HTTP/2 and gRPC-Web support. Kong and Apigee serve the same edge-gateway role with a stronger focus on API management, rate limiting, and developer portals for external Open Banking consumers. Where a browser client needs to call a gRPC service directly (rather than through a REST-translating gateway), a gRPC-Web proxy is required, since browsers cannot originate raw HTTP/2 trailers-based gRPC calls natively.

Middleware Integration Platforms. Intellect iTurmeric and Olive Fabric, already covered in this site's core banking integration post, provide banking-specific adapters that can sit in front of either protocol. Spring Cloud Gateway is the equivalent general-purpose choice in Java/Spring estates, commonly used to front internal gRPC services with a REST-compatible edge.

Testing & Debugging Tooling. Postman and Swagger UI remain the default tools for exploring and testing REST APIs interactively. grpcurl is the command-line equivalent for gRPC, a curl-like tool that can call a gRPC service using its reflection API without needing generated client code. BloomRPC (and its actively maintained successors) provides the GUI equivalent, a Postman-style interface for exploring and invoking .proto-defined services.


Production Code: Contract-First gRPC Integration

The Contract: account_service.proto

Every gRPC integration starts with the .proto file. Note the use of google.type.Decimal for monetary amounts, never a native float or double, which cannot represent currency values exactly and will silently accumulate rounding error across enough transactions.

syntax = "proto3";

package banking.account.v1;

import "google/protobuf/timestamp.proto";
import "google/type/decimal.proto";

option csharp_namespace = "Banking.Account.V1";
option java_package = "com.bank.account.v1";
option go_package = "github.com/bank/account/v1;accountv1";

// AccountService exposes balance and transaction operations for core banking accounts.
service AccountService {
  // GetBalance returns the current balance for a single account (unary RPC).
  rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse);

  // StreamTransactions streams transaction history for an account as it is written (server-streaming RPC).
  rpc StreamTransactions(StreamTransactionsRequest) returns (stream Transaction);
}

message GetBalanceRequest {
  string account_id = 1;
  string correlation_id = 2;
}

message GetBalanceResponse {
  string account_id = 1;
  google.type.Decimal available_balance = 2;   // structured decimal — never a float/double for money
  google.type.Decimal ledger_balance = 3;
  string currency_code = 4;                    // ISO 4217, e.g. "USD"
  google.protobuf.Timestamp as_of = 5;
}

message StreamTransactionsRequest {
  string account_id = 1;
  google.protobuf.Timestamp since = 2;
}

message Transaction {
  string transaction_id = 1;
  string account_id = 2;
  google.type.Decimal amount = 3;
  string currency_code = 4;
  string description = 5;
  google.protobuf.Timestamp posted_at = 6;
}

Every field carries an explicit number, never reused across schema versions, which is how ProtoBuf achieves forward and backward compatibility: new fields get new numbers, old consumers simply ignore fields they don't recognize, and nothing shifts underneath a deployed client.

Scenario 1: High-Performance gRPC Server Implementation

The discipline that matters most on the server side: never let an unhandled exception reach the client as an opaque UNKNOWN status. Validate input explicitly, and map every domain failure to the gRPC StatusCode that actually describes it, InvalidArgument for bad input, NotFound for a missing account, so the client can make a correct retry decision instead of guessing.

gRPC Server: Unmapped Exceptions vs Proper Status Codes

✓ Production-ready pattern
1// C#: gRPC server — validated input, exceptions mapped to gRPC status codes
2public override async Task<GetBalanceResponse> GetBalance(GetBalanceRequest request, ServerCallContext context)
3{
4    if (string.IsNullOrWhiteSpace(request.AccountId))
5        throw new RpcException(new Status(StatusCode.InvalidArgument, "account_id is required"));
6
7    var account = await _accountService.FindAsync(request.AccountId);
8    if (account is null)
9        throw new RpcException(new Status(StatusCode.NotFound, $"account {request.AccountId} not found"));
10
11    return new GetBalanceResponse
12    {
13        AccountId = account.Id,
14        AvailableBalance = account.AvailableBalance.ToProtoDecimal(), // structured decimal, no precision loss
15        CurrencyCode = account.CurrencyCode,
16        AsOf = Timestamp.FromDateTime(DateTime.UtcNow)
17    };
18}

Scenario 2: gRPC Client Integration

The discipline that matters most on the client side: every internal call carries mTLS, an OAuth2 bearer token, and a correlation ID, attached once via an interceptor rather than remembered per call site. A plaintext, unauthenticated, untraceable internal gRPC call is exactly as dangerous inside the bank's perimeter as an unauthenticated REST call would be at the edge.

gRPC Client: Plaintext No-Auth vs mTLS + OAuth2 + Correlation ID

✓ Production-ready pattern
1// C#: gRPC client — mTLS channel, OAuth2 token and correlation ID via ClientInterceptor
2var channel = GrpcChannel.ForAddress("https://core-banking:5000", new GrpcChannelOptions
3{
4    HttpHandler = new SocketsHttpHandler { SslOptions = new SslClientAuthenticationOptions
5    {
6        ClientCertificates = new X509CertificateCollection { _clientCertificate } // mTLS
7    }}
8});
9
10var invoker = channel.Intercept(new AuthAndCorrelationInterceptor(_tokenProvider));
11var client = new AccountService.AccountServiceClient(invoker);
12var response = await client.GetBalanceAsync(new GetBalanceRequest { AccountId = accountId });
13
14// AuthAndCorrelationInterceptor attaches Authorization + x-correlation-id to every call's Metadata
15public class AuthAndCorrelationInterceptor : Interceptor
16{
17    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
18        TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
19        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
20    {
21        var headers = context.Options.Headers ?? new Metadata();
22        headers.Add("authorization", $"Bearer {_tokenProvider.GetToken()}");
23        headers.Add("x-correlation-id", Activity.Current?.Id ?? Guid.NewGuid().ToString());
24        var newOptions = context.Options.WithHeaders(headers);
25        return continuation(request, new ClientInterceptorContext<TRequest, TResponse>(
26            context.Method, context.Host, newOptions));
27    }
28}

Decision Matrix: When to Use Which Protocol in Banking

REST remains king when:

  • The API is externally facing — Open Banking endpoints under PSD2 or the UK Open Banking Standard, where the regulation itself specifies REST/JSON.
  • Consumers are public third parties you don't control and can't ship generated client stubs to.
  • The client is a web browser calling directly, without a gRPC-Web proxy in front of the service.
  • Developer onboarding speed matters more than raw throughput — REST's universal tooling means a new integration partner can start testing against Postman and a Swagger doc within minutes.

gRPC is mandatory when:

  • Latency budgets are tight and calls are internal — service-to-service calls inside the core banking estate where every millisecond of serialization overhead compounds across a multi-hop request.
  • The workload is high-frequency transaction settlement, where thousands of calls per second make JSON's per-message overhead a measurable cost, not a rounding error.
  • The data is naturally a stream, not a single response — real-time audit feeds, market data, or account activity that a REST polling loop would only approximate.
  • The API sits entirely within core banking's internal microservices, where every caller and callee can share a generated .proto contract and there's no external consumer to accommodate.

Key Takeaways

REST and gRPC solve different problems, and the strongest banking API architectures use both, deliberately segmented by trust boundary rather than chosen once for the whole estate. REST's universal HTTP compatibility and human-readable payloads make it the correct, often regulator-mandated, choice at the public edge: Open Banking, third-party integrations, browser clients. gRPC's binary serialization, HTTP/2 multiplexing, contract-first schema enforcement, and native streaming make it the correct choice for the high-volume, low-latency internal microservice mesh behind that edge, core banking, risk, settlement, and fraud detection calling each other thousands of times a second.

The architectural mistake is treating this as a single either-or decision. In the banking integration systems I've worked on, the pattern that scales cleanly is REST at the edge, translated by a gateway like Envoy into gRPC across every internal service boundary, with the .proto contract acting as the single source of truth that keeps every internal team's schema honest. Choose the protocol the trust boundary and latency budget actually demand, not the one that happens to be familiar.

EM

Emmanuel Maneswa

Full Stack Software Engineer

LinkedInGitHub
← Back to all posts
EM
Emmanuel Maneswa

Full Stack Software Engineer specialising in core banking integrations, payment systems, and distributed financial architectures.

All ProjectsAll Posts

Navigation

  • Home
  • About
  • Skills
  • Projects
  • Experience
  • Blog
  • Contact

Connect

GitHubLinkedInEmail
Open to remote opportunities

© 2026 Emmanuel Maneswa. All rights reserved.

Built with Next.js & Tailwind CSS

Sitemap