flowable/flowable-engine · error · RuntimeException

Failed to destroy listener container

Error message

Failed to destroy listener container

What it means

Thrown by unregisterChannelModel when destroying the Spring RabbitMQ MessageListenerContainer (via DisposableBean.destroy()) fails. The original exception is wrapped in a RuntimeException so the channel-unregistration flow aborts rather than leaving a half-destroyed container. It signals an underlying problem inside the container's own shutdown logic, not in Flowable itself.

Solutions

  1. Inspect the wrapped cause (e.getCause()) for the real failure — usually a RabbitMQ connection/amqp exception
  2. Verify the RabbitMQ broker is reachable and the connection factory is valid before unregistering
  3. Ensure the listener container was fully started/registered before attempting destroy; skip/replace containers already stopped
  4. Check for concurrent unregistration of the same endpointId and synchronize if needed
  5. Upgrade Spring AMQP/Flowable versions if the container destroy() has a known lifecycle bug

Example fix

// before
((DisposableBean) listenerContainer).destroy();
// after
try {
    if (listenerContainer instanceof DisposableBean) {
        ((DisposableBean) listenerContainer).destroy();
    }
} catch (Exception e) {
    logger.warn("Listener container already failed to destroy cleanly", e); // proceed with registry cleanup
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (listenerContainer == null || !(listenerContainer instanceof DisposableBean)) { skip destroy }

Type guard

boolean safelyDestroyable = obj instanceof DisposableBean d && ((org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer) obj).isRunning();

Try / catch

try { unregisterChannelModel(channelModel, tenantId); } catch (RuntimeException e) { log.error("Unregister failed", e.getCause()); /* retry or continue shutdown */ }

Prevention

When it happens

Trigger: Calling eventRegistry/rabbit channel unregistration for a channel whose listener container's destroy() method throws (e.g. underlying RabbitMQ connection/consumer cancellation errors, or a container whose afterPropertiesSet/stop lifecycle is in an inconsistent state).

Common situations: Shutting down or redeploying channel definitions while the broker is unreachable; concurrent shutdown of the application context racing with channel unregistration; containers created incorrectly due to earlier configuration errors.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/4680e1c84f17f229. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/rabbit/RabbitChannelDefinitionProcessor.java:297

    @Override
    public void unregisterChannelModel(ChannelModel channelModel, String tenantId, EventRepositoryService eventRepositoryService) {
        logger.info("Starting to unregister channel {} in tenant {}", channelModel.getKey(), tenantId);
        String endpointId = getEndpointId(channelModel, tenantId);
        // currently it is not possible to unregister a listener container
        // In order not to do a lot of the logic that Spring does we are manually accessing the containers to remove them
        // see https://github.com/spring-projects/spring-framework/issues/24228
        MessageListenerContainer listenerContainer = endpointRegistry.getListenerContainer(endpointId);
        if (listenerContainer != null) {
            logger.debug("Stopping message listener {} for channel {} in tenant {}", listenerContainer, channelModel.getKey(), tenantId);
            listenerContainer.stop();
        }

        if (listenerContainer instanceof DisposableBean) {
            try {
                logger.debug("Destroying message listener {} for channel {} in tenant {}", listenerContainer, channelModel.getKey(), tenantId);
                ((DisposableBean) listenerContainer).destroy();
            } catch (Exception e) {
                throw new RuntimeException("Failed to destroy listener container", e);
            }
        }

        Field listenerContainersField = ReflectionUtils.findField(endpointRegistry.getClass(), "listenerContainers");
        if (listenerContainersField != null) {
            listenerContainersField.setAccessible(true);
            @SuppressWarnings("unchecked")
            Map<String, MessageListenerContainer> listenerContainers = (Map<String, MessageListenerContainer>) ReflectionUtils
                .getField(listenerContainersField, endpointRegistry);
            if (listenerContainers != null) {
                listenerContainers.remove(endpointId);
            }
        } else {
            throw new IllegalStateException("Endpoint registry " + endpointRegistry + " does not have listenerContainers field");
        }

        logger.info("Finished unregistering channel {} in tenant {}", channelModel.getKey(), tenantId);
    }

View on GitHub (pinned to d6d39ce1c6)