HTTP Fundamentals - Complete Interview and Production Guide

Learn HTTP fundamentals including request and response structure, methods, status codes, headers, versions, connections, caching, content negotiation, HTTPS, Spring Boot examples, production scenarios, and 15 interview questions with answers.

Introduction

HTTP is the communication protocol used by browsers, mobile applications, API clients, gateways, load balancers, and backend services to exchange information over a network.

REST APIs depend heavily on HTTP.

To design production-ready APIs, developers must understand:

  • HTTP requests
  • HTTP responses
  • HTTP methods
  • Status codes
  • Headers
  • Message bodies
  • Connections
  • Caching
  • Content negotiation
  • Authentication headers
  • HTTP versions
  • HTTPS
  • Proxies and gateways
  • Timeouts and retries

A developer who understands only controller annotations but not HTTP behavior may create APIs that are difficult to cache, retry, secure, monitor, or scale.


What Is HTTP?

HTTP stands for Hypertext Transfer Protocol.

It is an application-layer protocol used for communication between clients and servers.

A client sends an HTTP request.

A server processes the request and returns an HTTP response.

Client

↓

HTTP Request

↓

Server

↓

HTTP Response

↓

Client

Example:

GET /api/v1/customers/101 HTTP/1.1
Host: api.codewithvenu.com
Accept: application/json

Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 68
{
  "id": 101,
  "name": "Venu",
  "status": "ACTIVE"
}

Where HTTP Fits in the Network Stack

HTTP operates at the application layer.

Application Layer
HTTP / HTTPS

↓

Transport Layer
TCP or QUIC

↓

Internet Layer
IP

↓

Network Access Layer
Ethernet / Wi-Fi

Traditional HTTP versions commonly use TCP.

HTTP/3 uses QUIC, which operates over UDP.


HTTP Communication Model

HTTP follows a request-response communication model.

Client

↓

Request Line

Request Headers

Request Body

↓

Server Processing

↓

Status Line

Response Headers

Response Body

↓

Client

The server normally does not send a response until it receives a request, except in technologies built on top of persistent communication patterns such as WebSockets or server-sent events.


Basic HTTP Request Structure

An HTTP request contains:

  1. Request line
  2. Headers
  3. Empty line
  4. Optional body

Example:

POST /api/v1/customers HTTP/1.1
Host: api.codewithvenu.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOi...
Content-Length: 86

{
  "name": "Venu",
  "email": "[email protected]",
  "city": "San Antonio"
}

Request Line

The request line contains:

HTTP Method + Request Target + HTTP Version

Example:

GET /api/v1/customers/101 HTTP/1.1

Meaning:

Part Value
Method GET
Request target /api/v1/customers/101
Protocol version HTTP/1.1

HTTP Request Headers

Headers provide metadata about the request.

Common request headers:

Header Purpose
Host Identifies the target host
Accept Specifies preferred response format
Content-Type Describes the request-body format
Authorization Carries authentication credentials
User-Agent Identifies the client
Cache-Control Defines caching behavior
If-None-Match Supports conditional requests using ETags
If-Modified-Since Supports date-based conditional requests
Accept-Encoding Indicates supported compression formats
Accept-Language Indicates preferred language
Idempotency-Key Helps prevent duplicate processing
X-Correlation-ID Helps trace a request across systems

Example:

Accept: application/json
Content-Type: application/json
Authorization: Bearer <access-token>
X-Correlation-ID: c72f6f93-9a53-4f85-8f51

HTTP Request Body

The body carries data sent to the server.

Request bodies are common with:

  • POST
  • PUT
  • PATCH

Example JSON body:

{
  "name": "Venu",
  "email": "[email protected]"
}

Other possible body formats:

  • XML
  • Form data
  • Multipart data
  • Plain text
  • Binary data
  • Protocol-specific payloads

Basic HTTP Response Structure

An HTTP response contains:

  1. Status line
  2. Response headers
  3. Empty line
  4. Optional body

Example:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/v1/customers/101
Cache-Control: no-store
Content-Length: 68

{
  "id": 101,
  "name": "Venu",
  "status": "ACTIVE"
}

Status Line

The status line contains:

HTTP Version + Status Code + Reason Phrase

Example:

HTTP/1.1 200 OK

Meaning:

Part Value
Protocol version HTTP/1.1
Status code 200
Reason phrase OK

HTTP Response Headers

Common response headers:

Header Purpose
Content-Type Response-body format
Content-Length Response size in bytes
Location URI of a newly created or redirected resource
Cache-Control Response caching rules
ETag Version identifier for a resource representation
Last-Modified Last update time
Retry-After Indicates when a client should retry
Set-Cookie Creates or updates a browser cookie
WWW-Authenticate Describes authentication requirements
Content-Encoding Indicates compression
Access-Control-Allow-Origin Controls browser cross-origin access
X-RateLimit-Remaining Shows remaining API quota

HTTP Methods

HTTP methods describe the intended action.

Method Typical Purpose Safe Idempotent
GET Retrieve a resource Yes Yes
HEAD Retrieve headers only Yes Yes
POST Create or trigger processing No No by default
PUT Replace or create a resource at a known URI No Yes
PATCH Partially update a resource No Depends on implementation
DELETE Remove a resource No Yes by HTTP semantics
OPTIONS Discover communication options Yes Yes
TRACE Diagnostic loopback Yes Yes

GET Method

GET retrieves information.

Example:

GET /api/v1/customers/101

Response:

{
  "id": 101,
  "name": "Venu"
}

Characteristics:

  • Safe
  • Idempotent
  • Commonly cacheable
  • Should not modify server state

Bad design:

GET /api/v1/customers/101/delete

A GET request should not delete data.


POST Method

POST submits data for processing or creates a resource under a collection.

Example:

POST /api/v1/customers
Content-Type: application/json
{
  "name": "Venu",
  "email": "[email protected]"
}

Response:

HTTP/1.1 201 Created
Location: /api/v1/customers/101
{
  "id": 101,
  "name": "Venu",
  "email": "[email protected]"
}

POST is not idempotent by default.

Submitting the same request twice may create two resources.

For payment or order APIs, an idempotency key is often required.


PUT Method

PUT usually replaces the complete representation of a resource.

Example:

PUT /api/v1/customers/101
Content-Type: application/json
{
  "name": "Venugopal",
  "email": "[email protected]",
  "status": "ACTIVE"
}

Sending the same PUT request multiple times should leave the resource in the same final state.

That makes PUT idempotent.


PATCH Method

PATCH performs a partial update.

Example:

PATCH /api/v1/customers/101
Content-Type: application/json
{
  "status": "INACTIVE"
}

Only the supplied field is updated.

PATCH is not automatically idempotent.

For example, this operation may not be idempotent:

{
  "incrementRewardPointsBy": 100
}

Sending it twice changes the result twice.

This operation can be idempotent:

{
  "status": "INACTIVE"
}

Sending it repeatedly produces the same final state.


DELETE Method

DELETE removes a resource.

Example:

DELETE /api/v1/customers/101

Possible response:

HTTP/1.1 204 No Content

The first request may delete the resource.

A repeated request may return:

HTTP/1.1 404 Not Found

DELETE is still considered idempotent because the final server state remains the same: the resource does not exist.

Idempotent does not mean every response must be identical.


HEAD Method

HEAD is similar to GET, but the response does not include a message body.

Example:

HEAD /api/v1/documents/report.pdf

Response:

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 409600
ETag: "document-v12"

Use cases:

  • Check whether a resource exists
  • Read metadata
  • Validate cache entries
  • Determine content size before downloading

OPTIONS Method

OPTIONS tells the client which communication options are supported.

Example:

OPTIONS /api/v1/customers

Response:

HTTP/1.1 204 No Content
Allow: GET, POST, OPTIONS

Browsers also use OPTIONS for CORS preflight requests.


Safe vs Idempotent Methods

Safe Method

A safe method should not change server state.

Safe methods:

GET
HEAD
OPTIONS
TRACE

Idempotent Method

Calling an idempotent method multiple times should produce the same final server state as calling it once.

Common idempotent methods:

GET
HEAD
PUT
DELETE
OPTIONS
TRACE

POST is not idempotent by default.

PATCH depends on the operation.


HTTP Status Code Categories

HTTP status codes are grouped into five categories.

Range Category Meaning
100–199 Informational Request is continuing
200–299 Success Request succeeded
300–399 Redirection Additional action is required
400–499 Client Error Request has a client-side problem
500–599 Server Error Server failed to process a valid request

Important 2xx Status Codes

200 OK

Use when the request succeeds and a response body is returned.

GET /api/v1/customers/101
HTTP/1.1 200 OK

201 Created

Use when a new resource is created.

POST /api/v1/customers
HTTP/1.1 201 Created
Location: /api/v1/customers/101

202 Accepted

Use when the request is accepted for asynchronous processing.

POST /api/v1/reports
HTTP/1.1 202 Accepted
Location: /api/v1/jobs/JOB-901

The work may not be complete yet.


204 No Content

Use when the operation succeeds but no response body is needed.

Common use:

DELETE /api/v1/customers/101
HTTP/1.1 204 No Content

A 204 response should not contain a response body.


Important 3xx Status Codes

301 Moved Permanently

The resource has permanently moved.

Clients and search engines may cache the new location.


302 Found

Temporary redirection.

Historically, some clients may change POST to GET during redirection.


303 See Other

Directs the client to retrieve another resource using GET.

Often useful after processing a POST request.


304 Not Modified

Used with conditional requests.

It tells the client to reuse its cached representation.

Example request:

GET /api/v1/products/101
If-None-Match: "product-v5"

Response:

HTTP/1.1 304 Not Modified
ETag: "product-v5"

No response body is returned.


307 Temporary Redirect

Temporary redirect that preserves the original HTTP method and body.


308 Permanent Redirect

Permanent redirect that preserves the original HTTP method and body.


Important 4xx Status Codes

400 Bad Request

Use when the request is malformed or validation fails.

Example:

{
  "code": "INVALID_REQUEST",
  "message": "Email address is required."
}

401 Unauthorized

Despite the name, 401 usually means the request is not successfully authenticated.

Possible causes:

  • Missing token
  • Expired token
  • Invalid token
  • Invalid credentials

Example:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer

403 Forbidden

The caller is authenticated but does not have permission.

Example:

User is logged in

but

User does not have ADMIN permission

404 Not Found

The requested resource does not exist or is intentionally hidden.

GET /api/v1/customers/9999
HTTP/1.1 404 Not Found

405 Method Not Allowed

The endpoint exists, but the method is unsupported.

Example:

DELETE /api/v1/reports

Response:

HTTP/1.1 405 Method Not Allowed
Allow: GET, POST

406 Not Acceptable

The server cannot produce a representation matching the client's Accept header.

Example:

Accept: application/xml

when the API supports only JSON.


409 Conflict

The request conflicts with the current resource state.

Examples:

  • Duplicate email
  • Version conflict
  • Resource already exists
  • Invalid state transition

412 Precondition Failed

A condition supplied by the client was not satisfied.

Often used with optimistic concurrency.

Example:

If-Match: "customer-v5"

If the current ETag is customer-v6, the update may return 412.


415 Unsupported Media Type

The server does not support the request-body format.

Example:

Content-Type: application/xml

when the endpoint accepts only JSON.


422 Unprocessable Content

The request is syntactically valid but fails semantic or business validation.

Example:

{
  "withdrawalAmount": -500
}

429 Too Many Requests

The client exceeded a rate limit.

HTTP/1.1 429 Too Many Requests
Retry-After: 60

Important 5xx Status Codes

500 Internal Server Error

An unexpected server-side failure occurred.

Do not expose stack traces or database details to clients.


502 Bad Gateway

A gateway or proxy received an invalid response from a downstream service.

Client

↓

API Gateway

↓

Payment Service returned invalid response

↓

502 Bad Gateway

503 Service Unavailable

The service is temporarily unavailable.

Possible causes:

  • Maintenance
  • Overload
  • Dependency outage
  • No healthy service instances

A Retry-After header may be provided.


504 Gateway Timeout

A gateway did not receive a response from the downstream service within the allowed time.

Client

↓

Gateway

↓

Order Service

↓

Payment Service takes too long

↓

504 Gateway Timeout

401 vs 403

Status Meaning
401 Authentication is missing or invalid
403 Authentication succeeded, but permission is insufficient

Example:

No valid access token
→ 401
Valid token without ADMIN role
→ 403

400 vs 422

Status Typical Use
400 Malformed request, invalid JSON, missing syntax-level information
422 Structurally valid request that violates semantic or business rules

Teams should choose a consistent convention and document it.


500 vs 502 vs 503 vs 504

Status Meaning
500 The application failed unexpectedly
502 A proxy received an invalid downstream response
503 The service is unavailable or overloaded
504 A proxy timed out waiting for a downstream response

Content-Type vs Accept

These headers solve different problems.

Content-Type

Describes the format of the current message body.

Request example:

Content-Type: application/json

Meaning:

The request body is JSON.

Accept

Describes the response format preferred by the client.

Accept: application/json

Meaning:

The client wants JSON.

Example:

POST /api/v1/customers
Content-Type: application/json
Accept: application/json

Content Negotiation

Content negotiation allows the client and server to agree on a representation.

Client request:

Accept: application/json

Server response:

Content-Type: application/json

Possible representation types:

  • application/json
  • application/xml
  • text/plain
  • text/html
  • application/pdf
  • application/octet-stream

URI Components

Example:

https://api.codewithvenu.com:443/api/v1/customers/101?include=accounts#summary

Components:

Component Value
Scheme https
Host api.codewithvenu.com
Port 443
Path /api/v1/customers/101
Query string include=accounts
Fragment summary

The URL fragment is normally processed by the client and is not sent to the server in an HTTP request.


Path Parameters

Path parameters identify a particular resource.

GET /api/v1/customers/101

Here:

101 = customerId

Spring Boot:

@GetMapping("/{customerId}")
public CustomerResponse getCustomer(
        @PathVariable Long customerId
) {
    return customerService.getCustomer(customerId);
}

Query Parameters

Query parameters modify retrieval behavior.

Example:

GET /api/v1/customers?status=ACTIVE&page=0&size=20

Spring Boot:

@GetMapping
public List<CustomerResponse> getCustomers(
        @RequestParam String status,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size
) {
    return customerService.getCustomers(
            status,
            page,
            size
    );
}

Common query uses:

  • Filtering
  • Sorting
  • Searching
  • Pagination
  • Optional response expansion

HTTP Is Stateless

HTTP is stateless by design.

This means each request should contain enough information for the server to understand and process it.

Request 1

Authorization: Bearer token

↓

Server processes request
Request 2

Authorization: Bearer token

↓

Server independently processes request

The protocol itself does not automatically remember prior requests.

Applications may still implement state using:

  • Cookies
  • Sessions
  • Databases
  • Distributed caches
  • Access tokens

Stateless HTTP does not mean the application cannot store state.


Cookies and Sessions

A server may create a session and send a cookie.

Response:

Set-Cookie: SESSION_ID=abc123; HttpOnly; Secure; SameSite=Lax

Later request:

Cookie: SESSION_ID=abc123

The cookie allows the server to identify the session.

Important cookie attributes:

Attribute Purpose
Secure Send cookie only over HTTPS
HttpOnly Prevent JavaScript access
SameSite Reduce cross-site request risks
Max-Age Cookie lifetime
Path Restrict cookie path
Domain Restrict domain scope

HTTP Connections

HTTP/1.0

Connections were commonly closed after each response unless keep-alive was explicitly used.

Request

↓

Response

↓

Connection Closed

HTTP/1.1

Persistent connections are enabled by default.

Connection Open

↓

Request 1 / Response 1

↓

Request 2 / Response 2

↓

Request 3 / Response 3

This reduces connection setup overhead.

HTTP/1.1 also introduced or standardized features such as:

  • Host header
  • Persistent connections
  • Chunked transfer encoding
  • Better caching support

HTTP/2

HTTP/2 supports multiplexing.

Multiple request-response streams can share a single connection.

One TCP Connection

├── Stream 1: Customer Request
├── Stream 2: Account Request
├── Stream 3: Image Request
└── Stream 4: Notification Request

Benefits:

  • Multiplexing
  • Header compression
  • Binary framing
  • Better network utilization
  • Server push support, though it is not widely relied upon today

HTTP/2 still uses TCP.

Packet loss can affect multiple streams sharing the same TCP connection.


HTTP/3

HTTP/3 uses QUIC over UDP.

Benefits include:

  • Faster connection establishment
  • Improved behavior during packet loss
  • Stream-level independence
  • Better connection migration when networks change
HTTP/3

↓

QUIC

↓

UDP

Example use case:

A mobile device changes from Wi-Fi to cellular connectivity while maintaining communication more effectively.


HTTP/1.1 vs HTTP/2 vs HTTP/3

Feature HTTP/1.1 HTTP/2 HTTP/3
Transport TCP TCP QUIC over UDP
Multiplexing Limited Yes Yes
Header compression No standard binary compression HPACK QPACK
Binary framing No Yes Yes
Connection migration No No Better support
Packet-loss isolation Limited TCP-level blocking Stream-level improvement
Common API support Yes Yes Increasing

HTTP Keep-Alive

Creating a connection can require:

  • DNS resolution
  • TCP handshake
  • TLS handshake

Reusing an existing connection reduces this overhead.

Without Reuse

Request 1 → New Connection

Request 2 → New Connection

Request 3 → New Connection
With Reuse

One Connection

├── Request 1
├── Request 2
└── Request 3

Connection pooling is critical for high-volume service-to-service communication.


HTTP Timeouts

Every production HTTP client should use explicit timeouts.

Common timeout types:

Timeout Meaning
Connection timeout Maximum time to establish a connection
Read timeout Maximum wait for response data
Write timeout Maximum wait while sending data
Response timeout Maximum total wait for a response
Connection acquisition timeout Maximum wait for a pooled connection

Without timeouts, threads and connections may wait indefinitely.


Retry Behavior

Retries can improve reliability, but they can also create duplicate operations or overload failing systems.

Safe retry candidates:

  • GET
  • HEAD
  • PUT when correctly implemented
  • DELETE when correctly implemented
  • POST with a valid idempotency key

Avoid automatic retry for non-idempotent operations unless duplicate protection exists.

Example:

Client sends payment

↓

Server processes payment

↓

Response is lost

↓

Client retries

↓

Payment may execute twice

Solution:

Idempotency-Key: PAYMENT-REQ-1001

HTTPS

HTTPS is HTTP over a secure TLS connection.

HTTP

+

TLS

=

HTTPS

HTTPS provides:

  • Encryption
  • Server authentication
  • Data integrity
  • Protection against network eavesdropping
  • Reduced risk of message tampering

Simplified TLS Flow

Client

↓

TLS Handshake

↓

Server Certificate Validation

↓

Session Key Established

↓

Encrypted HTTP Communication

HTTPS does not automatically provide application authorization.

The API still needs controls such as:

  • OAuth 2.0
  • JWT validation
  • API keys
  • mTLS
  • Role-based access control
  • Attribute-based access control

HTTP Caching

Caching reduces:

  • Latency
  • Server load
  • Network usage
  • Database load

Possible cache locations:

Browser Cache

↓

CDN

↓

Reverse Proxy

↓

API Gateway Cache

↓

Application Cache

Cache-Control Header

Example:

Cache-Control: public, max-age=300

Meaning:

  • Public caches may store the response
  • The response is fresh for 300 seconds

Common directives:

Directive Meaning
public Shared caches may store the response
private Only a private client cache should store it
no-cache Cache may store it but must revalidate before use
no-store Do not store the response
max-age Freshness lifetime in seconds
s-maxage Freshness lifetime for shared caches
must-revalidate Stale response must be revalidated

no-cache does not always mean "do not store."

It usually means the stored response must be revalidated before reuse.

no-store means it should not be stored.


ETag and Conditional Requests

An ETag identifies a specific resource representation.

Initial response:

HTTP/1.1 200 OK
ETag: "customer-v7"

Later request:

GET /api/v1/customers/101
If-None-Match: "customer-v7"

When unchanged:

HTTP/1.1 304 Not Modified
ETag: "customer-v7"

This saves bandwidth because the response body is not returned.


Optimistic Concurrency with ETag

ETags can also prevent lost updates.

Client reads:

GET /api/v1/customers/101

Response:

ETag: "customer-v7"

Client updates:

PUT /api/v1/customers/101
If-Match: "customer-v7"

If another request already changed the resource to version 8, the server returns:

HTTP/1.1 412 Precondition Failed

This prevents the client from overwriting newer data unknowingly.


Compression

Compression reduces response size.

Client:

Accept-Encoding: gzip, br

Server:

Content-Encoding: gzip

Compression is useful for:

  • JSON
  • HTML
  • CSS
  • JavaScript
  • Text

Compression may be less useful for already compressed formats such as:

  • JPEG
  • PNG
  • ZIP
  • MP4

Compression also consumes CPU, so it should be configured thoughtfully.


Transfer-Encoding and Content-Length

Content-Length

Specifies the message-body size.

Content-Length: 256

Chunked Transfer Encoding

Allows a server to send a response in chunks when the full size is not known in advance.

Transfer-Encoding: chunked

HTTP/2 and HTTP/3 use their own framing mechanisms rather than HTTP/1.1 chunked transfer encoding.


Proxies, Load Balancers, and Gateways

A production HTTP request may pass through several intermediaries.

Client

↓

CDN

↓

Load Balancer

↓

API Gateway

↓

Reverse Proxy

↓

Application Service

These components may perform:

  • TLS termination
  • Routing
  • Authentication
  • Rate limiting
  • Caching
  • Compression
  • Header transformation
  • Logging
  • Request-size enforcement
  • Timeout management

Forwarded Headers

When requests pass through proxies, the application may need original client information.

Common headers include:

Forwarded: for=203.0.113.10;proto=https;host=api.example.com

Legacy-style headers:

X-Forwarded-For: 203.0.113.10
X-Forwarded-Proto: https
X-Forwarded-Host: api.example.com

Applications should trust forwarded headers only from approved proxies.

Blindly trusting client-supplied forwarded headers can create security and logging problems.


CORS and HTTP

CORS stands for Cross-Origin Resource Sharing.

Browsers enforce same-origin restrictions.

Example origins:

https://app.codewithvenu.com

and:

https://api.codewithvenu.com

These are different origins because the hosts differ.

The browser may send a preflight request:

OPTIONS /api/v1/customers
Origin: https://app.codewithvenu.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type

Server response:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.codewithvenu.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type

CORS is primarily a browser-enforced policy.

It is not a replacement for authentication or authorization.


Complete Spring Boot HTTP Example

We will create a basic customer API supporting:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • Correct status codes
  • Request validation
  • Response headers
  • Global error handling

Project Structure

src/main/java/com/codewithvenu/httpfundamentals

├── controller
│   └── CustomerController.java
├── dto
│   ├── CreateCustomerRequest.java
│   ├── UpdateCustomerRequest.java
│   ├── CustomerResponse.java
│   └── ApiErrorResponse.java
├── exception
│   ├── CustomerNotFoundException.java
│   └── GlobalExceptionHandler.java
├── service
│   └── CustomerService.java
└── HttpFundamentalsApplication.java

Customer Response DTO

package com.codewithvenu.httpfundamentals.dto;

public record CustomerResponse(
        Long id,
        String name,
        String email,
        String status
) {
}

Create Customer Request

package com.codewithvenu.httpfundamentals.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record CreateCustomerRequest(

        @NotBlank(message = "Name is required")
        String name,

        @NotBlank(message = "Email is required")
        @Email(message = "Email must be valid")
        String email
) {
}

Update Customer Request

package com.codewithvenu.httpfundamentals.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record UpdateCustomerRequest(

        @NotBlank(message = "Name is required")
        String name,

        @NotBlank(message = "Email is required")
        @Email(message = "Email must be valid")
        String email,

        @NotBlank(message = "Status is required")
        String status
) {
}

Customer Not Found Exception

package com.codewithvenu.httpfundamentals.exception;

public class CustomerNotFoundException
        extends RuntimeException {

    public CustomerNotFoundException(Long customerId) {
        super(
                "Customer "
                        + customerId
                        + " was not found."
        );
    }
}

Customer Service

package com.codewithvenu.httpfundamentals.service;

import com.codewithvenu.httpfundamentals.dto.CreateCustomerRequest;
import com.codewithvenu.httpfundamentals.dto.CustomerResponse;
import com.codewithvenu.httpfundamentals.dto.UpdateCustomerRequest;
import com.codewithvenu.httpfundamentals.exception.CustomerNotFoundException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;

@Service
public class CustomerService {

    private final Map<Long, CustomerResponse> customers =
            new ConcurrentHashMap<>();

    private final AtomicLong customerIdSequence =
            new AtomicLong(100);

    public CustomerService() {

        CustomerResponse initialCustomer =
                new CustomerResponse(
                        101L,
                        "Venu",
                        "[email protected]",
                        "ACTIVE"
                );

        customers.put(
                initialCustomer.id(),
                initialCustomer
        );

        customerIdSequence.set(101);
    }

    public CustomerResponse getCustomer(
            Long customerId
    ) {

        CustomerResponse customer =
                customers.get(customerId);

        if (customer == null) {
            throw new CustomerNotFoundException(
                    customerId
            );
        }

        return customer;
    }

    public CustomerResponse createCustomer(
            CreateCustomerRequest request
    ) {

        Long customerId =
                customerIdSequence.incrementAndGet();

        CustomerResponse customer =
                new CustomerResponse(
                        customerId,
                        request.name(),
                        request.email(),
                        "ACTIVE"
                );

        customers.put(customerId, customer);

        return customer;
    }

    public CustomerResponse replaceCustomer(
            Long customerId,
            UpdateCustomerRequest request
    ) {

        getCustomer(customerId);

        CustomerResponse updatedCustomer =
                new CustomerResponse(
                        customerId,
                        request.name(),
                        request.email(),
                        request.status()
                );

        customers.put(
                customerId,
                updatedCustomer
        );

        return updatedCustomer;
    }

    public CustomerResponse updateStatus(
            Long customerId,
            String status
    ) {

        CustomerResponse currentCustomer =
                getCustomer(customerId);

        CustomerResponse updatedCustomer =
                new CustomerResponse(
                        currentCustomer.id(),
                        currentCustomer.name(),
                        currentCustomer.email(),
                        status
                );

        customers.put(
                customerId,
                updatedCustomer
        );

        return updatedCustomer;
    }

    public void deleteCustomer(
            Long customerId
    ) {

        CustomerResponse removedCustomer =
                customers.remove(customerId);

        if (removedCustomer == null) {
            throw new CustomerNotFoundException(
                    customerId
            );
        }
    }
}

Customer Controller

package com.codewithvenu.httpfundamentals.controller;

import com.codewithvenu.httpfundamentals.dto.CreateCustomerRequest;
import com.codewithvenu.httpfundamentals.dto.CustomerResponse;
import com.codewithvenu.httpfundamentals.dto.UpdateCustomerRequest;
import com.codewithvenu.httpfundamentals.service.CustomerService;
import jakarta.validation.Valid;
import java.net.URI;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/customers")
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(
            CustomerService customerService
    ) {
        this.customerService = customerService;
    }

    @GetMapping("/{customerId}")
    public ResponseEntity<CustomerResponse>
    getCustomer(
            @PathVariable Long customerId
    ) {

        CustomerResponse response =
                customerService.getCustomer(
                        customerId
                );

        return ResponseEntity
                .ok()
                .cacheControl(
                        CacheControl
                                .noCache()
                )
                .body(response);
    }

    @PostMapping
    public ResponseEntity<CustomerResponse>
    createCustomer(
            @Valid
            @RequestBody
            CreateCustomerRequest request
    ) {

        CustomerResponse response =
                customerService.createCustomer(
                        request
                );

        URI resourceLocation =
                URI.create(
                        "/api/v1/customers/"
                                + response.id()
                );

        return ResponseEntity
                .created(resourceLocation)
                .body(response);
    }

    @PutMapping("/{customerId}")
    public ResponseEntity<CustomerResponse>
    replaceCustomer(
            @PathVariable Long customerId,
            @Valid
            @RequestBody
            UpdateCustomerRequest request
    ) {

        CustomerResponse response =
                customerService.replaceCustomer(
                        customerId,
                        request
                );

        return ResponseEntity.ok(response);
    }

    @PatchMapping("/{customerId}/status")
    public ResponseEntity<CustomerResponse>
    updateCustomerStatus(
            @PathVariable Long customerId,
            @RequestParam String status
    ) {

        CustomerResponse response =
                customerService.updateStatus(
                        customerId,
                        status
                );

        return ResponseEntity.ok(response);
    }

    @DeleteMapping("/{customerId}")
    public ResponseEntity<Void>
    deleteCustomer(
            @PathVariable Long customerId
    ) {

        customerService.deleteCustomer(
                customerId
        );

        return ResponseEntity
                .noContent()
                .build();
    }
}

Error Response DTO

package com.codewithvenu.httpfundamentals.dto;

import java.time.Instant;
import java.util.List;

public record ApiErrorResponse(
        Instant timestamp,
        int status,
        String code,
        String message,
        String path,
        List<String> validationErrors
) {
}

Global Exception Handler

package com.codewithvenu.httpfundamentals.exception;

import com.codewithvenu.httpfundamentals.dto.ApiErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomerNotFoundException.class)
    public ResponseEntity<ApiErrorResponse>
    handleCustomerNotFound(
            CustomerNotFoundException exception,
            HttpServletRequest request
    ) {

        ApiErrorResponse errorResponse =
                new ApiErrorResponse(
                        Instant.now(),
                        HttpStatus.NOT_FOUND.value(),
                        "CUSTOMER_NOT_FOUND",
                        exception.getMessage(),
                        request.getRequestURI(),
                        List.of()
                );

        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(errorResponse);
    }

    @ExceptionHandler(
            MethodArgumentNotValidException.class
    )
    public ResponseEntity<ApiErrorResponse>
    handleValidationFailure(
            MethodArgumentNotValidException exception,
            HttpServletRequest request
    ) {

        List<String> validationErrors =
                exception
                        .getBindingResult()
                        .getFieldErrors()
                        .stream()
                        .map(
                                fieldError ->
                                        fieldError.getField()
                                                + ": "
                                                + fieldError
                                                        .getDefaultMessage()
                        )
                        .toList();

        ApiErrorResponse errorResponse =
                new ApiErrorResponse(
                        Instant.now(),
                        HttpStatus.BAD_REQUEST.value(),
                        "VALIDATION_FAILED",
                        "Request validation failed.",
                        request.getRequestURI(),
                        validationErrors
                );

        return ResponseEntity
                .badRequest()
                .body(errorResponse);
    }
}

Step-by-Step Execution: GET Request

Client request:

GET /api/v1/customers/101 HTTP/1.1
Host: localhost:8080
Accept: application/json

Step 1: HTTP Server Receives the Request

The embedded application server accepts the network connection and parses the HTTP request.


Step 2: DispatcherServlet Receives It

Spring MVC routes the request through:

DispatcherServlet

↓

Handler Mapping

↓

CustomerController

Step 3: Endpoint Is Selected

Spring matches:

@GetMapping("/{customerId}")

with:

customerId = 101

Step 4: Service Retrieves the Customer

customerService.getCustomer(customerId);

Step 5: Controller Builds the HTTP Response

return ResponseEntity
        .ok()
        .cacheControl(CacheControl.noCache())
        .body(response);

The response includes:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache

Step 6: Jackson Serializes the DTO

Java object:

new CustomerResponse(
    101L,
    "Venu",
    "[email protected]",
    "ACTIVE"
)

becomes:

{
  "id": 101,
  "name": "Venu",
  "email": "[email protected]",
  "status": "ACTIVE"
}

Step-by-Step Execution: POST Request

Request:

POST /api/v1/customers HTTP/1.1
Content-Type: application/json
Accept: application/json
{
  "name": "John",
  "email": "[email protected]"
}

Step 1: Content-Type Is Evaluated

Spring sees:

Content-Type: application/json

and selects a JSON message converter.


Step 2: JSON Is Deserialized

The request body becomes:

CreateCustomerRequest

Step 3: Validation Runs

Annotations such as:

@NotBlank
@Email

are evaluated because the controller uses:

@Valid

Step 4: Service Creates the Resource

The service generates a new ID and stores the customer.


Step 5: Controller Returns 201

ResponseEntity.created(resourceLocation)

produces:

HTTP/1.1 201 Created
Location: /api/v1/customers/102

Request and Response Examples

GET Customer

Request:

GET /api/v1/customers/101
Accept: application/json

Response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
{
  "id": 101,
  "name": "Venu",
  "email": "[email protected]",
  "status": "ACTIVE"
}

Create Customer

Request:

POST /api/v1/customers
Content-Type: application/json
Accept: application/json
{
  "name": "John",
  "email": "[email protected]"
}

Response:

HTTP/1.1 201 Created
Location: /api/v1/customers/102
Content-Type: application/json
{
  "id": 102,
  "name": "John",
  "email": "[email protected]",
  "status": "ACTIVE"
}

Replace Customer

Request:

PUT /api/v1/customers/102
Content-Type: application/json
{
  "name": "John Smith",
  "email": "[email protected]",
  "status": "ACTIVE"
}

Response:

HTTP/1.1 200 OK

Partial Update

Request:

PATCH /api/v1/customers/102/status?status=INACTIVE

Response:

HTTP/1.1 200 OK
{
  "id": 102,
  "name": "John Smith",
  "email": "[email protected]",
  "status": "INACTIVE"
}

Delete Customer

Request:

DELETE /api/v1/customers/102

Response:

HTTP/1.1 204 No Content

Validation Failure

Request:

POST /api/v1/customers
Content-Type: application/json
{
  "name": "",
  "email": "invalid-email"
}

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
  "timestamp": "2026-07-19T12:00:00Z",
  "status": 400,
  "code": "VALIDATION_FAILED",
  "message": "Request validation failed.",
  "path": "/api/v1/customers",
  "validationErrors": [
    "name: Name is required",
    "email: Email must be valid"
  ]
}

HTTP Logging

A production API should log meaningful HTTP metadata.

Example:

timestamp=2026-07-19T12:30:00Z
level=INFO
service=customer-api
traceId=21ab9d48
method=GET
path=/api/v1/customers/101
status=200
durationMs=73
clientId=mobile-app

Do not log:

  • Passwords
  • Access tokens
  • Refresh tokens
  • Sensitive request bodies
  • Full payment-card data
  • Private keys
  • Session secrets

Important HTTP Metrics

Monitor:

  • Request count
  • Requests per second
  • Status-code distribution
  • P50 latency
  • P95 latency
  • P99 latency
  • Timeout count
  • Active connections
  • Connection pool usage
  • Request size
  • Response size
  • Retry count
  • Rate-limited requests
  • Downstream HTTP errors

Production Scenario 1: API Returns 504

Flow:

Client

↓

API Gateway

↓

Order Service

↓

Payment Service

The gateway waits five seconds, but the order service does not respond.

Result:

HTTP/1.1 504 Gateway Timeout

Investigation:

  1. Check gateway timeout configuration.
  2. Inspect distributed traces.
  3. Check order-service latency.
  4. Check payment-service latency.
  5. Review connection pools.
  6. Review database queries.
  7. Check thread-pool saturation.
  8. Confirm whether the client retried.
  9. Verify that retries did not multiply traffic.

Production Scenario 2: API Returns 502

The gateway successfully connects to a backend but receives:

  • Invalid protocol response
  • Prematurely closed connection
  • TLS handshake failure
  • Malformed response
  • Unhealthy upstream behavior

Result:

HTTP/1.1 502 Bad Gateway

Check:

  • Upstream health
  • TLS certificates
  • Proxy configuration
  • Backend startup logs
  • Protocol compatibility
  • Response-header size
  • Connection resets

Production Scenario 3: API Returns 415

Request:

POST /api/v1/customers
Content-Type: application/xml

The endpoint accepts only:

application/json

Response:

HTTP/1.1 415 Unsupported Media Type

Fix:

Content-Type: application/json

Production Scenario 4: API Returns 406

Client sends:

Accept: application/xml

The server can produce only JSON.

Response:

HTTP/1.1 406 Not Acceptable

Fix:

Accept: application/json

Production Scenario 5: Duplicate Payment

Client Sends POST

↓

Payment Processed

↓

Response Lost

↓

Client Retries

↓

Second Payment Created

Cause:

POST is not idempotent by default.

Solution:

Idempotency-Key: PAYMENT-10001

The server should store the key and previously generated result.


Production Scenario 6: Stale Update Overwrites New Data

Two users read version 5 of a profile.

User A updates it to version 6.

User B later submits an update based on version 5.

Without concurrency protection, User B may overwrite User A's changes.

Solution:

If-Match: "profile-v5"

Server response:

HTTP/1.1 412 Precondition Failed

Common HTTP Mistakes

1. Using GET to Modify Data

Bad:

GET /accounts/101/close

Better:

POST /accounts/101/closure-requests

or:

PATCH /accounts/101

depending on business semantics.


2. Returning 200 for Every Result

Bad:

HTTP/1.1 200 OK
{
  "success": false,
  "error": "Customer not found"
}

Better:

HTTP/1.1 404 Not Found

3. Returning 500 for Validation Errors

Validation failures are normally client errors.

Use:

400 Bad Request

or a documented 422 convention.


4. Confusing 401 and 403

Use:

401 → Missing or invalid authentication

403 → Authenticated but not authorized

5. Ignoring Content-Type

The server should validate request formats.

A JSON endpoint should not silently accept unsupported data.


6. Missing Timeouts

An HTTP client without timeouts can exhaust:

  • Threads
  • Connections
  • Memory
  • Request capacity

7. Retrying Every Failure

Do not automatically retry:

  • Validation errors
  • Authentication failures
  • Authorization failures
  • Non-idempotent operations without protection

8. Caching Sensitive Data Publicly

Bad:

Cache-Control: public

for personal account data.

Better:

Cache-Control: no-store

or a carefully designed private-cache policy.


9. Logging Authorization Headers

Never log bearer tokens.

Mask or remove sensitive headers.


10. Returning Bodies with 204

A 204 No Content response should not include a response body.


11. Exposing Internal Failure Details

Bad response:

{
  "message": "NullPointerException at CustomerService.java:84"
}

Better:

{
  "code": "INTERNAL_ERROR",
  "message": "The request could not be processed."
}

12. Trusting Client-Supplied Forwarded Headers

Only trust forwarding metadata from approved proxies and gateways.


HTTP Best Practices

  • Use HTTP methods according to their semantics.
  • Return accurate status codes.
  • Validate Content-Type and Accept.
  • Use HTTPS for all production APIs.
  • Configure explicit client and gateway timeouts.
  • Retry only safe or idempotent operations.
  • Use idempotency keys for critical POST requests.
  • Add request correlation and trace identifiers.
  • Avoid logging sensitive headers and bodies.
  • Use caching only when the data and security model allow it.
  • Use ETags for conditional retrieval and concurrency control.
  • Compress large text responses.
  • Reuse connections through connection pools.
  • Publish supported media types.
  • Return standardized error responses.
  • Monitor latency, errors, traffic, and saturation.
  • Keep proxy and application timeout values coordinated.
  • Limit request-body and header sizes.
  • Apply rate limits to protect services.
  • Document HTTP behavior in OpenAPI.

Frequently Asked Interview Questions

1. What is HTTP?

HTTP is an application-layer request-response protocol used by clients and servers to exchange resources and data over a network.

A client sends an HTTP request containing a method, target, headers, and an optional body. The server returns a response containing a status code, headers, and an optional body.


2. What is the difference between HTTP and HTTPS?

HTTP transfers application messages without TLS protection.

HTTPS is HTTP over TLS and provides:

  • Encryption
  • Server authentication
  • Message integrity

HTTPS protects data in transit but does not replace application authentication or authorization.


3. What are the main parts of an HTTP request?

The main parts are:

Request Line

Headers

Empty Line

Optional Body

The request line contains the HTTP method, request target, and protocol version.


4. What are the main parts of an HTTP response?

The main parts are:

Status Line

Headers

Empty Line

Optional Body

The status line contains the protocol version, status code, and reason phrase.


5. What is the difference between GET and POST?

GET retrieves a resource and should not change server state. It is safe and idempotent and is commonly cacheable.

POST submits data for processing or creates a child resource. It is not safe and is not idempotent by default.


6. What is the difference between PUT and PATCH?

PUT usually replaces the complete resource representation and is expected to be idempotent.

PATCH applies a partial modification. Its idempotency depends on the operation.

Example:

{
  "status": "ACTIVE"
}

can be idempotent.

{
  "incrementBalanceBy": 100
}

is not idempotent.


7. What is the difference between safe and idempotent HTTP methods?

A safe method should not change server state.

An idempotent method may change state, but repeated identical requests should leave the server in the same final state as one request.

GET is safe and idempotent.

PUT is not safe, but it is idempotent.


8. Why is DELETE considered idempotent if the second request can return 404?

Idempotency concerns the final server state, not identical response bodies.

After the first DELETE, the resource does not exist.

After repeated DELETE requests, the resource still does not exist.

Therefore, the final state is unchanged.


9. What is the difference between 401 and 403?

401 Unauthorized normally means the request lacks valid authentication.

403 Forbidden means authentication succeeded, but the caller does not have sufficient permission.


10. What is the difference between 502, 503, and 504?

502 Bad Gateway means a proxy received an invalid response from an upstream service.

503 Service Unavailable means a service is temporarily unavailable or overloaded.

504 Gateway Timeout means a proxy did not receive an upstream response before its timeout expired.


11. What is the difference between Content-Type and Accept?

Content-Type describes the format of the current request or response body.

Accept tells the server which response formats the client can handle or prefers.

Example:

Content-Type: application/json
Accept: application/json

The request body is JSON, and the client wants a JSON response.


12. What does stateless HTTP mean?

Stateless HTTP means the protocol does not automatically retain conversational state between requests.

Each request should contain enough context to be processed independently.

Applications may still maintain state through sessions, cookies, databases, caches, or tokens.


13. What are the main differences among HTTP/1.1, HTTP/2, and HTTP/3?

HTTP/1.1 commonly processes requests over persistent TCP connections but has limited multiplexing behavior.

HTTP/2 uses binary framing, header compression, and multiplexed streams over TCP.

HTTP/3 uses QUIC over UDP and improves connection establishment, stream independence, and network migration behavior.


14. How does HTTP caching work with ETags?

The server sends an ETag with a resource representation.

The client later sends that ETag using If-None-Match.

When the representation has not changed, the server returns 304 Not Modified without the response body.

ETags can also support optimistic concurrency through If-Match.


15. What HTTP considerations are important in production APIs?

Important production concerns include:

  • HTTPS
  • Correct status codes
  • Request validation
  • Timeouts
  • Connection pooling
  • Safe retries
  • Idempotency
  • Rate limiting
  • Caching
  • Compression
  • Correlation IDs
  • Distributed tracing
  • Standardized errors
  • Request-size limits
  • Monitoring latency and failures

A senior engineer should consider HTTP behavior across the client, gateway, service, downstream dependencies, and network—not only inside the controller.


Senior-Level Interview Answer

A strong answer could be:

HTTP is an application-layer request-response protocol used by APIs and web applications. A request contains a method, target, headers, and an optional body, while a response contains a status code, headers, and an optional body. In production API design, I use methods according to their semantics, return accurate status codes, secure traffic with HTTPS, configure connection and read timeouts, retry only idempotent operations, use idempotency keys for critical POST requests, and apply caching through Cache-Control and ETags where appropriate. I also consider proxy behavior, gateway timeouts, content negotiation, compression, rate limiting, distributed tracing, and connection pooling.


Quick Revision

Topic Summary
HTTP Application-layer request-response protocol
Request Method, target, headers, optional body
Response Status, headers, optional body
Safe methods GET, HEAD, OPTIONS, TRACE
Common idempotent methods GET, HEAD, PUT, DELETE, OPTIONS, TRACE
Create resource POST with 201 Created
Async processing 202 Accepted
Successful deletion 204 No Content
Authentication failure 401
Authorization failure 403
Resource missing 404
Conflict 409
Rate limit 429
Upstream invalid response 502
Service unavailable 503
Gateway timeout 504
Secure transport HTTPS
Conditional caching ETag and If-None-Match
Concurrency protection ETag and If-Match
HTTP/2 Multiplexed streams over TCP
HTTP/3 QUIC over UDP
Interview Frequency ⭐⭐⭐⭐⭐

Key Takeaways

  • HTTP is the foundation of REST API communication.
  • Every HTTP exchange consists of a request and response.
  • Methods communicate intent and have safety and idempotency semantics.
  • Status codes should accurately describe the result.
  • Content-Type describes the current body, while Accept describes the desired response format.
  • HTTP is stateless, but applications may maintain state using sessions, cookies, tokens, and databases.
  • HTTP/1.1 uses persistent TCP connections.
  • HTTP/2 adds binary framing, header compression, and multiplexing over TCP.
  • HTTP/3 uses QUIC over UDP.
  • HTTPS protects data in transit through TLS.
  • Explicit timeouts and connection pooling are essential in production.
  • Retries must account for idempotency and duplicate processing.
  • Caching through Cache-Control, ETags, and conditional requests can reduce latency and server load.
  • Correct handling of 401, 403, 429, 502, 503, and 504 is important for production troubleshooting.
  • HTTP knowledge helps developers design APIs that are secure, scalable, observable, reliable, and easy to consume.