Companion code for the article "Mastering Dynamic Spring Beans: Building
Plug-and-Play Enterprise Libraries" — a practical example of building a
reusable, configuration-driven Spring library that registers beans dynamically
from application.yml and integrates fully with the Spring lifecycle.
⚠️ Placeholder coordinates. This repository intentionally usescom.example:spring-dynamic-beans-libraryand thecom.example.dynamicbeanspackage. Replace them with your organization's coordinates before publishing the library to a real repository.
- Dynamic multi-instance bean registration:
BeanDefinitionRegistryPostProcessorregisterBeanDefinition()turn a YAML map into N named Spring beans, injectable via@Qualifier("<name>")— the same pattern Spring Data uses.
- Resource-safe lifecycle: native resources (the AWS SDK's connection pools
and threads) are registered as separate beans with
destroy-method: close, so Spring shuts them down cleanly — no leaked sockets on redeploy. - Non-bootable library packaging: no
spring-boot-starter-parent, nospring-boot-maven-plugin, Spring dependencies inprovidedscope. - Opt-in activation:
@ConditionalOnPropertygates the whole factory behind.enabled=true.
| Artifact | Pattern shown |
|---|---|
dynamic-beans-s3 |
Two beans per configured client (SDK client with destroy method + wrapper) — the heavyweight variant |
dynamic-beans-keyvault |
One bean per configured vault, no destroy method needed — the lightweight variant |
dynamic-beans-kafka |
N producers/consumers/listener containers from YAML, no @KafkaListener — the containers variant |
dynamic-beans-alfresco |
N named REST clients behind one interface (RestClient built in-constructor, no destroy method) — the article's original case study |
The library is a Maven reactor of four independent artifacts (they share no code). Depend on the module you use — and only on it:
<!-- pick one or more -->
<dependency>
<groupId>com.example</groupId>
<artifactId>dynamic-beans-s3</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>dynamic-beans-keyvault</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>dynamic-beans-kafka</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>dynamic-beans-alfresco</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>Each artifact brings its own technology dependency — a compile-scope SDK, or
provided spring-web in the alfresco module, whose "SDK" is Spring's own
RestClient — and its own auto-configuration manifest, gated twice:
@ConditionalOnClass (technology on the classpath?) and
@ConditionalOnProperty (feature enabled?). Nothing activates until you
configure it.
See docs/application.yml.sample.
Each entry under aws.s3.clients produces two beans:
aws:
s3:
enabled: true
clients:
primaryStorage: # -> beans "primaryStorage" and "primaryStorage-sdkClient"
enabled: true
region: us-east-1
bucketName: example-bucket
presignedUrlDuration: 24h@Service
public class DocumentService {
private final AmazonS3Client storage;
// Explicit constructor: Lombok's @RequiredArgsConstructor would drop @Qualifier.
public DocumentService(@Qualifier("primaryStorage") AmazonS3Client storage) {
this.storage = storage;
}
}The S3 and Key Vault modules are the same pattern with one deliberate difference — resource lifecycle:
- S3 (heavyweight client): the AWS SDK
S3Clientowns connection pools and threads, so the factory registers TWO beans per client — the SDK client withdestroy-method: close, and the wrapper referencing it. - Key Vault (lightweight client): the Azure
SecretClientholds no native resources requiring an explicit close, so the factory registers ONE bean per vault and no destroy method.
Always ask: "does this resource need explicit cleanup?" — that answer, not habit,
decides the registration shape. And if a starter already gives you the single
client you need (e.g. Spring Cloud Azure's autoconfigured SecretClient when one
vault is enough), you may not need dynamic registration at all: reach for
BeanDefinitionRegistryPostProcessor when the bean COUNT depends on configuration.
Requires Java 21 and Maven 3.9+. The build is a multi-module reactor:
mvn -B clean verify # builds and tests all modules
mvn -B clean verify -pl dynamic-beans-kafka # one module onlyCopyright 2026 Sourcesense.
Licensed under the Apache License, Version 2.0. You may obtain a copy of the
License in the LICENSE file or at
https://www.apache.org/licenses/LICENSE-2.0.