OpenFeign/feign · error · IOException

failed to open GraphQL subscription to

Error message

failed to open GraphQL subscription to ${request.url()}

What it means

GraphqlSubscriptionClient opens a WebSocket for a GraphQL subscription via java.net.http.WebSocket. If the asynchronous attach/handshake fails (unreachable server, TLS error, rejected upgrade, timeout), the CompletionException is unwrapped and rethrown as an IOException naming the request URL.

Solutions

  1. Verify the server supports GraphQL subscriptions (WebSocket upgrade) at that URL and the scheme is ws:// or wss://
  2. Check proxies/load balancers allow the WebSocket Upgrade headers
  3. Inspect e.getCause() on the IOException for the underlying CompletionException/ConnectException
  4. Confirm network reachability (connectivity test to host:port) and TLS certificate validity

Example fix

// before
GraphQL client = GraphQL.builder()
    .decoder(decoder).target("https://api.example.com/graphql").build();
client.subscriptions().execute(subscriptionRequest);
// after: ensure the URL/endpoint supports ws upgrades and handle the IOException
try {
  client.subscriptions().execute(subscriptionRequest);
} catch (IOException e) {
  log.error("subscription setup failed: " + e.getCause(), e);
}
Defensive patterns

Strategy: retry

Validate before calling

URI uri = URI.create(request.url());
if (!uri.getScheme().matches("wss?"))
  throw new IllegalArgumentException("subscription URL must use ws/wss: " + uri);

Try / catch

try {
  client.subscriptions().execute(req);
} catch (IOException e) {
  if (e.getCause() instanceof CompletionException || e.getMessage().contains("failed to open")) {
    // retry with backoff or surface a connectivity problem
    retryWithBackoff(req);
  } else throw e;
}

Prevention

When it happens

Trigger: subscribe()/execute() called and subscription.attach(...).join() completes exceptionally: server does not accept WebSocket upgrade at the URL, network/TLS failure, wrong scheme (http instead of ws), or the join() future times out.

Common situations: GraphQL endpoint does not support subscriptions, a reverse proxy blocks WebSocket upgrades, the URL points at a plain HTTP route, firewall blocks ws/wss traffic, server down.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10). Data as JSON: /api/errors/fb1e6039e26e0807. Report an issue: GitHub.

Appendix: source

Thrown at graphql/src/main/java/feign/graphql/GraphqlSubscriptionClient.java:128

    var builder = httpClient.newWebSocketBuilder().subprotocols("graphql-transport-ws");
    if (options != null && options.connectTimeoutMillis() > 0) {
      builder.connectTimeout(Duration.ofMillis(options.connectTimeoutMillis()));
    }
    request
        .headers()
        .forEach(
            (name, values) -> {
              if (isForwardable(name)) {
                values.forEach(value -> builder.header(name, value));
              }
            });

    try {
      subscription.attach(builder.buildAsync(webSocketUri(request.url()), subscription).join());
    } catch (CompletionException e) {
      var cause = e.getCause() == null ? e : e.getCause();
      throw new IOException("failed to open GraphQL subscription to " + request.url(), cause);
    }

    // 204 keeps feign's logger from draining and replacing the body, which would drop the live
    // subscription. Nothing here ever crosses the wire.
    return Response.builder()
        .status(HttpURLConnection.HTTP_NO_CONTENT)
        .reason("Subscribed")
        .request(request)
        .headers(Collections.emptyMap())
        .body(subscription)
        .build();
  }

  /** Headers the JDK WebSocket handshake rejects or manages itself. */
  private static boolean isForwardable(String header) {
    var name = header.toLowerCase(Locale.ROOT);
    return !name.equals("connection")
        && !name.equals("upgrade")

View on GitHub (pinned to e2a1e27560)