apache/cordova-android · error · IllegalStateException

Do not perform IO operations on the UI thread. Use CordovaIn

Error message

Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.

What it means

CordovaResourceApi.assertBackgroundThread guards all IO entry points: when thread checking is enabled (the default) and the calling thread is the Android main/UI thread (Looper.getMainLooper().getThread()), it throws IllegalStateException. File and network IO on the UI thread jams the app and can trigger ANRs, so cordova enforces background execution.

Source

Thrown at framework/src/org/apache/cordova/CordovaResourceApi.java:422

                outputStream.close();
            }
        }
    }

    public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
        copyResource(openForRead(sourceUri), outputStream);
    }

    // Added in 3.5.0.
    public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {
        copyResource(openForRead(sourceUri), openOutputStream(dstUri));
    }

    private void assertBackgroundThread() {
        if (threadCheckingEnabled) {
            Thread curThread = Thread.currentThread();
            if (curThread == Looper.getMainLooper().getThread()) {
                throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
            }
            if (curThread == jsThread) {
                throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
            }
        }
    }

    private String getDataUriMimeType(Uri uri) {
        String uriAsString = uri.getSchemeSpecificPart();
        int commaPos = uriAsString.indexOf(',');
        if (commaPos == -1) {
            return null;
        }
        String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
        if (mimeParts.length > 0) {
            return mimeParts[0];
        }
        return null;

View on GitHub (pinned to 7c1e190064)

Solutions

  1. Move the IO call onto Cordova's thread pool: cordova.getThreadPool().execute(() -> resourceApi.copyResource(...))
  2. For UI-initiated work, wrap the whole read/write in the thread pool and hop back with runOnUiThread for UI updates only
  3. As a last-resort workaround during migration, webView.getResourceApi().setThreadCheckingEnabled(false) — only for debugging, since it removes the ANR protection

Example fix

// before: crashes on UI thread
copyButton.setOnClickListener(v ->
    resourceApi.copyResource(srcUri, dstUri));

// after
copyButton.setOnClickListener(v ->
    cordova.getThreadPool().execute(() ->
        resourceApi.copyResource(srcUri, dstUri)));
Defensive patterns

Strategy: validation

Validate before calling

// guard before IO
if (Looper.myLooper() == Looper.getMainLooper()) {
    throw new IllegalStateException("refuse IO on UI thread");
}
resourceApi.copyResource(src, dst);

Type guard

static boolean isSafeIoThread() {
    Thread t = Thread.currentThread();
    return t != Looper.getMainLooper().getThread() && t != CordovaResourceApi.jsThread;
}

Try / catch

if (isSafeIoThread()) { resourceApi.copyResource(src, dst); }
else { cordova.getThreadPool().execute(() -> resourceApi.copyResource(src, dst)); }

Prevention

When it happens

Trigger: Invoking openForRead, openOutputStream, copyResource, createHttpConnection, etc. directly from onCreate/onPostExecute/UI-button handlers or any code running on the main looper — e.g. a plugin doing synchronous file IO inside execute() when called on the UI thread.

Common situations: Plugin execute() assumed to be on a background thread but invoked on UI; refactor moving IO into a click listener; Native code calling the resource API from onResume; occasional crashes only when threadCheckingEnabled is on (default).

Related errors


AI-assisted analysis of apache/cordova-android@7c1e190064 (2026-08-22). Data as JSON: /api/errors/d84dad2621acb927. Report an issue: GitHub.