grpc/grpc-java · error · IllegalStateException
The transport factory is closed.
Error message
The transport factory is closed.
What it means
CronetChannelBuilder's internal transport factory is closed when the builder's built channel is shut down; after that, any attempt to create a new client transport through the same factory throws IllegalStateException. This is a lifecycle guard preventing transport creation on a torn-down builder/channel.
Source
Thrown at cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java:276
boolean useGetForSafeMethods,
boolean usePutForIdempotentMethods) {
usingSharedScheduler = timeoutService == null;
this.timeoutService = usingSharedScheduler
? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService;
this.maxMessageSize = maxMessageSize;
this.alwaysUsePut = alwaysUsePut;
this.streamFactory = streamFactory;
this.executor = Preconditions.checkNotNull(executor, "executor");
this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
this.useGetForSafeMethods = useGetForSafeMethods;
this.usePutForIdempotentMethods = usePutForIdempotentMethods;
}
@Override
public ConnectionClientTransport newClientTransport(
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
if (closed) {
throw new IllegalStateException("The transport factory is closed.");
}
InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
return new CronetClientTransport(streamFactory, inetSocketAddr, options.getAuthority(),
options.getUserAgent(), options.getEagAttributes(), executor, maxMessageSize,
alwaysUsePut, transportTracer, useGetForSafeMethods, usePutForIdempotentMethods);
}
@Override
public ScheduledExecutorService getScheduledExecutorService() {
return timeoutService;
}
@Override
public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials channelCreds) {
return null;
}
@OverrideView on GitHub (pinned to 64daddc1f3)
Solutions
- Create a new channel via CronetChannelBuilder.forAddress(...) instead of reusing the closed one.
- Ensure all RPCs are issued before calling channel shutdown, or keep the channel open as long as it is used.
- Guard concurrent shutdown vs. new-call creation with application-level synchronization or an atomic 'closed' check.
Example fix
// before channel.shutdown(); stub.someRpc(request); // IllegalStateException // after stub.someRpc(request); channel.shutdown();
Defensive patterns
Strategy: validation
Validate before calling
if (channel.isShutdown() || channel.isTerminated()) { channel = CronetChannelBuilder.forAddress(host, port, engine).build(); } Try / catch
try { stub.call(req); } catch (IllegalStateException e) { if (e.getMessage().contains("transport factory is closed")) { channel = rebuildChannel(); } else { throw e; } } Prevention
- Never reuse a channel after shutdown()
- Check channel.isShutdown() before issuing RPCs
- Manage channel lifecycle in a single owner component
When it happens
Trigger: Calling newClientTransport (e.g. by opening a new RPC via the channel's transport) after the CronetChannelBuilder's transport factory has been closed — typically after channel shutdown.
Common situations: Reusing a channel (or its builder) after shutdown() to issue more RPCs; a race where shutdown happens while a new call is being created.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The transport factory is closed.
- Configurators are already set
- Metric with name ${name} already exists
- ScheduledExecutorService not set in Builder
- ChannelLogger is not set in Builder
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/99a72674ed74f149.
Report an issue: GitHub.