Your Android app crashes with:
android.os.NetworkOnMainThreadException
The stack trace points to a line where you’re making an HTTP request, and the app dies immediately.
What causes this
Android’s main thread (also called the UI thread) handles all user interface rendering — drawing views, processing touch events, running animations. If you block this thread with a network request that takes even a few hundred milliseconds, the UI freezes.
Starting with Android 3.0 (Honeycomb), Android enforces a strict policy: any network operation on the main thread throws NetworkOnMainThreadException. This isn’t a suggestion — it’s a hard crash.
You’ll hit this when:
- You call an HTTP client (OkHttp, HttpURLConnection, etc.) directly from an Activity, Fragment, or any code running on the main thread
- You’re prototyping quickly and forgot to move the call off the main thread
- A library you’re using makes a synchronous network call internally
Fix 1: Use Kotlin coroutines
The modern and recommended approach. Launch a coroutine on the IO dispatcher:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
api.fetchData() // runs on background thread
}
// Back on main thread — safe to update UI
textView.text = result
}
lifecycleScope is tied to the Activity/Fragment lifecycle, so the coroutine is automatically cancelled if the user navigates away. No memory leaks.
Fix 2: Use a background thread (Java)
If you’re in a Java codebase without coroutines:
new Thread(() -> {
String result = fetchData(); // runs on background thread
runOnUiThread(() -> {
textView.setText(result); // update UI on main thread
});
}).start();
This works but is bare-bones. You’re responsible for error handling, cancellation, and lifecycle management. For anything beyond a quick prototype, use a more structured approach.
Fix 3: Use Retrofit with coroutines or callbacks
Retrofit handles threading for you when configured properly:
// Define the API interface with suspend
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): User
}
// Call it from a coroutine
lifecycleScope.launch {
try {
val user = retrofit.create(ApiService::class.java).getUser("123")
nameTextView.text = user.name
} catch (e: Exception) {
Log.e("API", "Failed to fetch user", e)
}
}
With the suspend keyword, Retrofit automatically runs the request on a background thread and resumes on the calling dispatcher.
Fix 4: Use RxJava (if already in your stack)
If your project uses RxJava:
apiService.getUser("123")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ user -> nameTextView.text = user.name },
{ error -> Log.e("API", "Failed", error) }
)
subscribeOn(Schedulers.io()) moves the work off the main thread. observeOn(AndroidSchedulers.mainThread()) switches back for the UI update.
Don’t do this: Disabling StrictMode
You might find answers suggesting you disable the check:
// ❌ NEVER do this in production
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder().permitNetwork().build()
)
This removes the crash but doesn’t fix the problem. Your UI will still freeze during network calls, leading to ANR (Application Not Responding) dialogs and a terrible user experience.
How to prevent it
- Use
suspendfunctions and coroutines as your default for all async work in Kotlin - Configure Retrofit, Room, and other libraries to use coroutines or callbacks — never their synchronous APIs
- Enable StrictMode in debug builds to catch accidental main-thread I/O early
- Use the Android Studio profiler to identify main-thread blocking calls during development