REST vs gRPC vs GraphQL – A Practical Comparison for Modern Systems

Modern distributed systems expose data and functionality through APIs. With microservices, mobile apps, web clients, partner integrations, and 3rd-party ecosystems, choosing the right communication style is a fundamental architectural decision.

API choice is not merely a syntactic preference—it directly influences performance, developer efficiency, cost, and even product flexibility. In modern backend systems, three dominant paradigms shape communication: REST, gRPC, and GraphQL.

Each has strengths, weaknesses, and ideal application contexts. The real skill is knowing which tool to use where.

1) REST – The Industry Standard for Client–Server Communication

REST (Representational State Transfer) is the most widely adopted API style on the internet. It relies on standard HTTP verbs like GET, POST, PUT, DELETE and returns data usually in JSON. REST is simple to use, human-readable, cache-friendly, and works reliably across browsers, mobile apps, and server-to-server communication.

In E-commerce, REST is ideal for public-facing APIs such as:

- browsing products,
- retrieving order history,
- fetching user profile data,
- adding items to cart.

When a user opens a product page, the mobile app sends:

GET /products/123

The server responds with all relevant details. REST excels in scenarios where data contracts are stable, where caching helps scalability, and where client flexibility doesn't require custom data querying. However, REST can be inefficient when clients need different slices of data from multiple endpoints, leading to the known problem of over-fetching and under-fetching.

REST in Spring Boot – Public Product API

In most Spring Boot systems, REST is your default for client-facing APIs. Think of a /api/products/{id} endpoint the mobile app calls. In a Spring Boot project, the main bit is spring-boot-starter-web (if you use Spring Initializr, you already have it):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
We'll expose a simple product with id, name and price:

public class ProductDto {
    private String id;
    private String name;
    private String currency;
    private double price;

    // getters/setters/constructors
}
Here is a typical REST endpoint in Spring Boot:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService; // a Spring @Service bean

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/{id}")
    public ResponseEntity getProduct(@PathVariable String id) {
        ProductDto product = productService.getProductById(id);
        return ResponseEntity.ok(product);
    }

    @PostMapping
    public ResponseEntity createProduct(@RequestBody ProductDto request) {
        ProductDto created = productService.createProduct(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}
The client (web or mobile) just hits GET /api/products/123 and receives JSON like:

{
  "id": "123",
  "name": "Running Shoe",
  "currency": "INR",
  "price": 3999.0
}
From the outside world's perspective, this is simple, debuggable, cacheable HTTP. Inside the service, though, we might already be using gRPC to talk to other services.

2) gRPC – High-Performance Communication for Microservices

As distributed backends evolved, internal service-to-service communication demanded something faster and more compact than REST. gRPC, built on HTTP/2 and using Protocol Buffers (Protobuf) for serialization, provides binary, highly efficient communication.

It supports bi-directional streaming, multiplexed connections, built-in type safety, and extremely low latency.

In a large E-commerce system, backend services constantly talk to each other: inventory updates, pricing validations, fraud checks, payment processing, search indexing. These interactions require fast, secure, predictable communication at massive scale. gRPC is perfect for this internal mesh.

Consider payment services communicating with inventory reservation and fulfillment orchestration:

service InventoryService {
  rpc ReserveStock (StockRequest) returns (StockResponse);
}
This binary message transfer is significantly faster than REST JSON serialization, making gRPC the backbone of many microservice architectures. It's not designed for public APIs because browsers don't support gRPC natively, but it excels in backend-to-backend flows where efficiency matters.

gRPC in Spring Boot – Service-to-Service Pricing Call

Now imagine the Product Service doesn't compute price itself. It calls a separate Pricing Service to figure out discounts, taxes, and currency conversions. That internal hop is a great use case for gRPC: fast, binary, strongly typed.

Below is a complete working example of Spring Boot gRPC communication using Java. This includes:

- gRPC Server (Pricing Service)
- gRPC Client (Product Service calling Pricing Service)
- .proto file
- Maven dependencies
- API request/response example


Module Role
pricing-service gRPC Server exposing getProductPrice()
product-service gRPC Client calling pricing-service to fetch price

Step 1: Create the pricing.proto (Both services)

Create src/main/proto/pricing.proto in both services.

syntax = "proto3";

package com.ecommerce.grpc;

option java_multiple_files = true;
option java_package = "com.cb.grpc";
option java_outer_classname = "PricingProto";

service PricingService {
    rpc GetProductPrice (PriceRequest) returns (PriceResponse);
}

message PriceRequest {
    int32 productId = 1;
}

message PriceResponse {
    double price = 1;
    string currency = 2;
}

Step 2: Add Maven Dependencies (Both services)

Pricing Service
<dependency>
   <groupId>org.springframework.grpc</groupId>
   <artifactId>spring-grpc-spring-boot-starter</artifactId>
</dependency>
Product Service
<dependency>
   <groupId>org.springframework.grpc</groupId>
   <artifactId>spring-grpc-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Step 3: Generate strongly typed request/response classes (Both services)

If you have created a Spring Boot project from Spring Initializer and added "Spring gRPC", you should already have protobuf-maven-plugin in pom.xml.

% mvn clean install

Once compiled, this plugin should create strongly typed request/response classes from pricing.proto in target/generated-sources/protobuf/java/.

Step 4: Implement gRPC Server (Pricing Service)

package com.cb.service;

import com.cb.grpc.PriceRequest;
import com.cb.grpc.PriceResponse;
import com.cb.grpc.PricingServiceGrpc;
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;
import org.springframework.stereotype.Service;

@GrpcService
@Service
public class PricingServiceImpl extends PricingServiceGrpc.PricingServiceImplBase {

    @Override
    public void getProductPrice(PriceRequest request,
                                StreamObserver responseObserver) {

        double price = switch (request.getProductId()) {
            case 1 -> 49999.00;
            case 2 -> 1299.00;
            default -> 999.00;
        };

        PriceResponse response = PriceResponse.newBuilder()
                .setPrice(price)
                .setCurrency("INR")
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

Step 5: Config (Pricing Service)

application.properties

spring.application.name=pricing-service
spring.grpc.server.port=9091

Step 6: Config (Product Service)

application.properties

spring.application.name=product-service
server.port=8080
spring.grpc.client.channels.pricing.address=static://localhost:9091
spring.grpc.client.channels.pricing.negotiation-type=plaintext
GrpcClientConfig.java

package com.cb.config;

import com.cb.grpc.PricingServiceGrpc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.client.GrpcChannelFactory;

@Configuration
public class GrpcClientConfig {

    @Bean
    public PricingServiceGrpc.PricingServiceBlockingStub pricingBlockingStub(GrpcChannelFactory factory) {
        return PricingServiceGrpc.newBlockingStub(factory.createChannel("pricing"));
    }
}

Step 7: Implement gRPC Server (Product Service)

package com.cb.service;

import com.cb.grpc.PriceRequest;
import com.cb.grpc.PriceResponse;
import com.cb.grpc.PricingServiceGrpc;
import org.springframework.stereotype.Service;

@Service
public class PricingClientService {

    private final PricingServiceGrpc.PricingServiceBlockingStub stub;

    public PricingClientService(PricingServiceGrpc.PricingServiceBlockingStub stub) {
        this.stub = stub;
    }

    public PriceResponse getPrice(int productId) {
        return stub.getProductPrice(
                PriceRequest.newBuilder()
                        .setProductId(productId)
                        .build()
        );
    }
}

Step 8: DTO and Controller

This has nothing to do with gRPC demonstration, in fact this controller is using REST (This is just to run end to end flow), but the service call between pricing-service and product-service is a gRPC one.

package com.cb.dto;

public record Product(int productId, double price, String currency
) {
}
package com.cb.controller;


import com.cb.dto.Product;
import com.cb.grpc.PriceResponse;
import com.cb.service.PricingClientService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/product")
public class ProductController {

    private final PricingClientService client;

    public ProductController(PricingClientService client) {
        this.client = client;
    }

    @GetMapping("/price/{id}")
    @ResponseBody
    public Product price(@PathVariable int id) {
        PriceResponse r = client.getPrice(id);
        return new Product(id, r.getPrice(), r.getCurrency());
    }
}

Step 9: Run & Test

Start pricing-service
Start product-service

REST:
GET http://localhost:8080/product/price/2

Response:
{
"productId": 2,
"price": 1299,
"currency": "INR"
}
From the outside, the client still sees a simple REST JSON API. Inside the platform, services (pricing and product) talk using gRPC for speed and reliability.

3)GraphQL – Flexible Queries for Modern Client Applications

GraphQL flips the traditional API model by allowing clients to request exactly the data they need. Instead of fixed endpoints, clients send queries specifying fields, and the server resolves them via a schema.

This makes GraphQL extremely powerful for complex user interfaces and data-rich applications where pages combine product details, recommendations, reviews, and personalization.

In E-commerce, a single product page today includes:

- product info,
- price and discount rules,
- stock status,
- seller info,
- recommendations (ML systems),
- aggregated reviews.

Using REST, this often becomes multiple calls:

GET /products/123
GET /products/123/reviews
GET /recommendations?product=123
GET /inventory/123

GraphQL allows a single query:

query {
  product(id: "123") {
    name
    price
    stock
    reviews {
      rating
      comment
    }
    recommendations {
      title
      price
    }
  }
}
This reduces network chattiness and provides great flexibility to clients like mobile apps, smart TVs, or partner integrations without requiring backend engineers to create dozens of tailored endpoints.

However, GraphQL can be complex to cache, harder to optimize for backend performance, and may expose too much flexibility if not secured properly. It thrives when UI teams iterate rapidly and need customizable responses.

Putting It All Together

The most scalable retailers do not choose one over the others. Instead, they combine them based on strengths:

- REST for stable, cacheable, public-facing APIs (product pages, order history).
- GraphQL for dynamic client queries, especially in mobile apps and multi-experience commerce.
- gRPC for high-performance microservice communication (payment, search indexing, inventory, machine learning).

In a mature system, a product detail page might use GraphQL to fetch composite UI data, while the backend may coordinate pricing rules and stock validation using gRPC, and the website may expose basic product browsing via REST for partner integrations.

Conclusion

API selection is not a technical debate—it's an architectural choice aligned with user experience, performance, scalability, and business evolution.

- REST offers stability.
- gRPC offers speed.
- GraphQL offers flexibility.

A modern E-commerce platform leverages all three with intent, not fashion.

Source Code: GitHub
Share this Article