title: reactor.core.Exceptions$OverflowException: Can't deliver value due to lack of requests
body: <p>We've got a pretty complicated reactive spring application that processes requests from SQS and we've recently run into the following error in production:</p>
  <pre><code>2020-11-05 13:01:07.268 ERROR [reactive-multitenant-bridge,,,] 6 --- [llEventLoop-4-1] c.c.multibridge.sqs.SqsGateway           : error in sqs gateway foo:REGISTRATION
  
  reactor.core.Exceptions$OverflowException: Can't deliver value due to lack of requests
      at reactor.core.Exceptions.failWithOverflow(Exceptions.java:234) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:340) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:180) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:126) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:147) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:152) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.deferredComplete(FluxUsingWhen.java:402) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxUsingWhen$CommitInner.onComplete(FluxUsingWhen.java:536) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:81) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:816) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:600) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:580) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:457) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxArray$ArraySubscription.slowPath(FluxArray.java:137) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:99) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:363) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMerge.subscribe(FluxMerge.java:69) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onComplete(FluxUsingWhen.java:394) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:838) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:600) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapMain.innerComplete(FluxFlatMap.java:909) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxFlatMap$FlatMapInner.onComplete(FluxFlatMap.java:1013) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$BaseSink.complete(FluxCreate.java:438) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:784) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$BufferAsyncSink.complete(FluxCreate.java:732) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$SerializedSink.drainLoop(FluxCreate.java:239) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$SerializedSink.drain(FluxCreate.java:205) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.FluxCreate$SerializedSink.complete(FluxCreate.java:196) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at io.lettuce.core.ScanStream$SubscriptionAdapter.chunkCompleted(ScanStream.java:375) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.ScanStream$ScanSubscriber.hookOnComplete(ScanStream.java:518) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at reactor.core.publisher.BaseSubscriber.onComplete(BaseSubscriber.java:197) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:96) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:77) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
      at io.lettuce.core.RedisPublisher$ImmediateSubscriber.onNext(RedisPublisher.java:885) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.RedisPublisher$RedisSubscription.onNext(RedisPublisher.java:278) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.RedisPublisher$SubscriptionCommand.complete(RedisPublisher.java:753) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
      at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
      at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
      at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
      at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
      at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
  </code></pre>
  <p>This happens during a large spike of requests on a given request queue, but only sometimes, and not in any kind of repeatable way. I've been attempting to reproduce the error in a local development environment, and I've seen the error occur 3 times out of around 50 nearly identical tests. I've searched a bit and found some information indicating that this error occurs when using DirectProcessor when a publisher publishes n events and the consumer requests &lt; n, due to DirectProcessor not supporting backpressure. This information doesn't really help me figure the issue out, since I'm not sure where DirectProcessor is used in my application, and the stacktrace doesn't help me identify which code is the publisher and which code is the consumer or how this problem is occurring.</p>
  <p>High-level description of how the application is intended to work:
  On startup, the application starts pollers for several thousand SQS request queues. As the pollers receive messages, they're dispatched to message handlers make various REST and SOAP calls based on the particular gateway (gateway refers to the poller and consumer and the SQS request and response queue pair). Before a message handler dispatches the REST or SOAP call, it increments a counter in a redis hash so we can track inflight request count, and after the call finishes, we track the duration of the call and maintain an EWMA running average response time for the gateway, as well as a worker pool count that can grow or shrink within bounds based on a response time setpoint. On each poller loop, we query redis for inflight request count and the worker pool size and calculate the number of messages to request from SQS. When the number of messages to request is 0, we block on a redis pubsub channel for an inflight request to finish. The entire application is non-blocking, including SQS calls, redis calls, and the REST and SOAP calls made by message handlers.</p>
  <p>Main poller loop (SqsGateway.start):</p>
  <pre><code>public Flux&lt;U&gt; start() {
          log.info(&quot;Starting gateway {}&quot;, gatewayId);
          return Mono.defer(() -&gt; getSqsGatewayConfiguration()
                  .flatMap(sqsGatewayConfiguration -&gt;
                          Mono.defer(() -&gt; loadGatewayRuntimeStats(sqsGatewayConfiguration.getGatewayConfiguration()))
                                  .map(gatewayRuntimeStats -&gt; Tuples
                                          .of(sqsGatewayConfiguration, gatewayRuntimeStats,
                                                  calculateNumberOfMessagesToRequest(gatewayRuntimeStats)))
                                  .retryWhen(Retry.from(companion -&gt;
                                          companion.flatMap(retrySignal -&gt; {
                                              Throwable t = retrySignal.failure();
                                              if (t instanceof NoDemandException) {
                                                  return gatewayRuntimeStatsDao.waitForInflightRequestUpdate(gatewayId);
                                              } else {
                                                  //noinspection ReactiveStreamsThrowInOperator
                                                  throw Exceptions.propagate(t);
                                              }
                                          })
                                  ))
                  )
                  .flatMap(tuple -&gt; {
                      SqsGatewayConfiguration sqsGatewayConfiguration = tuple.getT1();
                      GatewayRuntimeStats gatewayRuntimeStats = tuple.getT2();
                      int numberOfMessagesToRequest = tuple.getT3();
                      return sqsService.receiveMessage(sqsGatewayConfiguration.getGatewayConfiguration()
                              .getRequestQueueName(), numberOfMessagesToRequest)
                              .map(receiveMessageResponse -&gt;
                                      Tuples.of(sqsGatewayConfiguration, gatewayRuntimeStats, receiveMessageResponse)
                              );
                  }))
                  .repeat()
                  .flatMap(
                          tuple -&gt; {
                              SqsGatewayConfiguration sqsGatewayConfiguration = tuple.getT1();
                              GatewayRuntimeStats stats = tuple.getT2();
                              ReceiveMessageResponse receiveMessageResponse = tuple.getT3();
  
                              return Flux.fromIterable(receiveMessageResponse.messages())
                                      .flatMap(m -&gt;
                                              handleMessage(sqsGatewayConfiguration, stats, m)
                                      );
                          }
                  )
                  .doOnError(e -&gt;
                          log.error(&quot;error in sqs gateway {}&quot;, gatewayId, e)
                  );
      }
  
  private int calculateNumberOfMessagesToRequest(GatewayRuntimeStats gatewayRuntimeStats) {
          int numberOfMessagesToRequest =
                  gatewayRuntimeStats.getWorkerPoolSize() - gatewayRuntimeStats.getInflightRequestCount();
          if (numberOfMessagesToRequest &lt; 1) {
              throw new NoDemandException();
          } else {
              return Math.min(10, numberOfMessagesToRequest);
          }
      }
  </code></pre>
  <p>message handling (SqsGateway.handleMessage):</p>
  <pre><code>private Mono&lt;U&gt; handleMessage(SqsGatewayConfiguration sqsGatewayConfiguration, GatewayRuntimeStats stats,
                                    Message m) {
          BridgeSettings bridgeSettings = sqsGatewayConfiguration.getBridgeSettings();
          GatewayConfiguration gatewayConfiguration = sqsGatewayConfiguration.getGatewayConfiguration();
          @SuppressWarnings(&quot;unchecked&quot;)
          SqsMessageHandler&lt;T, U&gt; sqsMessageHandler = (SqsMessageHandler&lt;T, U&gt;) sqsMessageHandlerFactory
                  .getSqsMessageHandler(gatewayConfiguration.getType(), bridgeSettings);
          try {
              T request = unmarshalMessage(m);
              try {
                  //noinspection BlockingMethodInNonBlockingContext
                  log.info(&quot;{}: message handler request {}&quot;, gatewayId, objectMapper.writeValueAsString(request));
              } catch (IOException e) {
                  log.warn(&quot;{}: error logging message handler request&quot;, gatewayId, e);
              }
              String instanceId = instanceUtils.getInstanceId();
  
              return gatewayRuntimeStatsDao.addInflightRequest(gatewayId, instanceId)
                      .then(
                              sqsMessageHandler.handleMessage(request)
                                      .elapsed()
                                      .flatMap(t -&gt; {
                                          try {
                                              Object response = t.getT2();
                                              try {
                                                  //noinspection BlockingMethodInNonBlockingContext
                                                  log.info(&quot;{}: message handler response: {}&quot;, gatewayId, objectMapper
                                                          .writeValueAsString(response));
                                              } catch (IOException e) {
                                                  log.warn(&quot;{}: error logging message handler response&quot;, gatewayId, e);
                                              }
                                              //noinspection BlockingMethodInNonBlockingContext
                                              return sqsService
                                                      .sendMessage(gatewayConfiguration
                                                              .getResponseQueueName(), objectMapper
                                                              .writeValueAsString(response)).thenReturn(t);
                                          } catch (IOException e) {
                                              return Mono.error(new RuntimeException(e));
                                          }
                                      })
                                      .flatMap(t -&gt;
                                              sqsService.deleteMessage(gatewayConfiguration.getRequestQueueName(), m
                                                      .receiptHandle()).thenReturn(t)
                                      )
                                      .flatMap(t -&gt;
                                              gatewayRuntimeStatsDao.removeInflightRequest(gatewayId,
                                                      instanceId).thenReturn(t)
                                      )
                                      .flatMap(t -&gt; {
                                          long elapsed = t.getT1();
                                          long nextRunningAverage = EwmaCalculator.getNextAverage(0.08,
                                                  stats.getRunningAverage(), elapsed);
                                          stats.setRunningAverage(nextRunningAverage);
                                          return gatewayRuntimeStatsDao.setRunningAverage(gatewayId,
                                                  nextRunningAverage)
                                                  .and(adjustWorkerPoolSize(sqsGatewayConfiguration, stats))
                                                  .and(publishResponseTimeStats(bridgeSettings,
                                                          elapsed))
                                                  .thenReturn(t.getT2());
                                      })
                      );
          } catch (Exception e) {
              log.error(&quot;{}: Error handling message&quot;, gatewayId, e);
              return sqsMessageHandler.getErrorResponse(getCorrelationId(m))
                      .flatMap(response -&gt; {
                          try {
                              try {
                                  //noinspection BlockingMethodInNonBlockingContext
                                  log.info(&quot;{}: message handler response: {}&quot;, gatewayId, objectMapper
                                          .writeValueAsString(response));
                              } catch (IOException innerException) {
                                  log.warn(&quot;{}: error logging message handler response&quot;, gatewayId, innerException);
                              }
                              //noinspection BlockingMethodInNonBlockingContext
                              return sqsService
                                      .sendMessage(gatewayConfiguration
                                              .getResponseQueueName(), objectMapper
                                              .writeValueAsString(response)).thenReturn(response);
                          } catch (IOException innerException) {
                              return Mono.error(new RuntimeException(innerException));
                          }
                      })
                      .flatMap(response -&gt;
                              sqsService.deleteMessage(gatewayConfiguration.getRequestQueueName(), m
                                      .receiptHandle()).thenReturn(response)
                      );
          }
      }
  </code></pre>
  <p>There's obviously a lot more code I'm leaving out of the post, but hopefully that's enough for someone to look at. We call subscribe on the flux returned from the gateway start method above and keep the disposables returned in a collection, and we have a scheduled task that periodically checks that the pollers that are expected to be running have not been disposed, as well as launch new ones that have been added via configuration while the application was running, and stop pollers that have been removed. When this issue occurs, the disposable remains and is not disposed, so the poller for that particular queue stops functioning and nothing is able to detect the problem and start it back up.</p>
  <p>Due to the presence of <code>at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:180) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]</code> in the stacktrace, I suspect the retryWhen in the SqsGateway.start method, but I've been testing like crazy and unable to figure out what the root cause could be. Any help here would be greatly appreciated!</p>
  <p>Edit: someone mentioned in comments that a hot source may be involved in the root cause. I use Mono.just in quite a few places, here's an example in a message handler:</p>
  <pre><code>@Override
      public Mono&lt;RegistrationResponse&gt; register(RegistrationRequest request) {
          SendToStudentCart igRequest = new SendToStudentCart();
          igRequest.setKEY(peoplesoftProperties.getAuthKey());
          igRequest.setEMPLID(request.getUsername());
          igRequest.setTERM(request.getTermCode());
          igRequest.setACTION(Action.ENROLL);
          try {
              List&lt;StudentCartItem&gt; items = request.getRegNumberRequests().stream().map(r -&gt; {
                  StudentCartItem item = new StudentCartItem();
                  if (r.getAction() == null) {
                      throw new IllegalArgumentException(&quot;RegNumberRequest action is required.&quot;);
                  }
                  item.setACTION(ItemAction.valueOf(r.getAction()));
                  item.setREGNUMBER(r.getRegNumber());
                  item.setACADCAREER(r.getAcademicCareerCode());               
                  return item;
              }).collect(Collectors.toList());
              igRequest.setITEMS(new SendToStudentCart.ITEMS());
              igRequest.getITEMS().getITEM().addAll(items);
          } catch (Exception e) {
              return Mono.error(e);
          }
  
          try {
              StringResult stringResult = new StringResult();
              marshaller.marshal(igRequest, stringResult);
              log.info(&quot;raw integration gateway request: {}&quot;, stringResult);
          } catch (JAXBException e) {
              throw new RuntimeException(e);
          }
  
          return Mono.create(sink -&gt; dispatch.invokeAsync(igRequest, ReactorAsyncHandler.into(sink))).map(igResponse -&gt; {
              try {
                  StringResult stringResult = new StringResult();
                  marshaller.marshal(igResponse, stringResult);
                  log.info(&quot;raw integration gateway response: {}&quot;, stringResult);
              } catch (JAXBException e) {
                  throw new RuntimeException(e);
              }
  
              if (igResponse instanceof SendToStudentCartResponse) {
                  SendToStudentCartResponse sendToStudentCartResponse = (SendToStudentCartResponse) igResponse;
                  RegistrationResponse response = new RegistrationResponse();
                  response.setUsername(request.getUsername());
                  response.setSubdomain(request.getSubdomain());
                  response.setTermCode(request.getTermCode());
  
                  Map&lt;String, RegNumberRequest&gt; regNumberRequestMap =
                          request.getRegNumberRequests().stream().collect(Collectors.toMap(RegNumberRequest::getRegNumber,
                                  r -&gt; r));
                  Map&lt;String, RegNumberResponse&gt; regNumberResponseMap = new HashMap&lt;&gt;();
                  if (sendToStudentCartResponse.getEnrollResults() != null) {
                      Consumer&lt;? super ClassResult&gt; action = consumeRegisterResponses(regNumberResponseMap,
                              regNumberRequestMap);
                      for (ClassResult classResult : sendToStudentCartResponse.getEnrollResults().getClazz()) {
                          action.accept(classResult);
                      }
                  }
                  response.setRegNumberResponses(new ArrayList&lt;&gt;(regNumberResponseMap.values()));
                  if (sendToStudentCartResponse.getCartCourses() != null) {
                      response.setCartCourses(sendToStudentCartResponse.getCartCourses().getCartCourse().stream()
                              .map(this::convertCartCourse).collect(Collectors.toList()));
                  }
                  if (sendToStudentCartResponse.getCurrentScheduleSections() != null) {
                      response.setRegisteredCourses(
                              sendToStudentCartResponse.getCurrentScheduleSections().getCurrentScheduleSection().stream()
                                      .map(this::convertRegisteredCourse).collect(Collectors.toList()));
                  }
                  return response;
              } else if (igResponse instanceof Exceptions) {
                  Exceptions exceptions = (Exceptions) igResponse;
                  log.error(&quot;Got exception response from register psoft interface call&quot;);
                  if (exceptions.getException() != null) {
                      for (String s : exceptions.getException()) {
                          log.error(s);
                      }
                  }
                  RegistrationResponse response = new RegistrationResponse();
                  response.getErrors()
                          .add(new RegistrationError(RegistrationErrorType.UNKNOWN, &quot;An unexpected error has occurred. &quot; +
                                  &quot;Please contact your administrator.&quot;));
                  return response;
              } else {
                  RegistrationResponse response = new RegistrationResponse();
                  response.getErrors().add(new RegistrationError(RegistrationErrorType.UNKNOWN, &quot;unrecognized response&quot;));
                  return response;
              }
          })
                  .onErrorResume(e -&gt; e instanceof WebServiceException, e -&gt; {
                      log.error(&quot;Registration Exception&quot;, e);
                      if (e.getCause() != null &amp;&amp; (e.getCause() instanceof IOException)) {
                          RegistrationResponse response = new RegistrationResponse();
                          response.getErrors()
                                  .add(new RegistrationError(RegistrationErrorType.SIS_UNAVAILABLE, &quot;Error contacting &quot; +
                                          &quot;peoplesoft registration service. Please contact your administrator.&quot;));
                          return Mono.just(response);
                      } else {
                          return Mono.error(e);
                      }
                  });
      }
  </code></pre>
  <p>Note the onErrorResume call, if the JAX-WS dispatch client throws an IOException, I'm emitting an error response using Mono.just. Is that a problem?</p>