Multi-Tenancy with Data Protection

Axoniq Data Protection encrypts selected fields while a message is converted to or from bytes. In a multi-tenant application, provide a separate converter for every tenant so the converter uses that tenant’s crypto engine and key store. This keeps key management separate even when two tenants use the same data-subject identifier.

The converter is selected when Axon Framework creates infrastructure for a known tenant. It is then used for command and query payloads, event storage, snapshots, and persistent-stream events. The tenant is not added to event metadata and does not need to be held in application state.

Marking protected fields

Mark the data-subject identifier and the fields to protect in the event payload. The data-subject identifier selects the encryption key; @PersonalData marks a field for encryption.

/*
 * Copyright (c) 2010-2026. AxonIQ B.V.
 *
 * Licensed under the AXONIQ TERMS OF SERVICE,
 * Version 29 April 2026 (the "License");
 *
 * The software is available for evaluation use without registration.
 * Continued use beyond the evaluation period requires registration
 * and a commercial license. See the License for the specific language
 * governing permissions and limitations under the License.
 * You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at:
 *  https://www.axoniq.io/legal/terms-of-service
 *
 * For licensing information and to register, visit:
 *  https://www.axoniq.io/pricing
 */

package multitenancy.dataprotection;

import io.axoniq.framework.dataprotection.api.DataSubjectId;
import io.axoniq.framework.dataprotection.api.PersonalData;
import org.axonframework.messaging.eventhandling.annotation.Event;

@Event(namespace = "example", name = "CustomerDataRecorded", version = "1.0.0")
public record CustomerDataRecorded(
        @DataSubjectId String customerId,
        @PersonalData String email
) {

}

Providing a converter per tenant

Register one tenant-scoped component provider for Converter. Its factory is called for a tenant when that tenant first needs a converter. Create a FieldEncryptingConverter with the crypto engine for that tenant and a normal delegate converter.

The crypto-engine factory is application-specific. It must select a key store or key namespace that is isolated for the supplied TenantDescriptor; a shared engine that maps equal data-subject identifiers to the same key does not provide tenant isolation.

  • Declarative - Configuration API

  • Spring Boot

Register the provider on the application’s MessagingConfigurer:

public void registerTenantConverters(MessagingConfigurer configurer) {
    configurer.componentRegistry(registry -> registry.registerComponent(
            TenantComponentProvider.class,                                      (1)
            configuration -> TenantComponentProvider.withFactory(
                    Converter.class,                                            (2)
                    tenant -> new FieldEncryptingConverter(
                            cryptoEngines.createFor(tenant),                    (3)
                            new JacksonConverter()
                    )
            )
    ));
}
1 Register the TenantComponentProvider so multi-tenancy can discover it.
2 The provider supplies Converter instances.
3 Build the crypto engine for the tenant that the provider is creating a converter for.

Expose the provider as a Spring bean:

@Bean
public TenantComponentProvider<Converter> tenantDataProtectionConverters(
        DeclarativeDataProtectionConfiguration.TenantCryptoEngineFactory cryptoEngines
) {
    return TenantComponentProvider.withFactory(
            Converter.class,                                                    (1)
            tenant -> new FieldEncryptingConverter(
                    cryptoEngines.createFor(tenant),                            (2)
                    new JacksonConverter()
            )
    );
}
1 The provider supplies one converter per tenant.
2 Build the crypto engine for that tenant before constructing its encrypting converter.

The provider creates and caches each converter lazily, and releases it when its tenant is removed. See tenant-scoped component lifecycle for the lifecycle details.

What remains global

Registering a tenant-scoped Converter does not replace the application’s normal global converter. The global converter remains in use for work that has no tenant, while the tenant-scoped converter is used wherever multi-tenancy has already resolved the tenant. If no tenant-scoped converter is registered, multi-tenancy uses the normal global converter for every tenant.

For data-protection configuration beyond tenant scoping, including crypto-engine selection and key deletion, see the Axoniq Data Protection reference documentation.