🔧 Error Fixes
· 2 min read
Last updated on

Android: NetworkOnMainThreadException — How to Fix It


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 suspend functions 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