Networking with HttpURLConnection
Networking with HttpURLConnection
Almost every modern Android app exchanges data with a remote server — fetching a news feed, posting a user action, downloading an image. Before you reach for a higher-level library like Retrofit, you need to understand the low-level plumbing that underlies all Android HTTP work: java.net.HttpURLConnection. This class ships with the Java standard library and requires no additional dependencies, making it the right tool for small requests, build scripts, or anywhere you want zero extra weight.
Why You Cannot Do Network Work on the Main Thread
Android's main thread drives the UI: it processes touch events, measures and draws views, and runs your Activity lifecycle callbacks. Any blocking operation on this thread — including a network call that might take hundreds of milliseconds — freezes the whole UI. Android enforces this rule at runtime: trying to open a socket on the main thread throws a NetworkOnMainThreadException.
In this lesson you will use a plain background Thread to keep the focus on HttpURLConnection itself. In Lesson 4 you saw how AsyncTask, HandlerThread, and ExecutorService give you cleaner abstractions; in real apps you would wrap this code in one of those patterns.
Declaring the Internet Permission
Before your app can open any socket, you must declare the INTERNET permission in AndroidManifest.xml. Unlike dangerous permissions (camera, contacts), this is a normal permission: Android grants it automatically and you do not need a runtime prompt.
Forgetting this line produces a silent failure: the openConnection() call succeeds but every connect() call throws a java.net.SocketException: Permission denied. Always add it before writing any networking code.
Making a GET Request Step by Step
A minimal GET request with HttpURLConnection follows six steps: build a URL, open the connection, configure it, connect, read the response, and close the stream. Here is a complete, self-contained example that fetches a JSON payload from a public API:
A few details deserve attention:
setConnectTimeoutvssetReadTimeout: The connect timeout governs how long the OS waits to complete the TCP three-way handshake. The read timeout governs how long to wait for data after the connection is established. Both default to zero (infinite) if you do not set them, which means your background thread could block forever on a dead server.getResponseCode()triggers the request: Calling this method is the point at whichHttpURLConnectionactually sends the request and waits for the response headers. Calls before this point only configure the connection object.- Error stream: When the server returns 4xx or 5xx, the body is on
getErrorStream(), notgetInputStream(). Reading it lets you surface useful API error messages to the developer. disconnect()in finally: This closes the underlying socket. Skipping it leaks OS resources, especially on busy screens that make many requests.
Sending a POST Request with a JSON Body
For a POST request you must configure three extra things: set the method to "POST", enable output with setDoOutput(true), and write the body bytes to getOutputStream(). The connection will not be sent until you read the response code.
Content-Type before calling getOutputStream(). Once you start writing the body, HttpURLConnection locks in the request headers. Setting headers after this point silently has no effect.
Reading the Response Code and Handling Errors
HTTP status codes tell you the semantic outcome of a request. Your code should handle at minimum three families:
- 2xx Success — read
getInputStream()for the body. - 4xx Client Error (404 Not Found, 401 Unauthorized, 422 Unprocessable) — your request was wrong; read
getErrorStream()for the API message and surface it to the developer or user. - 5xx Server Error — server is broken; retry with exponential back-off, or degrade gracefully.
201 Created is the standard response to a successful POST that creates a resource. Always check for the full 2xx range (status >= 200 && status < 300) rather than hard-coding == 200.
Delivering Results Back to the UI
The network call runs on a background thread, but all view updates — setting text, showing a progress bar, navigating — must happen on the main thread. The canonical way to hop back is new Handler(Looper.getMainLooper()).post(runnable). If you are inside an Activity, you can use the equivalent shortcut runOnUiThread(runnable).
HTTPS and Cleartext Traffic
From Android 9 (API 28) onward, cleartext (plain HTTP) traffic is blocked by default. Your app must use HTTPS for all production URLs. During development you can temporarily allow cleartext to a specific debug host by adding a network_security_config.xml, but never ship that configuration to production.
Summary
Every Android HTTP call lives on a background thread — NetworkOnMainThreadException is non-negotiable. HttpURLConnection gives you precise control over the request method, headers, timeouts, and body. The core loop is: open, configure, call getResponseCode(), read the appropriate stream, disconnect in a finally block, and dispatch the result back to the main thread via a Handler. Once you are comfortable with this plumbing, the next lesson examines Retrofit, which automates this boilerplate while preserving all the same underlying concepts.