bumptech/glide · error · IllegalStateException

You can't start or clear loads in RequestListener or Target

Error message

You can't start or clear loads in RequestListener or Target callbacks. If you're trying to start a fallback request when a load fails, use RequestBuilder#error(RequestBuilder). Otherwise consider posting your into() or clear() calls to the main thread using a Handler instead.

What it means

Thrown by SingleRequest.assertNotCallingCallbacks() (invoked at the start of begin/clear) when isCallingCallbacks is true — i.e. you are synchronously inside a RequestListener.onResourceReady/onLoadFailed or a Target callback (onResourceReady, onLoadFailed, onLoadCleared) and you attempt to start a new Glide load or clear one from within that callback. Glide holds the requestLock during callbacks and re-entrant start/clear would deadlock or corrupt state, hence the explicit guard (issue #2413).

Source

Thrown at library/src/main/java/com/bumptech/glide/request/SingleRequest.java:307

   *
   * @see #clear()
   */
  @GuardedBy("requestLock")
  private void cancel() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    target.removeCallback(this);
    if (loadStatus != null) {
      loadStatus.cancel();
      loadStatus = null;
    }
  }

  // Avoids difficult to understand errors like #2413.
  @GuardedBy("requestLock")
  private void assertNotCallingCallbacks() {
    if (isCallingCallbacks) {
      throw new IllegalStateException(
          "You can't start or clear loads in RequestListener or"
              + " Target callbacks. If you're trying to start a fallback request when a load fails,"
              + " use RequestBuilder#error(RequestBuilder). Otherwise consider posting your into()"
              + " or clear() calls to the main thread using a Handler instead.");
    }
  }

  /**
   * Cancels the current load if it is in progress, clears any resources held onto by the request
   * and replaces the loaded resource if the load completed with the placeholder.
   *
   * <p>Cleared requests can be restarted with a subsequent call to {@link #begin()}
   *
   * @see #cancel()
   */
  @Override
  public void clear() {
    Resource<R> toRelease = null;

View on GitHub (pinned to eb14a895d8)

Solutions

  1. For fallback-on-failure use the built-in API: .load(primary).error(Glide.with(ctx).load(fallback))
  2. If you must start a new load from a callback, post it to the main thread Handler (or use view.post { }) so it runs after the callback returns
  3. Use RequestBuilder.thumbnail() or .error(RequestBuilder) for chained loads instead of doing them in listeners
  4. Never call RequestManager.clear(target) for the same target from inside that target's own callback

Example fix

// before
listener = object : RequestListener<Drawable> {
  override fun onLoadFailed(...): Boolean {
    Glide.with(view).load(fallbackUrl).into(target) // IllegalStateException
    return true
  }
}

// after - use the error(RequestBuilder) API
Glide.with(view)
  .load(primaryUrl)
  .error(Glide.with(view).load(fallbackUrl))
  .into(target)
Defensive patterns

Strategy: fallback

Validate before calling

// Structural rule: do NOT start/clear loads inside RequestListener/Target callbacks.
// Use RequestBuilder.error(RequestBuilder) for fallback loads instead.

Prevention

When it happens

Trigger: Inside RequestListener.onResourceReady starting Glide.with(...).load(fallbackUrl).into(target); inside Target.onLoadFailed calling clear() on the same request; chaining a thumbnail->error request manually inside the listener rather than using RequestBuilder.error(RequestBuilder).

Common situations: Implementing a fallback-image pattern manually in onLoadFailed; preloading the next image inside onResourceReady; calling notifyDataSetChanged() that synchronously triggers a Glide.into() from within a callback.

Related errors


AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14). Data as JSON: /api/errors/b73f180ff6600885. Report an issue: GitHub.