grpc/grpc-java · error · IllegalArgumentException
TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R a
Error message
TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility
What it means
IntentNameResolver resolves targets in a different Android user profile via TARGET_ANDROID_USER NameResolver.Arg. Resolving a target user requires Context.createContextAsUser, which is only available as @SystemApi from Android SDK_INT >= R (30). When reflection to invoke that hidden API fails, the resolver throws this IllegalArgumentException rather than silently ignoring the target user.
Source
Thrown at binder/src/main/java/io/grpc/binder/internal/IntentNameResolver.java:104
Context context =
checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), "SOURCE_ANDROID_CONTEXT")
.getApplicationContext();
this.targetUserContext =
targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;
// This Executor is nominally optional but all grpc-java Channels provide it since 1.25.
this.offloadExecutor =
checkNotNull(args.getOffloadExecutor(), "NameResolver.Args.getOffloadExecutor()");
// Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.
this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);
this.syncContext = args.getSynchronizationContext();
this.serviceConfigParser = args.getServiceConfigParser();
}
private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {
try {
return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException(
"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility");
}
}
@Override
public void start(Listener2 listener) {
checkState(this.listener == null, "Already started!");
checkState(!shutdown, "Resolver is shutdown");
this.listener = checkNotNull(listener);
sequentialExecutor.execute(this::registerReceiver);
resolve();
}
@Override
public void refresh() {
checkState(listener != null, "Not started!");
resolve();
}View on GitHub (pinned to 64daddc1f3)
Solutions
- Only set a TARGET_ANDROID_USER arg when Build.VERSION.SDK_INT >= Build.VERSION_CODES.R and the app has system/privileged visibility.
- Fall back to resolving within the current user (omit the target-user arg) when the platform requirement is unmet.
- If cross-user is required, run on API 30+ with a privileged (system-signed, PRIVILEGED_SIGNATURE) install.
Example fix
// before
resolverArgsBuilder.setNameResolverArg(IntentNameResolver.TARGET_ANDROID_USER, otherUserHandle);
// after
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && isPrivilegedApp()) {
resolverArgsBuilder.setNameResolverArg(IntentNameResolver.TARGET_ANDROID_USER, otherUserHandle);
} else {
// resolve in current user
} Defensive patterns
Strategy: validation
Validate before calling
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !isSystemVisible()) {
throw new IllegalStateException("TARGET_ANDROID_USER requires SDK_INT >= R and @SystemApi visibility");
} Try / catch
try {
resolver.start(listener);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("TARGET_ANDROID_USER")) { /* fall back to current user resolution */ }
} Prevention
- Gate TARGET_ANDROID_USER usage on Build.VERSION.SDK_INT >= 30.
- Only use in system/privileged apps; check signing/privileges at startup.
- Provide a same-user fallback resolution path.
When it happens
Trigger: Using IntentNameResolver with a NameResolver.Arg targeting a different Android user on a device running SDK_INT < 30 (pre-R), or on a build where the app lacks @SystemApi / system-privilege visibility so createContextAsUser throws ReflectiveOperationException.
Common situations: Running a cross-user gRPC-binder app on Android 10 or older; shipping a consumer (non-system / non-privileged) APK that attempts TARGET_ANDROID_USER resolution; ROM or SDK-version changes that remove API visibility.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- call forAddress(AndroidComponentAddress, Context) instead
- call forAddress() instead
- TLS not supported in BinderServer
- The transport factory is closed.
- BinderProxy#transact(code, FLAG_ONEWAY) returned false
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/1d56d0fdd2ad755a.
Report an issue: GitHub.