Errors happen, and Spring Cloud Stream provides several flexible mechanisms to handle them.

Here is a visualization of the “happy path” for a publish-subscribe operation when using a Spring Cloud Stream with the Solace binder to create your event-driven microservices.

Terminology

Before we dive into the error handling functionalities and strategies, let us quickly review the key terminologies related to Error Handling in Spring Cloud Stream binder for Solace PubSub+.

  • Dead Message Queue (DMQ): A designated queue on the message broker that captures re-routed failed messages due to TTL expiration or exceeding the message’s maximum redelivery count.
  • Error Queue: Error queue is a Queue Endpoint on the broker that is used to capture re-routed messages which have been successfully consumed from the message broker yet cannot be processed by the Spring Cloud Stream application.
  • Error Handlers: Custom error handlers at a binding-level or a default error handler to receive and process the error messages.
  • Error Queue Auto-binding :A consumer config property, autoBindErrorQueue, that specifies whether to automatically create a durable error queue to republish messages when message processing failures are encountered.
  • Error Queue Custom Names: Override the opinionated queue naming scheme of Spring Cloud Stream binder for Solace PubSub+ and use custom names for error queues using For more information, check out the blog post Custom Name for your Queues when using Spring Cloud Stream Binder for Solace PubSub+.
  • Manual Acknowledgment: Manually acknowledging a received message with the status ACCEPT, REJECT, or REQUEUE. For more information, check out the blog post Confirming Message Publication and Acknowledging Receipt with Spring Cloud Stream Binder for Solace PubSub+.
  • Message Acknowledgement: The process of acknowledging the receipt of the message by the consumer. Options of automatic and manual acknowledgment are available. For more information, check out the blog post Confirming Message Publication and Acknowledging Receipt with Spring Cloud Stream Binder for Solace PubSub+.
  • Message Handlers: A message handler is a component responsible for receiving, processing, and acting upon messages that are sent to a destination, typically a message channel or a topic.
  • Publish Confirmation: The process of acknowledging the receipt of the message by the consumer. Options of automatic and manual acknowledgment are available. For more information, check out the blog post Confirming Message Publication and Acknowledging Receipt with Spring Cloud Stream Binder for Solace PubSub+.
  • Message Redelivery: An error handling strategy where failed messages are redelivered to the consumer group’s queue.

Publish Operation

When publishing a message from your Spring Cloud Stream microservice, you simply send a message to the broker on a topic. While this sounds simple, several different error scenarios can occur. Here are a few possible failures:

  • ACL permission issues
  • A consumer’s queue is full

If one of these failures were to occur, the Solace binder would throw an exception wrapping details on the failure. As a developer, you can catch these exceptions, take appropriate action, or log the failures for review.

Spring Cloud Stream binder for Solace PubSub+ provides a facility to get confirmation on a publish operation via the Publish Confirmation feature. It is implemented by setting CorrelationData on the message header and getting a publish confirmation within the stipulated time. For more information, check out the blog post Confirming Message Publication and Acknowledging Receipt with Spring Cloud Stream Binder for Solace PubSub+.

Consume Operation

As you see in the diagram, when consuming a message using a Spring Cloud Stream microservice, a message from the Solace PubSub+ Broker passes through the Spring Cloud Stream framework and the binder before reaching the application code. The final entry point in your application is the message handler. An error can occur in any of these levels, and each layer provides error handling features. The binder implementation offers error handling facilities that take advantage of the Broker facilities such as DMQ and Message Redelivery.

What can go wrong, and how do we handle them? Let us review some of the error scenarios and how to handle them.

Your Spring Microservice application code

It’s all about the message processing in the handler – a runtime error can occur, or an application exception may be thrown in the code. Your Spring Microservice could have multiple bindings and require different logic to handle errors at a binding level. Also, you may need a generic error handler to handle errors occurred in bindings that do not have specific error handlers.

Spring Cloud Stream framework allows for defining error handlers at a binding level and a default error handler in the application configuration. If an error occurs in the message handler at runtime, the framework wraps the offending message as a Spring ErrorMessage and calls the registered error handlers.

In example 1, bindingSpecificErrorHandler has been registered as the error handler for binding functionOne. When an error occurs or an exception is thrown in the message handler, it is handed to the registered binding-specific error handler as an ErrorMessage, wrapping the original message.

In example 2, a default error handler has been registered with no binding-specific error handlers. In this scenario, when an error occurs or an exception is thrown in the message handler and no binding-specific error handler is present, it is handed to the default error handler as an ErrorMessage.

Spring Cloud Stream Framework

Outside of wiring up, your custom error handlers, as discussed in the previous section, a particular abstraction that is of interest with respect to error handling is the Spring Cloud Stream framework’s use of RetryTemplate provided by the Spring Retry library. This allows the framework to make several attempts at re-trying the same message with no additional code.

You can configure the retry template settings in the application configuration as suited to your message processing requirements.

Here is an example of retry template settings at a binding level.

...
bindings:
  functionOne-in-0:
    error-handler-definition: bindingSpecificErrorHandler
    destination: solace/function/one
    group: errorhandler
    consumer:
      max-attempts: 3
    binder: solace-broker
...

The Big Picture

The following diagram comprehensively captures the message flow and error handling when using Spring Cloud Stream binder for Solace PubSub+ to build microservices.

Common Error Scenarios

Message acknowledged with REJECT status. Is an error queue is configured?

  • Yes, the message is acknowledged and moved to the error queue.
  • No, the message is acknowledged and removed from the queue.
Message acknowledged with REQUEUE status The message is simply negatively acknowledged and redelivered.
Message conversion failed
  1. Has Spring Retry max attempt exceeded?
    • Yes, check if the Queue is configured with a DMQ.
      • Yes, the message is moved to DMQ.
      • No, the message is removed from the queue.
  2. Binding-specific error handler registered?
    • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the binding-specific error handler.
    • No, check for the default error handler
  3. Default error handler registered?
    • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the default error handler.
    • No, check if the Queue is configured with a DMQ
      • Yes, the message is moved to DMQ.
      • No, the message is acknowledged and removed from the queue.
Max Retry Attempts exceeded Has Spring Retry max attempt exceeded?

  • No, the Spring framework will attempt to retry delivery.
  • Yes. Is a binding-specific error handler registered?
    • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the binding-specific error handler.
    • No. Is a default error handler registered?
      • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the default error handler.
      • No, check if the Queue is configured with a DMQ.
        • Yes, the message is moved to DMQ.
        • • No, the message is negatively acknowledged and removed from the queue.
Exception thrown in message handler
  1. Does Spring Retry max attempt exceed?
    • Yes, check if the Queue is configured with a DMQ.
      • Yes, the message is moved to DMQ.
      • No, the message is acknowledged and removed from the queue.
    • No, the Spring framework will attempt to retry delivery (default of 3 attempts)
  2. Binding-specific error handler registered?
    • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the binding-specific error handler.
    • No, check for the default error handler
  3. Default error handler registered?
    • Yes, A Spring ErrorMessage wrapping the failed message is delivered to the default error handler.
    • No, check if an error queue is configured.
  4. Exception thrown in error handler
    • Message is negatively acknowledged to requeue/redeliver.
    • Message will be redelivered and will be handled by the message handler, and error handler again.
    • This may create an infinite loop – to prevent this either set a max redelivery count or put logic in the message handler to break the loop.
  5. Is an error queue configured?
    • Yes, the message is moved to the configured error queue.
    • No, check if Solace Queue is configured with a DMQ.
  6. Is queueMaxMsgRedelivery is set (non-null) in the application configuration?
    • Yes, the Broker will attempt to redeliver the message to the consumer the specified number of times. Once exhausted, the message will be either queued in the DMQ or dropped.
    • No, check if Solace Queue is configured with a DMQ.

Tip: Don’t forget to set the errorMsgDmqEligible setting in the application configuration if you want the discarded messages to be captured in the DMQ.

General Guidance

We have all these options available for use. How do we choose what to do when handling errors? Of course, it all goes back to your requirements.

In general, keep it simple when possible!

  • Handle your exceptions, and don’t throw them when possible.
  • Consider how your function might fail and configure the RetryTemplate (Would Retrying help?) and binding-specific error channels appropriately.
  • Do you want messages that throw exceptions to end up in another queue? (Use autoBindErrorQueue and name the queues reflecting the error context using errorQueueNameExpression)

If you need more control and are fine writing more messaging-specific code, consider using both the internal framework and binder error-handling options in conjunction with Client/Manual Acknowledgements.

  • Use the Client/Manual Ack to REQUEUE messages that end in an error scenario that may be successful if retried, even if by another instance in the Consumer Group. For example, an infrastructure issue where your microservice couldn’t get a response from a downstream service.
  • Identify your failure scenarios that wouldn’t work if retried and consider if you want to send them all to one queue for further processing or to several destinations. If it is a single queue for all error messages, use the autoBindErrorQueue option, and use the Client/Manual Ack to REJECT the message and let the binder handle the queueing.
  • However, if you prefer to send to several destinations for error processing, use Dynamic Publishing to publish messages to destinations where you’d like. After publishing, use the Client/Manual Ack to ACCEPT the message.

Summary

As a developer, it is essential to understand the error-handling features supported by the Spring Cloud Stream framework and the Spring Cloud Stream binder for Solace PubSub+ to handle error scenarios gracefully in a programmatic manner. I hope this blog helped you to gain knowledge and set the course in that direction.

If you’re new to Spring Cloud Stream and want more help getting up and running, these are great places to learn more.

And finally, Solace Community, where developers and experts exchange ideas, share thoughts, and discuss topics related to event-driven architecture.

Giri Venkatesan
Giri Venkatesan

Giri is a developer advocate with extensive experience in various technical domains, including integration and master data management. He started his engineering journey in the classic EAI & B2B Integration space and has been a part of the integration evolution culminating in modern EDA, microservices, Spring, and other low-code/no-code frameworks. He has a keen interest in building and promoting creative solutions and is a huge fan of open-source standards and applications. He is excited to identify and explore tools and frameworks to aid businesses in their quest for achieving efficiency and increased productivity.