flowable/flowable-engine · error · IllegalStateException

Endpoint registry does not have listenerContainers field

Error message

Endpoint registry <endpointRegistry> does not have listenerContainers field

What it means

The processor unregisters endpoints by reflectively removing entries from the endpoint registry's 'listenerContainers' field. If that field cannot be found on the given registry class (incompatible KafkaListenerEndpointRegistry implementation), an IllegalStateException with this message is thrown. This guards against Spring Kafka versions whose internal registry layout changed.

Solutions

  1. Use the standard org.springframework.kafka.config.KafkaListenerEndpointRegistry that has the listenerContainers field.
  2. Align spring-kafka version with the version Flowable's event-registry-spring was built against (check flowable dependency management).
  3. Pin/downgrade spring-kafka to a compatible version if you upgraded recently.
  4. Override the unregister path or subclass KafkaChannelDefinitionProcessor to handle your registry implementation.

Example fix

// before
@Bean public MyCustomRegistry kafkaListenerEndpointRegistry() { ... }
// after
@Bean public KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry() { return new KafkaListenerEndpointRegistry(); }
Defensive patterns

Strategy: fallback

Validate before calling

Field f = ReflectionUtils.findField(registry.getClass(), "listenerContainers");
if (f == null) throw new IllegalStateException("Incompatible KafkaListenerEndpointRegistry: " + registry.getClass());

Try / catch

try {
    undeploy(channelKey);
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().contains("does not have listenerContainers field")) {
        log.error("Replace registry with standard KafkaListenerEndpointRegistry", e);
    } else { throw e; }
}

Prevention

When it happens

Trigger: Using a KafkaListenerEndpointRegistry subclass/replacement that no longer declares a 'listenerContainers' field when a channel is undeployed.

Common situations: Major spring-kafka upgrade that renamed/restructured the registry internals; custom EndpointRegistry implementation passed in.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java:790

            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 endpoint {}", endpointId);
    }

    /**
     * Register a new {@link KafkaListenerEndpoint} alongside the
     * {@link KafkaListenerContainerFactory} to use to create the underlying container.
     * <p>The {@code factory} may be {@code null} if the default factory has to be
     * used for that endpoint.
     */
    protected void registerEndpoint(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory) {
        Assert.notNull(endpoint, "Endpoint must not be null");
        Assert.hasText(endpoint.getId(), "Endpoint id must be set");

        Assert.state(this.endpointRegistry != null, "No KafkaListenerEndpointRegistry set");
        // We need to start the container immediately only if the endpoint registry is already running,
        // otherwise we should not start it and leave it to the registry to start all the containers when it starts.
        // We also need to start immediately if the application context has already been refreshed.

View on GitHub (pinned to d6d39ce1c6)