@@ -100,6 +100,78 @@ The processor generates a record for the input type as well:
100100public record CreateUserInput(String name, String email) {}
101101```
102102
103+ ## Subscriptions
104+
105+ ` subscription ` operations are detected from the query text and executed over the
106+ [ graphql-transport-ws] ( https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md ) WebSocket
107+ protocol instead of HTTP. The endpoint is the target URL with its scheme swapped to ` ws ` /` wss ` , and
108+ one connection is opened per call.
109+
110+ Queries, mutations and subscriptions can live on the same interface: only subscriptions are routed
111+ to a WebSocket, everything else goes over the regular Feign client — including whichever one you
112+ configured with ` .client(...) ` — with its own timeouts, retryer and interceptors unchanged.
113+
114+ The return type decides how many events you get and whether the call blocks:
115+
116+ | Return type | Events | Behaviour |
117+ | --- | --- | --- |
118+ | ` T ` | first only | blocks until the first event, then unsubscribes |
119+ | ` Optional<T> ` | first only | as above, empty if the server completes without one |
120+ | ` CompletableFuture<T> ` | first only | returns immediately, completes with the first event |
121+ | ` Stream<T> ` | all | returns once subscribed, then blocks on each element |
122+ | ` Flow.Publisher<T> ` | all | returns immediately, elements are pushed to the subscriber |
123+
124+ ``` java
125+ @GraphqlSchema (" my-schema.graphql" )
126+ interface StockApi {
127+
128+ @GraphqlQuery (" subscription($symbol: String!) { priceChanged(symbol: $symbol) { symbol price } }" )
129+ Price nextPrice (@Param (" symbol" ) String symbol );
130+
131+ @GraphqlQuery (" subscription($symbol: String!) { priceChanged(symbol: $symbol) { symbol price } }" )
132+ Stream<Price > onPrice (@Param (" symbol" ) String symbol );
133+
134+ @GraphqlQuery (" subscription($symbol: String!) { priceChanged(symbol: $symbol) { symbol price } }" )
135+ Flow .Publisher<Price > publishPrice (@Param (" symbol" ) String symbol );
136+ }
137+ ```
138+
139+ The single-event forms close the subscription as soon as they have their event. The multi-event
140+ forms hand you the lifecycle: closing the ` Stream ` — or cancelling the ` Flow.Subscription ` — sends
141+ ` complete ` and closes the WebSocket, so consume a ` Stream ` with try-with-resources:
142+
143+ ``` java
144+ try (var prices = api. onPrice(" ACME" )) {
145+ prices. forEach(System . out:: println);
146+ }
147+ ```
148+
149+ ` Stream ` here is the ordinary ` java.util.stream.Stream ` : synchronous and pull-based, with no timeout
150+ facilities of its own. So the blocking forms — ` T ` , ` Optional<T> ` and ` Stream<T> ` — are bounded by
151+ an event timeout, which defaults to ** 60 seconds** and applies to each event rather than to the
152+ subscription as a whole. Override it when creating the capability:
153+
154+ ``` java
155+ Feign . builder()
156+ // wait at most 5s for each event; Duration.ZERO waits indefinitely
157+ .addCapability(new GraphqlCapability (new JacksonCodec (), Duration . ofSeconds(5 )))
158+ .target(StockApi . class, " https://example.com/graphql" );
159+ ```
160+
161+ Exceeding it raises ` SocketTimeoutException ` from the blocking call or the stream element. A
162+ subscription that can legitimately sit idle for longer needs ` Duration.ZERO ` .
163+
164+ ` Flow.Publisher<T> ` and ` CompletableFuture<T> ` are deliberately * not* bounded by it — their caller
165+ already owns the deadline, via cancelling the subscription or
166+ ` get(timeout, unit) ` /` orTimeout(...) ` .
167+
168+ ` Flow.Publisher ` is ` java.util.concurrent.Flow.Publisher ` , so it plugs into Reactor
169+ (` JdkFlowAdapter.flowPublisherToFlux ` ) or RxJava (` Flowable.fromPublisher ` ) without extra
170+ dependencies here.
171+
172+ A server ` error ` message, or ` errors ` inside a payload, is raised as ` GraphqlErrorException ` .
173+ Request headers (for example ` Authorization ` ) are forwarded to the WebSocket handshake.
174+
103175## Custom Scalars
104176
105177When your schema defines custom scalars, map them to Java types using ` @Scalar ` on default methods:
0 commit comments