Sending Requests & Handling Responses
Sending Requests & Handling Responses
The previous lesson introduced HttpClient and its builder. Now we put it to work: crafting GET and POST requests, attaching headers, supplying a request body, and correctly interpreting the HttpResponse you get back. These are the everyday mechanics of any HTTP-based integration.
Building an HttpRequest
Every HTTP call starts with an HttpRequest built through its fluent builder. The minimum you supply is a URI and an HTTP method; everything else is optional but often essential in practice.
Sending a GET Request
Pass the request and a body handler to client.send(). The body handler tells the client how to convert the raw response bytes into the type you want:
Common built-in body handlers include ofString(), ofBytes(), ofFile(Path), ofInputStream(), and discarding() (for responses where the body is irrelevant). Choose the one that matches what you intend to do with the data — streaming a large download to a file with ofFile() avoids loading gigabytes into heap memory.
Reading Response Metadata
HttpResponse<T> surfaces more than just the body:
Sending a POST Request with a JSON Body
POST requests supply a body publisher that serialises your payload into bytes the client can stream to the server. The most common case is a JSON string:
For a POST with no body (uncommon but valid, e.g. triggering an action endpoint), use HttpRequest.BodyPublishers.noBody().
Other Built-in Body Publishers
BodyPublishers.ofString(String)— UTF-8 encoded text; the workhorse for JSON.BodyPublishers.ofByteArray(byte[])— raw bytes; useful for binary protocols or pre-serialised data.BodyPublishers.ofFile(Path)— streams a file directly from disk without loading it into memory first; ideal for file-upload endpoints.BodyPublishers.ofInputStream(Supplier<InputStream>)— lazily supplies an input stream; supports large or dynamically generated payloads.BodyPublishers.noBody()— signals an explicitly empty body (Content-Length: 0).
Handling HTTP Status Codes Professionally
HTTP defines status codes in five classes. Understanding the classes — not just memorising individual numbers — is what lets you write robust error handling:
- 1xx Informational — protocol-level signals (e.g.
100 Continue). Handled automatically by the client; you rarely see them. - 2xx Success —
200 OK(GET/PUT),201 Created(POST),204 No Content(DELETE with no body). - 3xx Redirection —
HttpClientfollows redirects automatically when configured withfollowRedirects(HttpClient.Redirect.NORMAL). - 4xx Client Error — the request is wrong.
400 Bad Request,401 Unauthorized,403 Forbidden,404 Not Found,409 Conflict,429 Too Many Requests. - 5xx Server Error — the server failed.
500 Internal Server Error,502 Bad Gateway,503 Service Unavailable.
A concise helper that encodes this logic:
BodyHandlers.ofString() for a DELETE that returns 204, response.body() will be an empty string — not null. Passing an empty string to a JSON parser will throw. Check the status code before deserialising, or use BodyHandlers.discarding() when you know the body will be empty.
Setting Headers Correctly
Headers fall into a few categories worth knowing explicitly:
- Content-Type — describes the body you are sending. Always set it on POST/PUT/PATCH. The most common values are
application/jsonandapplication/x-www-form-urlencoded. - Accept — tells the server what format you can parse. Providing
application/jsonis explicit and avoids surprises with APIs that can return XML or HTML. - Authorization — carries credentials. Use
Bearer <token>for OAuth 2.0 / JWT, orBasic <base64>for HTTP Basic auth. Never pass credentials as query parameters. - User-Agent — identifies your client. Some APIs rate-limit or block requests without a meaningful user agent string.
- Idempotency-Key (Stripe, etc.) — a unique UUID you generate per logical operation so the server can safely deduplicate retried POST requests.
Summary
You now know how to craft both GET and POST requests, attach arbitrary headers, choose the right body publisher for your payload, and read back the status code, headers, and body of the response. These are the building blocks every HTTP interaction in Java is composed of — the async and REST-client patterns in the next lessons build directly on this foundation.