HttpURLConnection
HttpURLConnection is Java’s built-in class for sending HTTP and HTTPS requests without any third-party library. It has been part of the JDK since Java 1.1 and remains widely used today — understanding it gives you a solid foundation for all HTTP communication in Java, and helps you appreciate the higher-level HTTP clients built on top of it.
How It Fits In
HttpURLConnection extends URLConnection, which in turn is produced by the URL class. The typical flow is:
- Create a
URLobject pointing at your endpoint. - Call
url.openConnection()and cast it toHttpURLConnection. - Configure the request (method, headers, timeouts).
- Write a request body if needed (POST/PUT).
- Read the response code, headers, and body.
- Disconnect.
Note: For new projects targeting Java 11+, consider the modern
java.net.http.HttpClientAPI introduced in Java 11, which is cleaner and supports HTTP/2 and async requests.HttpURLConnectionis still perfectly valid and is available in every JDK version.
Your First GET Request
The simplest use case: fetch a resource and print its body.
import java.io.*;
import java.net.*;
public class SimpleGet {
public static void main(String[] args) throws IOException {
URL url = new URL("https://jsonplaceholder.typicode.com/todos/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000); // 5 seconds to establish connection
conn.setReadTimeout(5000); // 5 seconds to read data
int status = conn.getResponseCode();
System.out.println("HTTP Status: " + status);
// Read the response body
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
StringBuilder body = new StringBuilder();
while ((line = reader.readLine()) != null) {
body.append(line);
}
System.out.println("Body: " + body);
}
conn.disconnect();
}
}
Output:
HTTP Status: 200
Body: { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}
Tip: Always wrap
getInputStream()in aBufferedReaderfor text responses. The raw stream gives you bytes one at a time; theBufferedReaderbuffers reads into chunks, which is much faster.
Sending a POST Request
POST requests send a body to the server. You must set setDoOutput(true) before writing to the connection’s output stream, and set the Content-Type header so the server knows how to parse your payload.
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class SimplePost {
public static void main(String[] args) throws IOException {
URL url = new URL("https://jsonplaceholder.typicode.com/posts");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true); // allow writing a body
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// Write the JSON body
String json = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
byte[] input = json.getBytes(StandardCharsets.UTF_8);
try (OutputStream os = conn.getOutputStream()) {
os.write(input);
}
int status = conn.getResponseCode();
System.out.println("HTTP Status: " + status); // 201 Created
// Read the response
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println("Response: " + response);
}
conn.disconnect();
}
}
Output:
HTTP Status: 201
Response: {"title":"foo","body":"bar","userId":1,"id":101}
Warning:
conn.getOutputStream()implicitly sends the request headers and opens the connection. Do not callsetRequestProperty()after you have written to the output stream — those headers will be silently ignored.
Setting Request Headers
Use setRequestProperty(key, value) to set a header, or addRequestProperty(key, value) to append an additional value to a header that may already have one.
conn.setRequestProperty("Authorization", "Bearer my-api-token");
conn.setRequestProperty("User-Agent", "MyApp/1.0");
conn.setRequestProperty("Accept-Language", "en-US");
// Add a second value to a multi-value header (e.g. Accept)
conn.addRequestProperty("Accept", "application/json");
conn.addRequestProperty("Accept", "text/plain");
Reading Response Headers
After the response arrives, inspect all headers using getHeaderFields(), or fetch a specific header by name:
HttpURLConnection conn = (HttpURLConnection) new URL("https://example.com").openConnection();
conn.setRequestMethod("GET");
conn.connect();
System.out.println("Content-Type : " + conn.getContentType());
System.out.println("Content-Length: " + conn.getContentLength());
System.out.println("Server : " + conn.getHeaderField("Server"));
// Iterate over all headers
conn.getHeaderFields().forEach((name, values) ->
System.out.println(name + " = " + values));
conn.disconnect();
Handling HTTP Errors (4xx / 5xx)
When the server returns an error status, getInputStream() throws an IOException. Use getErrorStream() to read the error body instead.
int status = conn.getResponseCode();
InputStream stream = (status >= 400)
? conn.getErrorStream()
: conn.getInputStream();
if (stream != null) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(stream, StandardCharsets.UTF_8))) {
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) sb.append(line);
System.out.println("Response (" + status + "): " + sb);
}
}
Note:
getErrorStream()can returnnullif the server sent no body with the error (for example, a bare404 Not Found). Always null-check it before reading.
PUT and DELETE Requests
The same pattern works for any HTTP method — just change setRequestMethod.
// PUT (update a resource)
conn.setRequestMethod("PUT");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");
// ... write body, read response ...
// DELETE (remove a resource)
conn.setRequestMethod("DELETE");
// DELETE typically has no body
int status = conn.getResponseCode(); // expect 200 or 204
Following Redirects
By default, HttpURLConnection follows HTTP 3xx redirects automatically only within the same protocol (HTTP→HTTP or HTTPS→HTTPS). It will not follow an HTTP→HTTPS redirect automatically.
// Enable automatic redirect following (default is true for same-protocol)
HttpURLConnection.setFollowRedirects(true); // global setting for all connections
conn.setInstanceFollowRedirects(true); // per-connection override
If you must follow an HTTP→HTTPS redirect, you need to detect the Location header manually and open a new connection:
int status = conn.getResponseCode();
if (status == 301 || status == 302) {
String newUrl = conn.getHeaderField("Location");
conn.disconnect();
conn = (HttpURLConnection) new URL(newUrl).openConnection();
// re-configure and send again ...
}
A Reusable Helper Method
Repeating boilerplate for every request is tedious. A helper method keeps your code clean:
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class HttpClient {
public static String get(String endpoint) throws IOException {
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10_000);
conn.setReadTimeout(10_000);
conn.setRequestProperty("Accept", "application/json");
int status = conn.getResponseCode();
InputStream stream = status >= 400 ? conn.getErrorStream() : conn.getInputStream();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(stream, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) sb.append(line);
return sb.toString();
} finally {
conn.disconnect();
}
}
public static void main(String[] args) throws IOException {
String response = get("https://jsonplaceholder.typicode.com/users/1");
System.out.println(response);
}
}
Quick Reference: Important Methods
| Method | Purpose |
|---|---|
setRequestMethod(String) | Set the HTTP verb: "GET", "POST", "PUT", "DELETE", etc. |
setConnectTimeout(int ms) | Max time to establish the TCP connection |
setReadTimeout(int ms) | Max time to wait for data after connecting |
setDoOutput(boolean) | Must be true to write a request body |
setRequestProperty(k, v) | Set a request header (replaces existing value) |
addRequestProperty(k, v) | Append to a multi-value request header |
getResponseCode() | Read the HTTP status code (triggers the request) |
getInputStream() | Read the success response body |
getErrorStream() | Read the error response body (4xx/5xx) |
getHeaderField(name) | Read a specific response header |
disconnect() | Release the underlying connection/socket |
Under the Hood
When you call conn.getResponseCode() (or getInputStream()), HttpURLConnection performs these steps under the hood:
- DNS lookup — the
URL’s hostname is resolved via the JVM’s DNS cache (default positive TTL: 30 seconds, controlled bynetworkaddress.cache.ttl). - TCP connection — a
Socketis opened to the resolved IP and port 80 (HTTP) or 443 (HTTPS). For HTTPS, the JVM’s built-in TLS implementation (JSSE) performs the TLS handshake using certificates from the defaultTrustStore. - Connection pooling — the JDK maintains a per-JVM HTTP connection pool (
sun.net.www.http.KeepAliveCache). If you callconn.disconnect(), the underlying socket is closed and removed from the pool. If you don’t disconnect, the socket is returned to the pool and reused for the next request to the same host — this is why callingdisconnect()in a tight loop is slower than not calling it. In practice, calldisconnect()when you are truly done with the host. - Request serialization — request line, headers, and optional body bytes are written to the socket’s
OutputStreamin HTTP/1.1 format. - Response parsing — the status line (
HTTP/1.1 200 OK) and headers are parsed from the socket’sInputStream. The body is left as a raw stream for you to consume. - HTTPS trust — if the server’s certificate is not in the JVM’s default truststore (the
cacertsfile in$JAVA_HOME/lib/security/), you will get anSSLHandshakeException. You can supply a customSSLSocketFactoryviaconn.setSSLSocketFactory(...)for scenarios like self-signed certificates.
Note:
HttpURLConnectiononly supports HTTP/1.1. If you need HTTP/2 (multiplexed streams, header compression) or async non-blocking requests, use thejava.net.http.HttpClientAPI introduced in Java 11.
Common Pitfalls
- Not calling
disconnect()— leaving connections open is fine for keep-alive reuse, but always disconnect at the end of your application’s lifecycle or when switching hosts. - Calling
setRequestProperty()aftergetOutputStream()— headers sent at that point are ignored. Configure everything before writing the body. - Ignoring
getErrorStream()— if you always callgetInputStream()on a 4xx/5xx response, you will get an exception and lose the error message from the server. - No timeouts — without
setConnectTimeoutandsetReadTimeout, a connection to an unresponsive host will block your thread indefinitely. - Forgetting
setDoOutput(true)for POST — you will get anIllegalStateExceptionwhen you try to write togetOutputStream().
Related Topics
- URL Class — create and inspect
URLobjects that feed intoHttpURLConnection - URLConnection — the parent class of
HttpURLConnection; covers the shared API - Networking Basics — overview of Java’s full networking API
- Socket Programming — go lower-level with raw TCP sockets
- InetAddress — DNS lookups and IP address resolution that underpin every HTTP call
- Exception Handling —
HttpURLConnectionthrowsIOExceptionon errors; handle them cleanly