Spring Cloud Stream 503 Max Publishing Flow Exceeded

Hi! We’re using Spring Cloud Stream with Solace and we ran into a 503 Max Router Publishing Flow Exceeded error. I’m trying to understand may have gone wrong and how we should do it right.

Observed error
The logs go roughly like this when error occurs:

Creating producer to TOPIC my/topic/67 <message handler ID: xxx...>

Client-67-420..: Error Response [503 Max Router Publishing Flow Exceeded], txSessionId=-1, flowName=...

Unable to get a message producer for session JCSMPSession
com.solacesystems.jcsmp.JCSMPErrorResponseException: 503: Max Router Publishing Flow Exceeded
  at com.solacesystems.jcsmp.impl.flow.PubFlowManager.doPubAssuredCtrl(PubFlowManager.java:355)
  at com.solacesystems...

Stopping producer to TOPIC my/topic/67 <message handler ID: xxx...>
xxx... is not the last user, persisting producer...

Failed to create producer binding; retrying in 30 seconds

java.lang.RuntimeException: Unable to get a message producer for session JCSMPSession...

Trying to unbind 'my-binder:my/topic/59', but no binding found.
Could not send event: 64883 due to exception: Dispatcher has no subscribers
for channel 'myapp.my/topic/67'.

Broker settings
maxIngressFlowCount = 100 per client connection.

Temporary fix
Restarting the pod restores publishing but I suspect it is temporary.

Event flow
The application gets messages through functional bindings, does its thing and stores messages to send in an outbox table. We use streamBridge to send messages in the outbox table, like streamBridge.send(myDynamicTopic, myBinder, myMessage).

Suspected bandit
Topics are dynamic like my/topic/1, my/topic/2… and high cardinality, so there can be a really high number of dynamic destinations in streamBridge.send(myDynamicTopic, myBinder, myMessage), like potentially hundreds. Exact number at the time of error unknown.

  • Could this lead to the exceeding max publishing flows on the Solace Broker?
  • Is one producer flow per dynamic destination produced this way and expected for the Solace binder?
  • Are flows released/reduced, how?
  • What is a good fix for this?

Stuff details
Spring Boot 3.2.11
Spring Cloud 2023.0.3
solace-spring-cloud-bom 4.5.0
spring-cloud-stream-binder-solace 5.5.0
Java 17, Kotlin 1.9.25

Hi @markymark1,

The broker enforces max-ingress-flows at three nested levels, so it would probably be worth checking at each of them:

Layer Scope Syslog Alert
System-wide Entire broker hardware `SYSTEM_AD_MAX_INGRESS_FLOWS_EXCEEDED`
Per Message VPN All clients in the VPN `VPN_AD_MAX_INGRESS_FLOWS_EXCEEDED`
Per Client Profile Per single client connection `CLIENT_AD_MAX_INGRESS_FLOWS_EXCEEDED`

The client profile limit is the most commonly hit. On Solace Cloud, the default is 1,000 ingress flows per client. If all of that looks good then I’d recommend looking at the flows in the broker manager to see if you can see more about what’s going on there.

On a related note, if you’re able to upgrade solace-spring-cloud you should consider it. Maybe there is something going on where the flows aren’t being properly handled and that is how you’re ending up with so many and hitting limits.

Cloud Stream Binder Bug (Pre-4.11.1)
There is a known bug in solace-spring-cloud versions prior to 4.11.1 that directly relates to this error:

Problem: When the broker sends an unsolicited CloseFlow (including as a result of the 503 limit being hit), the binder’s XMLMessageProducer is terminally closed. The session stays connected, but every subsequent producer.send() throws StaleSessionException — requiring an application restart to recover.

Fix: Merged in June 2026 (PR #483/#484). The binder now auto-recovers by detecting StaleSessionException / ClosedFacilityException and transparently recreating the producer.

Upgrade to solace-spring-cloud ≥ 4.11.1 if you’re able to.

Hope that helps!

Hi @markymark1,

Thanks for the detailed write-up. The logs and version info made this much easier to track down.

Short answer: yes, the high number of dynamic topics is what triggers the 503. The root cause is a resource leak in Spring Cloud Stream’s StreamBridge that shows up when a binder name is passed to send(...). We reproduced it on Spring Boot 3.2.11, Spring Cloud 2023.0.3 and binder 5.5.0, and saw the same sequence of log messages you shared.

Your questions:

1. Could many dynamic destinations exceed the max publishing flows?
Yes. Each StreamBridge destination becomes its own output binding.

2. Is one producer flow per dynamic destination expected?
Yes, that’s expected with the Solace binder. Each output binding opens its own publisher flow on the broker.

3. Are flows released, and how?
They’re supposed to be. StreamBridge keeps a cache of destinations (spring.cloud.stream.dynamic-destination-cache-size, default 10). When the cache is full, it unbinds the oldest destination, and that unbind closes the flow.

The problem is what happens when you call streamBridge.send(topic, binderName, message):

  • The cache stores the entry under the key binderName:topic.
  • The binding itself is registered under just topic.
  • On eviction, the unbind looks for a binding that doesn’t exist, so the flow is never closed.

That’s the meaning of this line in your logs:

Trying to unbind 'my-binder:my/topic/59', but no binding found.

Every new topic leaks one flow. Once you reach the client profile’s limit (100 in your case), the broker rejects new flows with 503 Max Router Publishing Flow Exceeded. Restarting the pod closes the session, which frees all the flows, so publishing recovers. But the leak starts again right away, so the problem comes back.

In my test with a 100-flow limit, the first 99 topics published fine, and every topic after that failed. When we ran the same test without the binder name, all 150 topics published and the old flows were closed as expected.

4. What’s a good fix?

Recommended: use one output binding and set the target topic per message.
This keeps a single publisher flow no matter how many topics you publish to. Define one output binding, for example outbox-out-0, bound to your binder. Then set the destination per message with the scst_targetDestination header:

val msg = MessageBuilder.withPayload(payload)
    .setHeader(BinderHeaders.TARGET_DESTINATION, myDynamicTopic)
    .build()
streamBridge.send("outbox-out-0", msg)
spring:
  cloud:
    stream:
      bindings:
        outbox-out-0:
          destination: my/topic/default   # used only if header is absent
          binder: my-binder

Alternative: stop passing the binder name.
Set spring.cloud.stream.default-binder: my-binder and call streamBridge.send(myDynamicTopic, message). Eviction then works correctly, and the flow count stays around the cache size. It still creates and tears down flows as topics change, so we’d recommend the first option for high-cardinality topics.

Raising maxIngressFlowCount on the broker would only delay the problem.

Upgrade option: The original StreamBridge issue was fixed in Spring Cloud Stream 4.2.0, which ships with Spring Cloud 2024.0.x (Spring Boot 3.4+). I re-ran the same test on Spring Boot 3.5.16 / Spring Cloud 2025.0.3 / Solace Binder 5.11.1 and on Spring Boot 4.0.8 / Spring Cloud 2025.1.3 / Solace binder 6.1.1, and in both cases flows were released correctly with no 503 errors. If upgrading isn’t possible right away, either of the workarounds above avoids the issue on your current versions.