HTTP Basics & Threads (Java)

This lesson starts the Networking section. Suppose RecipeBox adds a "Discover" screen that downloads a list of community-shared recipes from a server. Before touching any specific HTTP library, there is one rule that governs every networking approach on Android: never make a network call on the main (UI) thread. This lesson explains why, and shows the plain-Java tools for doing it correctly.

Why the Main Thread Is Off-Limits

Android runs all UI work — drawing, touch handling, animations — on a single thread, the main thread, also called the UI thread. A network request can take anywhere from a few milliseconds to many seconds, or hang entirely on a bad connection. If that call ran on the main thread, the entire app would freeze until it finished: no scrolling, no button taps, no animations. After roughly five seconds of an unresponsive main thread, Android shows the user an "Application Not Responding" (ANR) dialog and may kill the app.

NetworkOnMainThreadException: Android doesn't just rely on convention here — since Android 3.0 (API 11), the system actively enforces this rule by throwing NetworkOnMainThreadException the moment you attempt network I/O on the main thread. This is a deliberate crash, not a bug — it exists specifically to stop this mistake from shipping.

The Deprecated Classic: AsyncTask

For years, AsyncTask was Android's built-in answer to "run this off the main thread, then update the UI afterward." It is officially deprecated since API 30, but it still appears constantly in older tutorials and legacy code, so it's worth recognizing:

// Legacy pattern — deprecated since API 30, shown for recognition only
private class FetchRecipesTask extends AsyncTask<String, Void, List<Recipe>> {

    @Override
    protected List<Recipe> doInBackground(String... urls) {
        // Network call happens here, off the main thread
        return downloadRecipes(urls[0]);
    }

    @Override
    protected void onPostExecute(List<Recipe> recipes) {
        // Back on the main thread automatically — safe to touch views here
        adapter.submitList(recipes);
    }
}
Do not use this in new code. AsyncTask had well-known problems — it silently leaked Activity references, behaved inconsistently across Android versions, and its task queue was easy to misuse. Android now recommends plain ExecutorService (shown next) or Kotlin coroutines instead.

The Modern Java Approach: ExecutorService + runOnUiThread

In plain Java, the standard replacement is simple: an ExecutorService runs the network call on a background thread pool, and runOnUiThread() (or a Handler tied to the main Looper) posts the result back to the UI thread when it's ready.

// Fetching RecipeBox's recipe list on a background thread
private final ExecutorService networkExecutor = Executors.newSingleThreadExecutor();

public void loadDiscoverRecipes(String url) {
    networkExecutor.execute(() -> {
        List<Recipe> recipes = downloadRecipes(url); // runs on the background thread

        runOnUiThread(() -> {
            // runs back on the main thread — safe to update the RecyclerView
            recipeAdapter.submitList(recipes);
        });
    });
}
Explanation:
  • Executors.newSingleThreadExecutor() creates a background worker thread dedicated to network work, keeping the UI thread free.
  • execute(() -> { ... }) hands the executor a block of code — the network call — to run there.
  • runOnUiThread(), available on any Activity, hops back to the main thread to safely touch views like the RecyclerView adapter.
  • Outside an Activity (e.g. in a plain class), the equivalent is new Handler(Looper.getMainLooper()).post(() -> { ... }).

Raw HTTP with HttpURLConnection

Java's own HttpURLConnection can make an HTTP request with zero external libraries. It's shown here once, purely for completeness — this is genuinely painful compared to a real HTTP library, and you would not want to write this by hand for every request in RecipeBox.

// Raw HttpURLConnection — verbose, shown once for completeness
private List<Recipe> downloadRecipes(String urlString) throws IOException {
    HttpURLConnection connection = null;
    try {
        URL url = new URL(urlString);
        connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(10000);

        int responseCode = connection.getResponseCode();
        if (responseCode != HttpURLConnection.HTTP_OK) {
            throw new IOException("Server returned " + responseCode);
        }

        StringBuilder response = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(connection.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
        }
        return parseRecipesJson(response.toString());
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }
}
Notice the pain: manually opening a connection, setting timeouts, checking the response code, streaming and concatenating text line-by-line, remembering to disconnect() in a finally block — and this doesn't even cover POST bodies, headers, retries, or JSON parsing. Every one of those needs more hand-written code on top of this.

What Real Apps Use Instead

The main-thread rule and the executor pattern shown here stay the same no matter which HTTP tool you use — they're the foundation. But nobody writing a real app hand-rolls HttpURLConnection for every request. The next lessons cover the actual recommended tools:

LessonWhat it covers
OkHttp (Java)A modern, efficient HTTP client that handles connection pooling, timeouts, and interceptors for you
Retrofit (Java) + GsonTurns a REST API directly into a Java interface, with JSON parsing built in
The rule from this lesson — background thread for network I/O, main thread only for UI updates — applies everywhere from here on. The next lesson, OkHttp (Java), replaces the raw HttpURLConnection code above with a proper, purpose-built HTTP client.