A Morning That Starts with NetworkOnMainThreadException
\n\nYou are writing an Android app. Everything is going great until you decide to make a simple HTTP request in a button. You press "Refresh" — and the app crashes with a red stack trace. Sound familiar? android.os.NetworkOnMainThreadException is a classic error that every Android developer has seen at least once in their life. In 2025, it is still alive, and beginners keep stepping on the same rake.
\n\nIn this article, we will break down why NetworkOnMainThreadException occurs, how to fix it in Java, and which approaches are considered best practices in 2025. You will get ready-made solutions with code that you can immediately insert into your project.
\n\nWhy Does NetworkOnMainThreadException Occur?
\n\nAndroid is not just Java. It is a system with strict rules. One of them: do not perform network operations on the main (UI) thread. The main thread is responsible for rendering the interface and handling touches. If it gets blocked for even a second, the user will see lag, and if for five seconds, the system will show an "Application Not Responding" (ANR) dialog.
\n\nStarting from Android 3.0 (Honeycomb), Google added protection: any network call on the main thread throws android.os.NetworkOnMainThreadException. The exception says: "Hey, you are doing something dangerous for UX!".
In 2025, this rule hasn't gone anywhere. Even if you use modern libraries (Retrofit, OkHttp), you still must run them on a background thread. The exception is not a bug, but a safety feature.
\n\nTop 5 Ways to Fix the Error (Java, 2025)
\n\nWe will look at five approaches — from outdated to modern. Choose the one that fits your project.
\n\n1. Classic AsyncTask (Legacy, but Works)
\n\nAsyncTask was the standard about 10 years ago. In 2025, it is not recommended (it is deprecated in API 30+), but you may still encounter it in old projects.
\n\nprivate class MyNetworkTask extends AsyncTask<String, Void, String> {\n @Override\n protected String doInBackground(String... params) {\n // This is already a background thread\n try {\n URL url = new URL(params[0]);\n HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));\n StringBuilder result = new StringBuilder();\n String line;\n while ((line = reader.readLine()) != null) {\n result.append(line);\n }\n return result.toString();\n } catch (Exception e) {\n return "Error: " + e.getMessage();\n }\n }\n\n @Override\n protected void onPostExecute(String result) {\n // This is already the UI thread\n textView.setText(result);\n }\n}\n\n// Launch:\nnew MyNetworkTask().execute("https://api.example.com/data");\n\nCons: AsyncTask is hard to test, inconvenient for request chains, and can leak memory on screen rotation. Use only if the project is written in Java 7-8.
\n\n2. Java Thread + Handler (Clean Approach)
\n\nThe simplest way is to create a separate thread and return the result to the UI via Handler.
\n\nprivate Handler mainHandler = new Handler(Looper.getMainLooper());\n\nprivate void fetchDataInBackground() {\n new Thread(new Runnable() {\n @Override\n public void run() {\n // Background thread\n final String result = performNetworkCall();\n \n // Return to UI thread\n mainHandler.post(new Runnable() {\n @Override\n public void run() {\n textView.setText(result);\n }\n });\n }\n }).start();\n}\n\nprivate String performN