grpc/grpc-java · error · IllegalStateException
ScheduledExecutorService not set in Builder
Error message
ScheduledExecutorService not set in Builder
What it means
NameResolver.Args.getScheduledExecutorService throws IllegalStateException when no ScheduledExecutorService was provided to the Args.Builder before this getter is called. The field is optional at build time but required at read time, so the accessor enforces the invariant. It signals that the caller (typically a NameResolver implementation or a NameResolverProvider wiring path) expects an executor the builder never set.
Source
Thrown at api/src/main/java/io/grpc/NameResolver.java:425
public SynchronizationContext getSynchronizationContext() {
return syncContext;
}
/**
* Returns a {@link ScheduledExecutorService} for scheduling delayed tasks.
*
* <p>This service is a shared resource and is only meant for quick tasks. DO NOT block or run
* time-consuming tasks.
*
* <p>The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()}
* and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called.
*
* @since 1.26.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
public ScheduledExecutorService getScheduledExecutorService() {
if (scheduledExecutorService == null) {
throw new IllegalStateException("ScheduledExecutorService not set in Builder");
}
return scheduledExecutorService;
}
/**
* Returns the {@link ServiceConfigParser}.
*
* @since 1.21.0
*/
public ServiceConfigParser getServiceConfigParser() {
return serviceConfigParser;
}
/**
* Returns the value of a custom arg named 'key', or {@code null} if it's not set.
*
* <p>While ordinary {@link Args} should be universally useful and meaningful, custom arguments
* can apply just to resolvers of a certain URI scheme, just to resolvers producing a particularView on GitHub (pinned to 64daddc1f3)
Solutions
- Call .scheduledExecutorService(executor) on the Args.Builder before building — e.g. pass the channel's or a shared ScheduledThreadPoolExecutor.
- When implementing a NameResolver, avoid depending on the executor if args may lack it, or check availability first and provide a default executor.
- If you control provider code, update it to copy all fields from existing Args (including scheduledExecutorService) when rebuilding/wrapping args.
- Verify grpc-java version alignment: NameResolverProvider signatures changed in 1.26+; rebuild custom providers against the current API.
Example fix
// before
NameResolver.Args args = NameResolver.Args.newBuilder()
.setDefaultPort(443)
.setServiceConfigParser(parser)
.build();
// after
NameResolver.Args args = NameResolver.Args.newBuilder()
.setDefaultPort(443)
.setServiceConfigParser(parser)
.setScheduledExecutorService(sharedScheduler)
.build(); Defensive patterns
Strategy: try-catch
Validate before calling
NameResolver.Args.Builder b = NameResolver.Args.newBuilder()...; if (b != null) { /* ensure setScheduledExecutorService was called before build() */ } Type guard
boolean hasScheduler(NameResolver.Args args) { try { return args.getScheduledExecutorService() != null; } catch (IllegalStateException e) { return false; } } Try / catch
ScheduledExecutorService ses; try { ses = args.getScheduledExecutorService(); } catch (IllegalStateException e) { ses = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r, "name-resolver"); t.setDaemon(true); return t; }); } Prevention
- Always call setScheduledExecutorService on Args.Builder before build()
- When wrapping/copying Args, copy every field including the executor
- Rebuild custom NameResolverProvider against the current grpc-java API
- Prefer supplying a shared daemon scheduler rather than relying on defaults
When it happens
Trigger: Calling getScheduledExecutorService() on NameResolver.Args built without builder.scheduledExecutorService(...). Happens in custom NameResolver/NameResolverProvider implementations that read the executor, or when older provider/wrapper code (wrap, newNameResolver) builds Args without the executor.
Common situations: Upgrading grpc-java: NameResolverProvider APIs changed and custom providers construct Args without setting the executor; copying a NameResolver.Args builder but omitting scheduledExecutorService; a library's NameResolver assuming the executor exists on older args instances.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Metric with name ${name} already exists
- ChannelLogger is not set in Builder
- NameResolverRegistry is not set in Builder
- No method bound for descriptor entry ${fullMethodName}
- cannot create a NameResolver for ${targetUri}
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/a8c09f7e853c403f.
Report an issue: GitHub.