PC パソコン

Android Studio: Fix “expected to be within a catch-all for an instruction where a monitor is held”

A release APK can build successfully and still crash when ART loads a class from an AAR that was already optimized by R8 and then processed again by the app’s R8 build. This article explains the runtime failure expected to be within a catch-all for an instruction where a monitor is held, the affected configurations reported to Google, and the safest mitigations available while the R8 issue remains unresolved.

Representative error

java.lang.VerifyError: Verifier rejected class ...
... [0x34] expected to be within a catch-all for an instruction
where a monitor is held
(declaration of '...' appears in .../base.apk)

This is not a compilation failure. It occurs when the affected class is first verified on a device, so a successful release build alone does not prove that the output is valid at runtime.

Cause

In the reported case, Kotlin code performed a check and created an error message inside synchronized. An older R8 first optimized that code into a minified AAR, and a newer R8 processed the AAR again while building the app. In the resulting DEX, a const-string instruction executed while the monitor was held but sat outside a catch-all handler. ART requires a catch-all path that can release a held monitor if a potentially throwing instruction fails, so it rejects the class.

Current status: the malformed exception-range gap was already visible in the producer AAR, but the exact producer/consumer optimization combination responsible for the final failure has not been isolated. As of October 2, 2026, Issue #559396690 is Assigned. Google has not published a fix or a fixed AGP/R8 version.

Affected setup and tested versions

  • Build a library as a minified release AAR.
  • Consume that AAR in an app whose release build also has minifyEnabled true.
  • Run the app and instantiate or invoke the affected library component.

The representative producer used AGP 4.2.2, R8 2.2.71, Kotlin 1.6.0, and JDK 11. The representative consumer used AGP 8.13.2, R8 8.13.19, JDK 17, and minSdk 23. The failure reproduced on Android 6/API 23 and Android 14 hardware or emulation.

With the same AAR, release apps built with consumer AGP 4.2.2, 7.4.2, and 8.1.4 passed, while AGP 8.13.2 and 9.0.1 failed. The first affected version is not known. Replacing only the consumer R8 with 8.13.23 or 9.4.17 did not eliminate the crash in the reported setup.

Recommended resolution path

1. Determine whether the AAR is already minified

If you own the library, inspect its release build type. If it is a third-party artifact, ask the vendor whether it was optimized before publication. Do not assume the app-level R8 pass is the only relevant transformation.

2. Prefer an unminified AAR with consumer rules

Android’s library guidance states that a normal AAR does not need a separate shrinking step. Package the rules required by consumers through consumerProguardFiles, and let the final app build perform whole-program optimization once.

android {
    defaultConfig {
        consumerProguardFiles("consumer-rules.pro")
    }
    buildTypes {
        release {
            isMinifyEnabled = false
        }
    }
}

This avoids feeding bytecode previously transformed by an older R8 into a second R8 optimization pass.

3. If you own the code, move potentially throwing work outside synchronized

The reporter moved the failing check and message construction outside synchronized, while leaving state checks, registration, and active-work counter changes under the original lock. The representative Android 14 manual flow then completed with the original consumer R8 8.13.19.

// Conceptual only: re-check invariants and race behavior in your code.
val error = validateAndBuildMessage(input)
synchronized(lock) {
    checkStateWithoutAllocatingMessage(error)
    register(input)
}

This is a reporter-tested workaround, not a Google-confirmed fix. The full version matrix was not rerun, and changing a critical section can introduce races. Review the invariants protected by the lock before adopting it.

4. For third-party AARs, request an unminified or corrected build

If the problematic bytecode is inside a dependency, provide the vendor with the full stack trace, producer and consumer AGP/R8 versions if known, affected devices, and the exact minification conditions. Disabling app shrinking can be a useful diagnostic, but it changes size and optimization behavior and should not automatically become the permanent production configuration.

5. Capture an R8 compiler-input dump

Google requested an R8 compiler dump because it contains the inputs and keep rules required to reproduce the compilation.

mkdir -p /tmp/r8
./gradlew assembleRelease   -Dcom.android.tools.r8.dumpinputtodirectory=/tmp/r8   --no-daemon

The dump may contain proprietary code and configuration. Inspect it and use the private sharing route named by the issue owner rather than attaching it publicly.

Alternatives and pitfalls

  • Diagnostic: temporarily disable shrinking on either the producer or consumer side to confirm that the double-optimization path is involved.
  • Temporary fallback: use a consumer AGP version that passes your own release-device tests. The first affected version is unknown, so the issue’s comparison is not a universal compatibility guarantee.
  • Avoid: adding broad package-wide -keep rules as a permanent fix. They can damage app size and optimization and may not repair malformed exception ranges already present in the AAR.
  • Do not assume: merely overriding the consumer R8 to 8.13.23 or 9.4.17 fixes the issue; both still failed in the official report.

How to verify the mitigation

  1. Clean-build both the release AAR and the release app.
  2. Confirm that the intended AAR is packaged into the APK or AAB.
  3. Install the minified release output on at least API 23 and API 34-equivalent environments from the reported matrix.
  4. Exercise the path that first loads the affected class.
  5. Verify that Logcat contains neither VerifyError nor the catch-all/monitor diagnostic.
  6. If the synchronized region changed, add concurrency and stress tests.

A release smoke test that installs the minified artifact and reaches the affected code is more useful than a CI check that stops after compilation.

Summary

This failure occurs when an AAR already transformed by R8 is processed again and the final DEX lacks required catch-all protection for an instruction executed while a monitor is held. There is no published fixed version yet. Prefer distributing an unminified AAR with consumer rules, carefully restructure the synchronized code only when you own and can test it, and verify the result on release builds. If the failure remains, collect an R8 input dump and contribute it through the official issue’s private channel.

Official references