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:
- Debugging: When something goes wrong, you need to identify whether it's a request problem, a server processing issue, or a response handling error.
- Performance: Knowing where time is spent helps you optimize your application.
- Security: Understanding what data flows where helps you protect sensitive information.
- Architecture: Good API design comes from understanding this fundamental pattern.
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:
- Client sends a Request — "I want something" (e.g., "Give me user #123's profile")
- Network delivers the Request — The request travels across the internet to the server
- Server processes the Request — Authentication, authorization, business logic, database queries
- Server sends a Response — "Here's what you asked for" (or "Here's why I can't give it to you")
- Network delivers the Response — The response travels back to the client
- 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:
- Request: You tell the waiter what you want, including any special instructions ("Burger, medium-rare, no onions, extra pickles")
- Processing: The kitchen receives your order, checks if they have ingredients, prepares the food according to your specifications
- Response: The waiter brings your food back (or tells you they're out of burgers)
- Client Processing: You eat and enjoy (or send it back if something's wrong)
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
}
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