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.propertiesspring.application.name=pricing-service
spring.grpc.server.port=9091
Step 6: Config (Product Service)
application.propertiesspring.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-serviceStart 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