bumptech/glide · error · IllegalStateException

You cannot modify locked T, consider clone()

Error message

You cannot modify locked T, consider clone()

What it means

Thrown by BaseRequestOptions.selfOrThrowIfLocked() whenever any mutator (centerCrop, placeholder, override, etc.) is called on a RequestOptions object whose isLocked flag is true and auto-clone is not enabled. Glide locks options once they have been applied to a live request so that mid-flight mutations cannot corrupt the in-progress load.

Source

Thrown at library/src/main/java/com/bumptech/glide/request/BaseRequestOptions.java:1314

   *
   * <p>Auto clone is not retained by cloned objects returned from mutations. The cloned objects are
   * mutable and are not locked.
   */
  @NonNull
  public T autoClone() {
    if (isLocked && !isAutoCloneEnabled) {
      throw new IllegalStateException(
          "You cannot auto lock an already locked options object" + ", try clone() first");
    }
    isAutoCloneEnabled = true;
    return lock();
  }

  @NonNull
  @SuppressWarnings("unchecked")
  protected final T selfOrThrowIfLocked() {
    if (isLocked) {
      throw new IllegalStateException("You cannot modify locked T, consider clone()");
    }
    return self();
  }

  protected final boolean isAutoCloneEnabled() {
    return isAutoCloneEnabled;
  }

  public final boolean isDiskCacheStrategySet() {
    return isSet(DISK_CACHE_STRATEGY);
  }

  public final boolean isSkipMemoryCacheSet() {
    return isSet(IS_CACHEABLE);
  }

  @NonNull
  public final Map<Class<?>, Transformation<?>> getTransformations() {

View on GitHub (pinned to eb14a895d8)

Solutions

  1. Call .clone() before mutating: sharedOptions.clone().placeholder(drawable)
  2. Build options fresh per load, or treat stored options as an immutable template and always clone-then-modify
  3. If you want transparent cloning, enable autoClone() on a fresh unlocked instance before first use
  4. Stop reusing the same RequestOptions instance across long-lived requests

Example fix

// before
val opts = RequestOptions().centerCrop()
fun bind(url: String, error: Drawable) {
  opts.error(error) // throws on second call: opts locked after first load
  Glide.with(this).load(url).apply(opts).into(iv)
}

// after
val template = RequestOptions().centerCrop()
fun bind(url: String, error: Drawable) {
  Glide.with(this).load(url).apply(template.clone().error(error)).into(iv)
}
Defensive patterns

Strategy: validation

Validate before calling

fun RequestOptions.mutateSafe(block: RequestOptions.() -> Unit): RequestOptions =
  clone().apply(block)

Prevention

When it happens

Trigger: Reusing a RequestOptions instance across two loads where the first load's apply()/into() locked it, then calling a setter on the same instance before the second load. Also reachable via RequestOptions that were cached in a field and mutated after first use.

Common situations: Storing RequestOptions in a companion/field for reuse and then calling .placeholder()/.error() conditionally per request; chaining a setter on the result of into() (which returns the Target, not options, but mis-assumptions cause reuse); mutating options returned by a RequestBuilder.

Related errors


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