Tenant-Aware Dead-Letter Queues

The dead-letter queue is tenant-aware when multi-tenancy is enabled. The framework provides one routing queue and registry; each operation resolves the tenant from its ProcessingContext and delegates to that tenant’s concrete queue.

Enable the dead-letter queue for the processor as described in Dead-Letter Queue. Then provide a TenantAwareSequencedDeadLetterQueueFactory that creates the concrete JPA, JDBC, or in-memory queue for a tenant. The factory should obtain tenant-specific persistence resources from your application’s tenant-scoped components.

The ordinary SequencedDeadLetterQueueFactory is not replaced by your application code. Multi-tenancy installs the routing adapter automatically via enhancer and uses the tenant-aware factory you configure.

In Spring Boot, register the TenantAwareSequencedDeadLetterQueueFactory as a bean and enable the processor’s DLQ property. The bean is the only additional multi-tenancy DLQ integration point; your factory implementation still needs access to the tenant-specific datasource or entity manager it uses.

The following examples configure a JDBC queue. They assume that the application already registers a tenant-scoped DataSource provider as described in Tenant-scoped components.

  • Declarative configuration

  • Spring Boot

    public void configure(MessagingConfigurer configurer) {
        configurer.componentRegistry(registry -> registry.registerComponent(
                TenantAwareSequencedDeadLetterQueueFactory.class,
                configuration -> (tenant, processorName, queueConfiguration) -> createQueue(
                        tenant, processorName, queueConfiguration
                ))
        );
    }

    private JdbcSequencedDeadLetterQueue<EventMessage> createQueue(
            TenantDescriptor tenant,
            String processorName,
            Configuration configuration
    ) {
        TenantComponentProvider<DataSource> dataSourceProvider =
                TenantComponentProviderUtil.find(configuration, DataSource.class)
                                           .orElseThrow();
        JdbcTransactionalExecutorProvider executor =
                new JdbcTransactionalExecutorProvider(dataSourceProvider.componentFor(tenant));

        return JdbcSequencedDeadLetterQueue.<EventMessage>builder()
                                           .processingGroup(processorName)
                                           .transactionalExecutorProvider(ignored -> executor.getTransactionalExecutor(
                                                   null
                                           ))
                                           .eventConverter(configuration.getComponent(EventConverter.class))
                                           .genericConverter(configuration.getComponent(GeneralConverter.class))
                                           .build();
    }
    @Bean
    public TenantAwareSequencedDeadLetterQueueFactory tenantAwareDeadLetterQueueFactory() {
        return (tenant, processorName, queueConfiguration) -> createQueue(tenant, processorName, queueConfiguration);
    }

    private JdbcSequencedDeadLetterQueue<EventMessage> createQueue(
            TenantDescriptor tenant,
            String processorName,
            Configuration configuration
    ) {
        TenantComponentProvider<DataSource> dataSourceProvider =
                TenantComponentProviderUtil.find(configuration, DataSource.class)
                                           .orElseThrow();
        JdbcTransactionalExecutorProvider executor =
                new JdbcTransactionalExecutorProvider(dataSourceProvider.componentFor(tenant));

        return JdbcSequencedDeadLetterQueue.<EventMessage>builder()
                                           .processingGroup(processorName)
                                           .transactionalExecutorProvider(ignored -> executor.getTransactionalExecutor(
                                                   null
                                           ))
                                           .eventConverter(configuration.getComponent(EventConverter.class))
                                           .genericConverter(configuration.getComponent(GeneralConverter.class))
                                           .build();
    }