Request/Response Cycle

Understanding how web communication flows from client to server and back

← Back to Index

What is the Request/Response Cycle?

The request/response cycle is the fundamental communication pattern of the web and one of the most important concepts for any web developer to understand deeply. Every single interaction on the web—clicking a link, submitting a form, loading an image, fetching API data—triggers this cycle.

Understanding this cycle is essential because:

The Basic Pattern

Every web interaction follows the same basic pattern, regardless of whether it's a simple page load or a complex API call:

The cycle in a nutshell:

  1. Client sends a Request — "I want something" (e.g., "Give me user #123's profile")
  2. Network delivers the Request — The request travels across the internet to the server
  3. Server processes the Request — Authentication, authorization, business logic, database queries
  4. Server sends a Response — "Here's what you asked for" (or "Here's why I can't give it to you")
  5. Network delivers the Response — The response travels back to the client
  6. Client processes the Response — Parses data, updates UI, handles errors

Real-World Analogy: Ordering at a Restaurant

Think of the request/response cycle like ordering food:

The HTTP request contains all the "instructions" (headers, parameters, body), and the response contains the "result" (status, data, or error).

// The Complete Cycle Visualized

┌─────────────┐                                    ┌─────────────┐
│   CLIENT    │                                    │   SERVER    │
│  (Browser)  │                                    │   (Java)    │
└──────┬──────┘                                    └──────┬──────┘
       │                                                  │
       │  1. HTTP REQUEST                                 │
       │  ─────────────────────────────────────────────>  │
       │  GET /api/users/123                              │
       │  Host: api.example.com                           │
       │  Authorization: Bearer token123                  │
       │                                                  │
       │                              2. PROCESS REQUEST  │
       │                              ┌─────────────────┐ │
       │                              │ - Authenticate  │ │
       │                              │ - Query DB      │ │
       │                              │ - Build response│ │
       │                              └─────────────────┘ │
       │                                                  │
       │  3. HTTP RESPONSE                                │
       │  <─────────────────────────────────────────────  │
       │  HTTP/1.1 200 OK                                 │
       │  Content-Type: application/json                  │
       │  {"id": 123, "name": "John"}                     │
       │                                                  │
       │  4. PROCESS RESPONSE                             │
       │  ┌─────────────────┐                             │
       │  │ - Parse JSON    │                             │
       │  │ - Update UI     │                             │
       │  │ - Store data    │                             │
       │  └─────────────────┘                             │
       ▼                                                  ▼

Anatomy of an HTTP Request

// Complete HTTP Request Structure

// REQUEST LINE (Method + Path + Protocol)
POST /api/users HTTP/1.1

// HEADERS (Metadata about the request)
Host: api.example.com
Content-Type: application/json
Content-Length: 56
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
User-Agent: Mozilla/5.0
Cache-Control: no-cache

// BLANK LINE (separates headers from body)

// BODY (Optional - data being sent)
{
    "name": "John Doe",
    "email": "john@example.com"
}

Request Components Explained

Component Example Purpose
Method POST What action to perform
Path /api/users Which resource to act on
Protocol HTTP/1.1 HTTP version
Host api.example.com Target server
Content-Type application/json Format of body data
Authorization Bearer token Authentication credentials
Body JSON data Payload being sent

Anatomy of an HTTP Response

// Complete HTTP Response Structure

// STATUS LINE (Protocol + Status Code + Status Text)
HTTP/1.1 201 Created

// HEADERS (Metadata about the response)
Content-Type: application/json
Content-Length: 89
Location: /api/users/124
Date: Thu, 23 Jan 2026 10:30:00 GMT
Cache-Control: no-cache
X-Request-Id: abc123

// BLANK LINE (separates headers from body)

// BODY (The actual response data)
{
    "id": 124,
    "name": "John Doe",
    "email": "john@example.com",
    "createdAt": "2026-01-23T10:30:00Z"
}

Common Response Headers

Header Example Purpose
Content-Type application/json Format of response body
Content-Length 256 Size of response body in bytes
Location /api/users/124 URL of created/redirected resource
Cache-Control max-age=3600 Caching instructions
Set-Cookie session=abc123 Set cookies on client
ETag "v5" Version identifier for caching

The Cycle in Java (Server Side)

Using HttpServletRequest and HttpServletResponse

@WebServlet("/api/users/*")
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        // ═══════════════════════════════════════════════
        // READING THE REQUEST
        // ═══════════════════════════════════════════════

        // Request Line Info
        String method = request.getMethod();           // "GET"
        String uri = request.getRequestURI();          // "/api/users/123"
        String queryString = request.getQueryString(); // "include=orders"

        // Headers
        String contentType = request.getContentType();
        String authHeader = request.getHeader("Authorization");
        String userAgent = request.getHeader("User-Agent");

        // Query Parameters
        String include = request.getParameter("include");

        // Path Parameters (manual extraction)
        String pathInfo = request.getPathInfo();       // "/123"
        Long userId = Long.parseLong(pathInfo.substring(1));

        // ═══════════════════════════════════════════════
        // PROCESSING
        // ═══════════════════════════════════════════════

        User user = userService.findById(userId);

        // ═══════════════════════════════════════════════
        // BUILDING THE RESPONSE
        // ═══════════════════════════════════════════════

        if (user == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 404
            response.setContentType("application/json");
            response.getWriter().write("{\"error\": \"User not found\"}");
            return;
        }

        // Set response headers
        response.setStatus(HttpServletResponse.SC_OK);  // 200
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "max-age=60");

        // Write response body
        String json = objectMapper.writeValueAsString(user);
        response.getWriter().write(json);
    }
}

Using Spring MVC (Much Cleaner)

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(
            @PathVariable Long id,                    // From URL path
            @RequestParam(required = false) String include, // From query string
            @RequestHeader("Authorization") String auth) {  // From headers

        // Spring automatically:
        // - Parses the request
        // - Extracts parameters
        // - Validates input

        User user = userService.findById(id);

        if (user == null) {
            return ResponseEntity.notFound().build();
        }

        // Spring automatically:
        // - Serializes to JSON
        // - Sets Content-Type
        // - Sets status code
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
            .body(user);
    }

    @PostMapping
    public ResponseEntity<User> createUser(
            @Valid @RequestBody CreateUserDTO dto) {  // From request body

        // Spring automatically:
        // - Deserializes JSON body
        // - Validates the DTO

        User created = userService.create(dto);

        URI location = URI.create("/api/users/" + created.getId());

        return ResponseEntity
            .created(location)  // 201 + Location header
            .body(created);
    }
}

The Cycle in JavaScript (Client Side)

Using Fetch API

// GET Request
async function getUser(userId) {
    // 1. BUILD THE REQUEST
    const response = await fetch(`/api/users/${userId}`, {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'Authorization': `Bearer ${token}`
        }
    });

    // 2. CHECK RESPONSE STATUS
    if (!response.ok) {
        if (response.status === 404) {
            throw new Error('User not found');
        }
        throw new Error(`HTTP error: ${response.status}`);
    }

    // 3. PARSE RESPONSE BODY
    const user = await response.json();

    // 4. USE THE DATA
    return user;
}

// POST Request
async function createUser(userData) {
    const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify(userData)  // Convert to JSON string
    });

    if (response.status === 201) {
        // Get the Location header for the new resource
        const location = response.headers.get('Location');
        console.log('Created at:', location);
    }

    return response.json();
}

Reading Response Details

async function inspectResponse() {
    const response = await fetch('/api/users');

    // Response metadata
    console.log('Status:', response.status);        // 200
    console.log('Status Text:', response.statusText); // "OK"
    console.log('OK?', response.ok);                  // true (2xx status)
    console.log('URL:', response.url);                // Final URL (after redirects)

    // Headers
    console.log('Content-Type:',
        response.headers.get('Content-Type'));       // "application/json"
    console.log('Cache-Control:',
        response.headers.get('Cache-Control'));      // "max-age=3600"

    // Body (can only be read once!)
    const data = await response.json();  // or .text(), .blob(), .arrayBuffer()
}

Request Processing Pipeline (Server)

// What happens on the server when a request arrives

           Request Arrives
                 │
                 ▼
    ┌────────────────────────┐
    │    1. FILTER CHAIN     │  Security, logging, CORS
    │    - Security Filter   │
    │    - Logging Filter    │
    │    - CORS Filter       │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │  2. DISPATCHER SERVLET │  Routes to correct handler
    │    (Front Controller)  │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   3. HANDLER MAPPING   │  Finds matching @RequestMapping
    │    Which controller?   │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   4. INTERCEPTORS      │  Pre-processing (auth checks, etc)
    │    (preHandle)         │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   5. CONTROLLER        │  Your @RestController method
    │    Business Logic      │
    │    - Validate input    │
    │    - Call services     │
    │    - Build response    │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   6. INTERCEPTORS      │  Post-processing
    │    (postHandle)        │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │  7. VIEW RESOLUTION    │  JSON serialization for REST
    │    (Message Converter) │
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   8. FILTER CHAIN      │  Post-processing filters
    │    (Response phase)    │
    └───────────┬────────────┘
                │
                ▼
          Response Sent

Example: Full Request Journey

// 1. SECURITY FILTER - Validates JWT token
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                      HttpServletResponse response,
                                      FilterChain chain) {

        String token = request.getHeader("Authorization");

        if (isValidToken(token)) {
            // Set authentication in security context
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        chain.doFilter(request, response);  // Continue chain
    }
}

// 2. LOGGING INTERCEPTOR - Logs request details
@Component
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler) {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        log.info("Request: {} {}", request.getMethod(), request.getRequestURI());
        return true;  // Continue processing
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                 HttpServletResponse response,
                                 Object handler, Exception ex) {
        long startTime = (long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;
        log.info("Response: {} {} - {}ms",
            response.getStatus(), request.getRequestURI(), duration);
    }
}

// 3. CONTROLLER - Handles the actual request
@RestController
public class UserController {
    @GetMapping("/api/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

Content Negotiation

Clients can request specific response formats using the Accept header.

// Client requests JSON
GET /api/users/123
Accept: application/json

// Client requests XML
GET /api/users/123
Accept: application/xml

// Client accepts either (preference order)
GET /api/users/123
Accept: application/json, application/xml;q=0.9

// Spring Controller supporting multiple formats
@GetMapping(value = "/{id}",
            produces = {MediaType.APPLICATION_JSON_VALUE,
                        MediaType.APPLICATION_XML_VALUE})
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
    // Spring automatically serializes based on Accept header
}
Quality Factor (q)

application/xml;q=0.9 means XML has 90% preference compared to formats without q (which default to 1.0). Server picks the highest quality format it supports.

Error Handling in the Cycle

// Proper error handling throughout the cycle

@RestController
public class UserController {

    @GetMapping("/api/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

// Global exception handler converts exceptions to proper responses
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.NOT_FOUND.value(),
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex) {

        List<String> errors = ex.getBindingResult()
            .getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .toList();

        ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            "Validation failed",
            errors
        );
        return ResponseEntity.badRequest().body(error);
    }
}

// Error response format
{
    "status": 404,
    "message": "User not found with id: 123",
    "timestamp": "2026-01-23T10:30:00"
}

Asynchronous Request Handling

// Synchronous (Traditional) - Thread blocked during processing
@GetMapping("/api/reports/{id}")
public Report getReport(@PathVariable Long id) {
    // Thread waits here while report generates
    return reportService.generateReport(id);  // May take 30 seconds!
}

// Asynchronous - Thread released while processing
@GetMapping("/api/reports/{id}")
public CompletableFuture<Report> getReportAsync(@PathVariable Long id) {
    // Thread immediately released
    return CompletableFuture.supplyAsync(() ->
        reportService.generateReport(id)
    );
}

// Using DeferredResult for more control
@GetMapping("/api/reports/{id}")
public DeferredResult<Report> getReportDeferred(@PathVariable Long id) {
    DeferredResult<Report> result = new DeferredResult<>(30000L); // 30s timeout

    // Process in background
    reportService.generateReportAsync(id, report -> {
        result.setResult(report);  // Complete when ready
    });

    result.onTimeout(() -> {
        result.setErrorResult(new TimeoutException("Report generation timed out"));
    });

    return result;  // Returns immediately, response sent later
}

Debugging the Cycle

// Useful techniques for debugging request/response

// 1. CURL - See raw request/response
curl -v -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "John"}'

// 2. Spring Boot logging - Add to application.properties
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE

// 3. Request logging filter
@Component
public class RequestLoggingFilter extends CommonsRequestLoggingFilter {
    public RequestLoggingFilter() {
        setIncludeQueryString(true);
        setIncludePayload(true);
        setIncludeHeaders(true);
        setMaxPayloadLength(10000);
    }
}

// 4. Browser DevTools - Network tab shows all details
// 5. Postman/Insomnia - GUI tools for API testing

Summary

  • Request: Method + URL + Headers + Body sent from client
  • Response: Status + Headers + Body sent from server
  • Cycle: Client sends request → Server processes → Server sends response
  • Headers: Metadata about the request/response (Content-Type, Authorization, etc.)
  • Body: The actual data being transferred (JSON, XML, HTML, etc.)
  • Status Codes: Tell client what happened (200 OK, 404 Not Found, etc.)
  • Pipeline: Filters → Dispatcher → Interceptors → Controller → Response