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
- 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.
- Delete or exclude the duplicate non-mock impl from the build variant that triggered the scan.
- 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
- Enforce one real (non-mock) impl per BaseApi subclass across the whole app.
- Always annotate stub/debug implementations with @ApiUtils.Api(isMock = true).
- Isolate mock impls in a mock source set so they never ship alongside the real impl.
- Name impl classes distinctly and grep for duplicate `extends XxxApi` before merging.
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
- u should impl these apis: " + noImplApis + "\n u can check i
- No ApiUtils of ${apiUtilsClass} in $mProject.
- ApiExtension's apiUtilsClass is empty.
- These buses is not right: " + wrongBus + "\n u can check it
- No BusUtils of ${ext.busUtilsClass} in $mProject.
AI-assisted analysis of Blankj/AndroidUtilCode@7b4caf9e54 (2026-08-14).
Data as JSON: /api/errors/658163167b0db3cb.
Report an issue: GitHub.