Blankj/AndroidUtilCode · error · IllegalArgumentException

<" + className + "> and <" + apiInfo.implApiClass + "> impl

Error message

<" + className + "> and <" + apiInfo.implApiClass + "> impl same api of <" + superClassName + ">

What it means

The api-gradle-plugin scans bytecode for classes carrying @ApiUtils.Api and maps each BaseApi subclass to a single real implementation. ApiUtils is designed for exactly one non-mock impl per API. In ApiClassVisitor.visitEnd(), if a second non-mock (isMock == false) implementation of the same BaseApi subclass is found, it throws IllegalArgumentException naming both conflicting classes and their common API.

Source

Thrown at plugin/api-gradle-plugin/src/main/java/com/blankj/api/ApiClassVisitor.java:70

                public void visit(String name, Object value) {// 可获取注解的值
                    isMock = (boolean) value;
                    super.visit(name, value);
                }
            };
        }
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitEnd() {
        super.visitEnd();
        if (hasAnnotation) {
            if (!isMock) {// 如果不是 mock 的话
                ApiInfo apiInfo = mApiImplMap.get(superClassName);
                if (apiInfo == null || apiInfo.isMock) {// 不存在或者之前存在的是 mock
                    mApiImplMap.put(superClassName, new ApiInfo(className, false));
                } else {// 存在一个 api 多个非 mock 实现就报错
                    throw new IllegalArgumentException("<" + className + "> and <" + apiInfo.implApiClass + "> impl same api of <" + superClassName + ">");
                }
            } else {// mock 的话,如果 map 中已存在就不覆盖了
                if (!mApiImplMap.containsKey(superClassName)) {
                    mApiImplMap.put(superClassName, new ApiInfo(className, true));
                }
            }
        }
    }
}

View on GitHub (pinned to 7b4caf9e54)

Solutions

  1. Designate one of the two conflicting classes as a mock via @ApiUtils.Api(isMock = true); the plugin then keeps only the single non-mock impl.
  2. Delete or exclude the duplicate non-mock impl from the build variant that triggered the scan.
  3. Keep mock impls in a dedicated mock source set so they are not compiled into full/release builds.

Example fix

// before - two real impls of MainApi (isMock defaults to false) -> conflict
@ApiUtils.Api
public class MainApiImpl extends MainApi { /* ... */ }

@ApiUtils.Api
public class MainApiDebugImpl extends MainApi { /* ... */ }

// after - mark the debug one as mock so only one real impl remains
@ApiUtils.Api(isMock = true)
public class MainApiDebugImpl extends MainApi { /* ... */ }
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Two classes both annotated @ApiUtils.Api (isMock defaults to false) extend the same XxxApi (which extends ApiUtils.BaseApi), and both are compiled into the app transform scope scanned by the plugin.

Common situations: Forgetting to mark a debug/stub impl with isMock = true; copy-pasting an impl class; two modules each ship a real impl of a shared API; a mock impl left compiled into the full/release build variant.

Related errors


AI-assisted analysis of Blankj/AndroidUtilCode@7b4caf9e54 (2026-08-14). Data as JSON: /api/errors/658163167b0db3cb. Report an issue: GitHub.