RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
MediumSpring Boot#576 min readJul 11, 2026

API Gateway.

practice
learning
Practice modeTest yourself instead of reading straight through

Hook: An API Gateway is the front desk of a microservices system: interviewers love it because routing, security, and failure handling all meet in one place.

Question: What is an API Gateway in Spring Boot?

Answer: An API Gateway is a single entry point in front of your microservices. In Spring Boot, it receives a client request, checks rules like authentication and path matching, then forwards the request to the right backend service or combines results from several services. It hides internal service URLs from clients and lets you centralize cross-cutting concerns like logging, rate limiting, and security.

Interview-Ready Answer: I think of an API Gateway as the front door of a microservices system. In Spring Boot, it gives me one stable URL for clients, then handles routing, auth, logging, rate limiting, and sometimes response aggregation before traffic reaches the services. The big win is simpler clients and centralized control, but I keep the gateway lightweight and highly available because it can become a bottleneck if I put business logic there.

🧠 Memory Map
Memory map — visual summary of this topic

What it is

Detailed Explanation: An API Gateway is usually a reverse proxy, meaning a server that sits in front of other servers and forwards requests on their behalf. A route predicate is the condition that decides whether a request matches a route, and a filter is small code that runs before or after the request is forwarded. In Spring Boot, real gateways are often built with Spring Cloud Gateway, which is reactive, meaning it uses non-blocking I/O so threads are not tied up while waiting for network calls.

How it works under the hood

  1. The client sends one request, for example /api/orders/123, to the gateway instead of talking to many services directly.
  2. The gateway matches the request against route predicates such as path, host, header, or method.
  3. Cross-cutting filters run first: authentication, authorization, rate limiting, correlation ID creation, and request logging.
  4. The gateway can rewrite the path or headers so the downstream service sees a clean internal contract.
  5. The request is forwarded to the target microservice, often through a connection pool so sockets are reused instead of opened every time.
  6. If the downstream service is slow or fails, the gateway can apply a timeout, retry for safe requests, or return a fallback response.
  7. The response may be transformed again, for example to hide internal fields or normalize error bodies.
  8. The final response goes back to the client through the same single entry point.

When and why to use it

Use an API Gateway when many clients need a stable public API but your backend is split into several services. It is especially useful when mobile, web, and partner apps need different shapes of the same data, or when you want one place for auth, throttling, TLS termination, tracing, and request normalization. It is less useful if you only have one small service, because then the extra hop adds complexity without much value.

Gateway vs alternatives

ToolMain jobWhat it does not do
API GatewaySingle entry pointHeavy business logic
Load BalancerSpread trafficAuth or API shaping
BFFTailor one clientServe all clients equally
Service MeshService-to-service trafficClient-facing API design

Performance and edge cases

Route matching is conceptually O(R) in the number of routes, but in practice R is usually small enough that the real cost is the extra network hop and downstream latency. Space is also roughly O(R) for route definitions plus connection pool state. In real systems, I would usually start with short user-facing timeouts like 1-3 seconds, retries only for idempotent GET calls, and a modest connection pool per downstream service such as tens to low hundreds of connections per node depending on load. A classic gotcha is making the gateway stateful or putting business rules there; that turns a thin routing layer into a monolith. Another gotcha is assuming the gateway replaces service-level security: services should still verify authorization for their own data. If you are upgrading from Spring Boot 2 to 3, remember that servlet APIs moved from javax.* to jakarta.*.

Real-World Example: Imagine an e-commerce checkout platform with web, iOS, and partner clients. All of them call one gateway endpoint, and the gateway routes checkout traffic to inventory, pricing, and order services while adding a correlation ID for tracing and checking the user’s token once at the edge. A production incident happens when a bad rewrite rule removes the /api prefix from mobile requests. Users see endless loading or 404 errors, gateway logs show route-mismatch warnings, downstream services receive almost no traffic, and the checkout team spends an hour looking in the wrong place until they inspect the gateway configuration.

Spring Boot
package com.example.apigateway;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@SpringBootApplication
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    // A gateway should stay thin: auth, routing, and light aggregation.
    // Keeping its own worker pool small prevents one slow downstream from consuming every server thread.
    @Bean(destroyMethod = "shutdown")
    ExecutorService gatewayExecutor() {
        return Executors.newFixedThreadPool(8);
    }

    @Bean
    ApiKeyFilter apiKeyFilter() {
        return new ApiKeyFilter("secret-123");
    }
}

class ApiKeyFilter extends OncePerRequestFilter {
    private final String expectedKey;

    ApiKeyFilter(String expectedKey) {
        this.expectedKey = expectedKey;
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        // Only protect API traffic. Internal endpoints and non-API routes are left alone.
        return !request.getRequestURI().startsWith("/api/");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String supplied = request.getHeader("X-API-Key");

        // A gateway often rejects bad requests before any downstream service does work.
        if (!expectedKey.equals(supplied)) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType("text/plain");
            response.getWriter().write("Missing or invalid X-API-Key");
            return;
        }

        filterChain.doFilter(request, response);
    }
}

record ProductView(String productId, String name, int stock, double price) { }

record GatewayResponse(String message, ProductView product) { }

@RestController
@RequestMapping("/api/products")
class ProductGatewayController {
    private final InventoryService inventoryService;
    private final PricingService pricingService;
    private final ExecutorService gatewayExecutor;

    ProductGatewayController(InventoryService inventoryService,
                             PricingService pricingService,
                             ExecutorService gatewayExecutor) {
        this.inventoryService = inventoryService;
        this.pricingService = pricingService;
        this.gatewayExecutor = gatewayExecutor;
    }

    @GetMapping("/{id}")
    ResponseEntity<GatewayResponse> getProduct(@PathVariable String id) {
        try {
            // In a real gateway, these could be HTTP calls to different services.
            // Here we simulate downstream calls and show how the gateway composes them.
            CompletableFuture<Integer> stockFuture = CompletableFuture
                    .supplyAsync(() -> inventoryService.stockFor(id), gatewayExecutor)
                    .orTimeout(1, TimeUnit.SECONDS);

            CompletableFuture<Double> priceFuture = CompletableFuture
                    .supplyAsync(() -> pricingService.priceFor(id), gatewayExecutor)
                    .orTimeout(1, TimeUnit.SECONDS);

            int stock = stockFuture.join();
            double price = priceFuture.join();

            return ResponseEntity.ok(
                    new GatewayResponse(
                            "Aggregated by the gateway",
                            new ProductView(id, "Demo Product " + id, stock, price)
                    )
            );
        } catch (CompletionException ex) {
            Throwable cause = ex.getCause();

            // If a downstream service deliberately said 404, pass that status through.
            if (cause instanceof ResponseStatusException rse) {
                throw rse;
            }

            // If the call exceeded the gateway timeout, clients should see 504 Gateway Timeout.
            throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, "A downstream service timed out", cause);
        }
    }

    @ExceptionHandler(ResponseStatusException.class)
    ResponseEntity<String> handleResponseStatusException(ResponseStatusException ex) {
        String body = ex.getReason() == null ? ex.getStatusCode().toString() : ex.getReason();
        return ResponseEntity.status(ex.getStatusCode()).body(body);
    }
}

@Service
class InventoryService {
    int stockFor(String id) {
        // A real gateway must be ready for one service to reject a request while others still work.
        if ("404".equals(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
        }
        return 42;
    }
}

@Service
class PricingService {
    double priceFor(String id) {
        // Use this id to simulate a slow downstream dependency and prove the timeout path.
        if ("timeout".equals(id)) {
            slowDown(1500);
        }
        return 19.99;
    }

    private void slowDown(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Interrupted while pricing", e);
        }
    }
}

Follow-up & Tricky Questions:

  • How is an API Gateway different from a load balancer? A load balancer mainly spreads requests across instances, while an API Gateway also understands API paths, headers, auth, throttling, and sometimes request transformation.
  • Why not let clients call services directly? Direct calls force every client to know many service URLs and auth rules; the gateway gives you one stable contract and keeps internal topology hidden.
  • Where should authentication happen? The gateway is a good first check for token validation and coarse access rules, but each service should still enforce its own authorization for sensitive data.
  • When would you choose Spring Cloud Gateway? When you want a production-grade, reactive gateway with route predicates, filters, retries, and integration with Spring Boot and WebFlux.
  • What makes a gateway a bottleneck? Too much logic, no autoscaling, too few instances, or long downstream timeouts. The fix is to keep it stateless, scale horizontally, and fail fast.
  • Tricky: Does an API Gateway remove the need for service-to-service security? No. The gateway protects the edge, but services should still verify identity and authorization internally.
  • Tricky: Should I put all business rules in the gateway? No. Put only cross-cutting concerns and light composition there; business rules belong in the owning microservice.
  • Tricky: Does a gateway always make the system faster? No. It adds one hop, so raw latency can increase slightly. Its value is control, simplicity, and consistency, not speed by itself.

Common Mistakes:

  • Putting business logic in the gateway; the correction is to keep it thin and let services own domain rules.
  • Forgetting the gateway is still a single point of failure; the correction is to run multiple stateless instances behind a load balancer.
  • Using long timeouts and aggressive retries for every request; the correction is to fail fast and retry only safe, idempotent calls.
  • Thinking gateway auth is enough; the correction is to validate authorization again inside each service.

Memory Hook: Think of the gateway as airport security plus the information desk: it checks who you are, decides where you go, and hides the messy backstage area from travelers.

Cheat Sheet:

  • One public entry point for many services.
  • Central place for auth, routing, logging, and rate limiting.
  • Often built with Spring Cloud Gateway in Spring Boot.
  • Keep it stateless, fast, and horizontally scalable.
  • Use short timeouts and careful retries.
  • Do not move core business logic into the gateway.

Practice Tasks:

  • Build one endpoint that routes /api/orders/{id} to a downstream service.
  • Add a header check such as X-API-Key and return 401 when it is missing.
  • Simulate a slow downstream call and return 504 after a timeout.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

package com.example.apigateway; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.server.ResponseStatusException; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @SpringBootApplication public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } // A gateway should stay thin: auth, routing, and light aggregation. // Keeping its own worker pool small prevents one slow downstream from consuming every server thread. @Bean(destroyMethod = "shutdown") ExecutorService gatewayExecutor() { return Executors.newFixedThreadPool(8); } @Bean ApiKeyFilter apiKeyFilter() { return new ApiKeyFilter("secret-123"); } } class ApiKeyFilter extends OncePerRequestFilter { private final String expectedKey; ApiKeyFilter(String expectedKey) { this.expectedKey = expectedKey; } @Override protected boolean shouldNotFilter(HttpServletRequest request) { // Only protect API traffic. Internal endpoints and non-API routes are left alone. return !request.getRequestURI().startsWith("/api/"); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String supplied = request.getHeader("X-API-Key"); // A gateway often rejects bad requests before any downstream service does work. if (!expectedKey.equals(supplied)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("text/plain"); response.getWriter().write("Missing or invalid X-API-Key"); return; } filterChain.doFilter(request, response); } } record ProductView(String productId, String name, int stock, double price) { } record GatewayResponse(String message, ProductView product) { } @RestController @RequestMapping("/api/products") class ProductGatewayController { private final InventoryService inventoryService; private final PricingService pricingService; private final ExecutorService gatewayExecutor; ProductGatewayController(InventoryService inventoryService, PricingService pricingService, ExecutorService gatewayExecutor) { this.inventoryService = inventoryService; this.pricingService = pricingService; this.gatewayExecutor = gatewayExecutor; } @GetMapping("/{id}") ResponseEntity<GatewayResponse> getProduct(@PathVariable String id) { try { // In a real gateway, these could be HTTP calls to different services. // Here we simulate downstream calls and show how the gateway composes them. CompletableFuture<Integer> stockFuture = CompletableFuture .supplyAsync(() -> inventoryService.stockFor(id), gatewayExecutor) .orTimeout(1, TimeUnit.SECONDS); CompletableFuture<Double> priceFuture = CompletableFuture .supplyAsync(() -> pricingService.priceFor(id), gatewayExecutor) .orTimeout(1, TimeUnit.SECONDS); int stock = stockFuture.join(); double price = priceFuture.join(); return ResponseEntity.ok( new GatewayResponse( "Aggregated by the gateway", new ProductView(id, "Demo Product " + id, stock, price) ) ); } catch (CompletionException ex) { Throwable cause = ex.getCause(); // If a downstream service deliberately said 404, pass that status through. if (cause instanceof ResponseStatusException rse) { throw rse; } // If the call exceeded the gateway timeout, clients should see 504 Gateway Timeout. throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, "A downstream service timed out", cause); } } @ExceptionHandler(ResponseStatusException.class) ResponseEntity<String> handleResponseStatusException(ResponseStatusException ex) { String body = ex.getReason() == null ? ex.getStatusCode().toString() : ex.getReason(); return ResponseEntity.status(ex.getStatusCode()).body(body); } } @Service class InventoryService { int stockFor(String id) { // A real gateway must be ready for one service to reject a request while others still work. if ("404".equals(id)) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"); } return 42; } } @Service class PricingService { double priceFor(String id) { // Use this id to simulate a slow downstream dependency and prove the timeout path. if ("timeout".equals(id)) { slowDown(1500); } return 19.99; } private void slowDown(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Interrupted while pricing", e); } } }