Appearance
REST HTTP Verbs and Status Codes
Introduction
REST (Representational State Transfer) is an architectural style for distributed hypermedia systems, defined by Roy Fielding in his 2000 doctoral dissertation. It leverages the semantics of HTTP to create stateless, cacheable, and uniform interfaces between clients and servers. Understanding HTTP methods and status codes is foundational to building interoperable, well-behaved web APIs.
HTTP Methods: Semantics and Idempotency
HTTP defines a set of request methods that indicate the desired action for a given resource. Each method carries specific semantic meaning and idempotency guarantees.
| Method | Semantic | Idempotent | Safe | Body |
|---|---|---|---|---|
| GET | Retrieve resource | Yes | Yes | No |
| HEAD | Retrieve headers | Yes | Yes | No |
| POST | Create/process | No | No | Yes |
| PUT | Replace resource | Yes | No | Yes |
| PATCH | Partial update | No* | No | Yes |
| DELETE | Remove resource | Yes | No | No |
| OPTIONS | Describe options | Yes | Yes | No |
*PATCH can be made idempotent by using conditional requests (e.g.,
If-Match).
HTTP Methods Flowchart
Status Codes
HTTP status codes communicate the result of a request. They are grouped into five classes.
Status Code Categories
Status Code Decision Tree
RESTful Principles and Constraints
Fielding defined six architectural constraints that together constitute REST:
Request/Response Structure
Request/Response Lifecycle Sequence Diagram
CRUD to HTTP Verb Mapping
Implementation
Java HttpServer with RequestHandler Interface
java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
/**
* Minimal REST framework using Java's built-in HttpServer.
*/
public interface RequestHandler {
void handle(HttpExchange exchange) throws IOException;
}
// Router that dispatches by method
class MethodRouter implements HttpHandler {
private final Map<String, RequestHandler> methodHandlers = new HashMap<>();
public MethodRouter on(String method, RequestHandler handler) {
methodHandlers.put(method.toUpperCase(), handler);
return this;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
String method = exchange.getRequestMethod().toUpperCase();
RequestHandler handler = methodHandlers.get(method);
if (handler != null) {
handler.handle(exchange);
} else {
sendResponse(exchange, 405, "{\"error\":\"Method Not Allowed\"}");
}
}
public static void sendResponse(HttpExchange exchange, int status, String body) throws IOException {
byte[] bytes = body.getBytes();
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(status, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
}UserHandler — GET, POST, PUT, DELETE for /users
java
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Handles CRUD operations for the /users endpoint.
*/
public class UserHandler {
private final Map<Long, String> store = new ConcurrentHashMap<>();
private final AtomicLong idSeq = new AtomicLong(1);
public RequestHandler getAll() {
return exchange -> {
String json = store.entrySet().stream()
.map(e -> "{\"id\":" + e.getKey() + ",\"name\":\"" + e.getValue() + "\"}")
.collect(java.util.stream.Collectors.joining(",", "[", "]"));
MethodRouter.sendResponse(exchange, 200, json);
};
}
public RequestHandler create() {
return exchange -> {
String body = readBody(exchange);
// Minimal parse: expects {"name":"Alice"}
String name = extractField(body, "name");
if (name == null || name.isBlank()) {
MethodRouter.sendResponse(exchange, 400,
"{\"error\":\"'name' field is required\"}");
return;
}
long id = idSeq.getAndIncrement();
store.put(id, name);
exchange.getResponseHeaders().add(
"Location", "/users/" + id);
MethodRouter.sendResponse(exchange, 201,
"{\"id\":" + id + ",\"name\":\"" + name + "\"}");
};
}
public RequestHandler replace(long id) {
return exchange -> {
String body = readBody(exchange);
String name = extractField(body, "name");
if (!store.containsKey(id)) {
MethodRouter.sendResponse(exchange, 404,
"{\"error\":\"User not found\"}");
return;
}
store.put(id, name);
MethodRouter.sendResponse(exchange, 200,
"{\"id\":" + id + ",\"name\":\"" + name + "\"}");
};
}
public RequestHandler delete(long id) {
return exchange -> {
if (store.remove(id) == null) {
MethodRouter.sendResponse(exchange, 404,
"{\"error\":\"User not found\"}");
} else {
exchange.sendResponseHeaders(204, -1);
}
};
}
private String readBody(HttpExchange exchange) throws IOException {
try (InputStream is = exchange.getRequestBody()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
private String extractField(String json, String field) {
// Naive extraction — use a proper JSON library in production
String marker = "\"" + field + "\"";
int idx = json.indexOf(marker);
if (idx < 0) return null;
int colon = json.indexOf(':', idx);
int start = json.indexOf('"', colon) + 1;
int end = json.indexOf('"', start);
return json.substring(start, end);
}
}Main Class — Wiring the Server
java
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
/**
* Entry point: creates an HTTP server and registers /users routes.
*/
public class RestServer {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(
new InetSocketAddress(8080), 0);
UserHandler users = new UserHandler();
// Collection endpoint: GET all, POST create
server.createContext("/users", new MethodRouter()
.on("GET", users.getAll())
.on("POST", users.create()));
// Item endpoints: PUT replace, DELETE remove (id parsed from path)
server.createContext("/users/", exchange -> {
String path = exchange.getRequestURI().getPath();
long id = Long.parseLong(path.substring("/users/".length()));
new MethodRouter()
.on("PUT", users.replace(id))
.on("DELETE", users.delete(id))
.handle(exchange);
});
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.start();
System.out.println("REST server started on :8080");
}
}Status Code Helper Utility
java
/**
* Utility for selecting appropriate HTTP status codes.
*/
public final class HttpStatus {
private HttpStatus() {}
// 2xx
public static final int OK = 200;
public static final int CREATED = 201;
public static final int ACCEPTED = 202;
public static final int NO_CONTENT = 204;
public static final int PARTIAL_CONTENT = 206;
// 3xx
public static final int MOVED_PERMANENTLY = 301;
public static final int NOT_MODIFIED = 304;
// 4xx
public static final int BAD_REQUEST = 400;
public static final int UNAUTHORIZED = 401;
public static final int FORBIDDEN = 403;
public static final int NOT_FOUND = 404;
public static final int METHOD_NOT_ALLOWED = 405;
public static final int CONFLICT = 409;
public static final int GONE = 410;
public static final int UNPROCESSABLE_ENTITY = 422;
public static final int TOO_MANY_REQUESTS = 429;
// 5xx
public static final int INTERNAL_SERVER_ERROR = 500;
public static final int BAD_GATEWAY = 502;
public static final int SERVICE_UNAVAILABLE = 503;
public static final int GATEWAY_TIMEOUT = 504;
/** Returns true for any 2xx status. */
public static boolean isSuccess(int status) {
return status >= 200 && status < 300;
}
/** Returns true for any 4xx status. */
public static boolean isClientError(int status) {
return status >= 400 && status < 500;
}
/** Returns true for any 5xx status. */
public static boolean isServerError(int status) {
return status >= 500 && status < 600;
}
/** Maps CRUD operation to its canonical HTTP method. */
public static String crudToMethod(String operation) {
return switch (operation.toUpperCase()) {
case "CREATE" -> "POST";
case "READ" -> "GET";
case "UPDATE" -> "PUT";
case "PARTIAL_UPDATE" -> "PATCH";
case "DELETE" -> "DELETE";
default -> throw new IllegalArgumentException(
"Unknown operation: " + operation);
};
}
}Best Practices
Resource naming — Use plural nouns for collections (
/users,/orders). Avoid verbs in URIs; let the HTTP method carry the action semantics (GET /usersnotGET /getUsers).Idempotency by design — Make PUT and DELETE unconditionally idempotent. For PATCH, use
If-Matchwith ETags to avoid lost-update races.Caching headers — Return
ETagon all GET responses. SetCache-Control: no-storefor sensitive data,Cache-Control: max-age=3600, must-revalidatefor public resources. UseLast-Modifiedas a fallback when ETags are expensive.Versioning strategies — Prefer URI versioning (
/v1/users) for clear lifecycle management. ConsiderAccept: application/vnd.api+json;version=2(vendor media type) or theAccept-Versionheader for header-based versioning when URIs must remain stable.Consistent error bodies — Always return a structured error object (e.g.,
{"type": "...", "title": "...", "status": 404, "detail": "..."}per RFC 9457 / Problem Details).Use 201 + Location on POST — Always set the
Locationheader pointing to the newly created resource so clients can retrieve it without a second lookup.Pagination — Use
Linkheaders (RFC 5988) orX-Total-Countfor collection pagination. Prefer cursor-based pagination for large, frequently-updated datasets.Rate limiting — Return
429 Too Many RequestswithRetry-AfterandX-RateLimit-*headers to help clients back off gracefully.