Skip to content
Java networking 7 min read

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:

  1. Create a URL object pointing at your endpoint.
  2. Call url.openConnection() and cast it to HttpURLConnection.
  3. Configure the request (method, headers, timeouts).
  4. Write a request body if needed (POST/PUT).
  5. Read the response code, headers, and body.
  6. Disconnect.

Note: For new projects targeting Java 11+, consider the modern java.net.http.HttpClient API introduced in Java 11, which is cleaner and supports HTTP/2 and async requests. HttpURLConnection is 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 a BufferedReader for text responses. The raw stream gives you bytes one at a time; the BufferedReader buffers 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 call setRequestProperty() 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 return null if the server sent no body with the error (for example, a bare 404 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

MethodPurpose
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:

  1. DNS lookup — the URL’s hostname is resolved via the JVM’s DNS cache (default positive TTL: 30 seconds, controlled by networkaddress.cache.ttl).
  2. TCP connection — a Socket is 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 default TrustStore.
  3. Connection pooling — the JDK maintains a per-JVM HTTP connection pool (sun.net.www.http.KeepAliveCache). If you call conn.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 calling disconnect() in a tight loop is slower than not calling it. In practice, call disconnect() when you are truly done with the host.
  4. Request serialization — request line, headers, and optional body bytes are written to the socket’s OutputStream in HTTP/1.1 format.
  5. Response parsing — the status line (HTTP/1.1 200 OK) and headers are parsed from the socket’s InputStream. The body is left as a raw stream for you to consume.
  6. HTTPS trust — if the server’s certificate is not in the JVM’s default truststore (the cacerts file in $JAVA_HOME/lib/security/), you will get an SSLHandshakeException. You can supply a custom SSLSocketFactory via conn.setSSLSocketFactory(...) for scenarios like self-signed certificates.

Note: HttpURLConnection only supports HTTP/1.1. If you need HTTP/2 (multiplexed streams, header compression) or async non-blocking requests, use the java.net.http.HttpClient API 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() after getOutputStream() — headers sent at that point are ignored. Configure everything before writing the body.
  • Ignoring getErrorStream() — if you always call getInputStream() on a 4xx/5xx response, you will get an exception and lose the error message from the server.
  • No timeouts — without setConnectTimeout and setReadTimeout, a connection to an unresponsive host will block your thread indefinitely.
  • Forgetting setDoOutput(true) for POST — you will get an IllegalStateException when you try to write to getOutputStream().
  • URL Class — create and inspect URL objects that feed into HttpURLConnection
  • 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 HandlingHttpURLConnection throws IOException on errors; handle them cleanly
Last updated June 13, 2026
Was this helpful?