What’s New in Arconia 0.30.0

Arconia 0.30.0 provides a number of new features and enhancements. This page includes the highlights of the release, but you can also check out the full release notes for more details about each new feature and bug fix.

We provide an automated way to upgrade your project to the latest version of Arconia, which is described in the Upgrading Arconia documentation.

Dev Services

Ollama

  • The Ollama Dev Service can now pull models automatically once the container is ready, via the new arconia.dev.services.ollama.models property (for example, qwen3:0.6b). Models are pulled in the order they are declared, which is convenient when your application needs both a chat model and an embedding model. No model is pulled by default. Since the first pull downloads the entire model, consider combining it with the reuse option to avoid paying that cost at every application restart. Learn more about Pulling Models.

  • The Spring AI OpenAI compatibility layer, which points the OpenAI client at the Ollama Dev Service, can now be turned off on its own via the new arconia.dev.services.ollama.openai-compatibility property (enabled by default). Previously, keeping your own spring.ai.openai.* configuration in dev or test mode required disabling the whole Ollama Dev Service, which is inconvenient when using Ollama and OpenAI side by side. Learn more about Spring AI OpenAI Compatibility.

Oracle XE

  • The Oracle XE Dev Service classes have moved from the io.arconia.dev.services.oracle package to io.arconia.dev.services.oracle.xe. The previous package was shared with the separate Oracle Dev Service artifact, resulting in a split package across two JARs. Configuration properties are unaffected: they remain under arconia.dev.services.oracle-xe. You only need to act if you referenced OracleXeDevServicesAutoConfiguration or OracleXeDevServicesProperties by name, most commonly when excluding the auto-configuration via spring.autoconfigure.exclude. The automated upgrade takes care of it.

Pulsar

  • The arconia.dev.services.pulsar.management-console-port property has been renamed to arconia.dev.services.pulsar.admin-port. The port it configures exposes the Pulsar HTTP admin API, not a management console: unlike the Artemis, LLDAP, and RabbitMQ Dev Services, which keep the management-console-port name, Pulsar ships no web console. If you set the old property, rename it; the other Dev Services are unaffected. Learn more about Accessing the Admin API.

Multitenancy

Data Access

  • Tenant-aware data access is now available via the new arconia-multitenancy-data-jdbc artifact, supporting the database-per-tenant isolation strategy. Declare a TenantDataSource bean and each connection request is routed to the database belonging to the current tenant, either from data sources registered upfront or from a factory that creates them on demand and caches the result. It builds on plain JDBC, so it works with JdbcClient, JdbcTemplate, and Spring Data JDBC alike. An unknown tenant never falls back to the default data source, so a missing registration cannot silently expose another tenant’s data. Learn more about Data JDBC.

  • TenantDataSource validates the tenant identifier itself before asking the factory to create a data source for it, rather than trusting whatever bound the tenant context. The context can be bound programmatically, from a message consumer or a scheduled task, which bypasses the validation an entry point such as the TenantContextFilter performs, and the identifier is about to select a database. Supply your own validator through tenantIdentifierValidator() when your tenant identifiers have a stricter known shape. Learn more about Validating Tenant Identifiers.

  • The number of data sources created on demand is now bounded, at 100 by default and configurable through maxTenantDataSources(). Each one holds a connection pool, so without a limit a stream of unknown tenant identifiers could exhaust connections, file descriptors, and heap. Data sources registered upfront do not count towards it. Learn more about Limiting Created Data Sources.

  • Creating and closing a tenant data source is now logged, and the first use of the default data source is reported as a warning. A tenant context is not inherited by threads started from within its scope, so work handed to an executor silently reached the default data source instead of the tenant’s database; it now leaves a trace in the logs. The tenants whose data sources are currently open are available through TenantDataSource.getCreatedTenantIdentifiers(). Learn more about Observability.

  • Closing the data sources at shutdown no longer stops at the first failure. Previously a data source that threw from close() left every remaining pool open and the cache populated. All of them are now closed, and the first failure is rethrown afterwards with any others attached as suppressed exceptions.

Tenant Resolution

  • The tenant can now be resolved from a claim in the OAuth2 token that authenticated the request, via the new oauth2 value for the arconia.multitenancy.resolution.http.resolution-mode property. It covers both OAuth2 client applications, where the claim is read from the ID token and the UserInfo response, and OAuth2 resource servers, where it is read from the access token, whether it is a JWT or an opaque token resolved via introspection. Use the new arconia.multitenancy.resolution.http.oauth2.claim-name property to configure which claim holds the tenant identifier (tenant_id by default). Learn more about OAuth2-Based Resolution.

  • The TenantContextFilter can now be positioned relative to the other servlet filters via the new arconia.multitenancy.resolution.http.filter.order property. By default the filter still runs after the Spring Security filter chain, which is what the new oauth2 resolution mode requires. Previously, changing the filter placement meant registering it in a SecurityFilterChain by hand. Learn more about Filter Order.

Web

  • A failure of the tenant registry itself, for example an unreachable database behind the TenantDetailsService, now produces an HTTP 503 Service Unavailable response instead of an unhandled 500 with a stack trace on every request. Only a caller problem, such as a missing, malformed, or unknown tenant identifier, still produces a 400 Bad Request. Error responses are also served as application/problem+json with an explicit UTF-8 encoding.

  • The TenantContextFilter now runs on error dispatches, so error handling for a failed request executes with the same tenant bound as the request that failed. Previously the tenant context was gone by the time an /error handler ran. Asynchronous request handling is unchanged and still runs outside the tenant context. Learn more about Context Propagation Across Threads.

  • Ignore-path patterns are now matched against the path within the application instead of the raw request URI, so they keep working under a context path. With server.servlet.context-path=/app, the default /actuator/\** pattern previously did not match a request to /app/actuator/health, which meant those requests were tenant-resolved and rejected.

  • The default ignore paths gained /login, /oauth2/authorization/*, and /login/oauth2/code/\*, so a user can authenticate before any tenant has been established. At the default filter order these endpoints are handled inside the Spring Security filter chain and never reached the filter anyway, but they do reach it when the login page is served by your own controller or when the filter is ordered ahead of the security chain. Learn more about Ignoring Paths.

  • @TenantIdentifier now supports Optional<String> parameters, which makes it possible to express in the signature that a handler can be reached without a tenant, as happens on an ignored path. A plain String parameter continues to be null in that case, matching how Spring Security’s @AuthenticationPrincipal behaves for an unauthenticated request. Learn more about @TenantIdentifier Annotation.

  • HeaderTenantResolver, CookieTenantResolver, and OAuth2TenantResolver are now created through a builder instead of a constructor, matching the rest of the multitenancy module. Replace new HeaderTenantResolver(headerName) with HeaderTenantResolver.builder().tenantHeaderName(headerName).build(), and the no-argument constructors with a plain builder().build(). The automated upgrade takes care of it.

Tenant Details

  • Tenant identifiers are now validated on the way in, whatever the application configures. Previously, the syntax check lived inside the DefaultTenantVerifier, which is only auto-configured when a TenantDetailsService is present, so an application without a tenant registry accepted any non-blank string as a tenant identifier. That value goes on to select tenant-specific resources such as a database, so it is now checked at the boundary by the new TenantIdentifierValidator. The auto-configured implementation accepts alphanumeric characters, dashes, and underscores, up to 64 characters, and rejects anything else with an HTTP 400 Bad Request response. Register your own TenantIdentifierValidator bean, or the built-in one with a different maximum length, to change that. Learn more about Tenant Identifier Validation.

  • The tenants known to the application can now be inspected at runtime through the new tenants actuator endpoint, auto-configured when Spring Boot Actuator is in the classpath and a TenantDetailsService is available. It lists the registered tenants and whether each one is enabled, which was previously not observable at all. The endpoint reports attribute names but never attribute values, since tenant attributes are application-defined and may carry credentials. As with any endpoint other than health, include it in the web exposure configuration to reach it over HTTP. Learn more about Tenants Endpoint.

  • Tenant details can now be stored in a relational database, via the new arconia-multitenancy-details-jdbc-spring-boot-starter artifact. Adding the dependency is enough to auto-configure a JdbcTenantDetailsService that loads tenants and their attributes from the DataSource available in the application, so tenants can be added and disabled without restarting it. Until now, the only built-in source was the application configuration, which requires the full list of tenants to be known upfront. Learn more about JDBC-Based Tenant Details.

  • The database holding the tenant details now has a health indicator, auto-configured when Spring Boot Actuator is in the classpath alongside the JDBC-based tenant details. An unreachable tenant details database is the condition that makes every request fail tenant verification, so it is worth surfacing on its own rather than inferring it from the resulting error rate. The check is a bounded count against the tenant details table rather than a full read, and reports the number of registered tenants as a detail. Learn more about Health Indicator.

  • Schema scripts for the tenant details tables are bundled for PostgreSQL and H2. Following the same convention as the Spring Boot SQL initialization, they are applied only when the application uses an embedded database. Use the new arconia.multitenancy.details.jdbc.initialize-schema property to initialize any database or to opt out entirely when you manage the schema yourself, for example with Flyway or Liquibase.

  • The arconia.multitenancy.details.source property has been removed. Which TenantDetailsService is auto-configured is now determined by what the application actually declares, following the same approach Spring Boot took when it removed spring.session.store-type: listing tenants under arconia.multitenancy.details.tenants auto-configures the properties-based implementation, and adding a tenant details dependency auto-configures the corresponding one. Requiring both a dependency and a property value did not scale as more implementations were added, and it forced the core module to know about each of them upfront. If you set source: properties, remove the property and keep your tenants list; if you set source: none, remove the property, since that was already the default. Should both a tenant list and a tenant details dependency be present, the dependency wins, and the new arconia.multitenancy.details.jdbc.enabled property lets you invert that without removing the dependency. The automated upgrade takes care of it.