Spring Boot Dynamic Component Scan at Runtime Guide
Learn how to dynamically scan and load packages in Spring Boot at runtime like static scanBasePackages. Fix ClassPathBeanDefinitionScanner dependency issues, handle @Configuration processing, and register Spring Shell commands without child contexts.
How to dynamically scan and load new packages at runtime in a Spring Boot modular application, similar to static scanBasePackages?
Setup
- Main class annotated with:
@SpringBootApplication(scanBasePackages = "hu.xyz.spring")
@Command(group = "DeToX base commands")
@ShellComponent
- Application started via:
SpringApplication application = new SpringApplication(any);
context = application.run(args);
Dynamic Scanning Attempt
private static void addPackage(String pkg) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) Main.ctx();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) cac.getBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.scan(pkg);
}
Issue
- New package
pkgcontains bean"xy"depending on beans from a@Configurationin the same package. - After
scanner.scan(pkg),getBean("xy")fails with missing dependency exception. - Static inclusion in
scanBasePackagesworks perfectly. - Context is
AnnotationConfigServletWebApplicationContext;refresh()cannot be called twice.
Goals
- Load packages dynamically only when needed (runtime scanning).
- Avoid parent-child contexts if possible, as child
@BeanCommandRegistration(e.g., for shell commands) doesn’t register in parent shell.
Questions
- Why doesn’t runtime scanning load dependencies like static scanning?
- How to force full bean loading (including configurations) for new packages at runtime?
- Can shell commands be dynamically registered in the parent context?
In Spring Boot modular applications, dynamic component scan at runtime using ClassPathBeanDefinitionScanner.scan() registers bean definitions but skips crucial post-processing like @Configuration classes and their dependencies—unlike static scanBasePackages which runs during full context initialization. To mimic static behavior without restarting, create a child context for scanning and refresh it, then bridge beans back to the parent, or manually trigger configuration processing where possible. For shell commands, directly register them in the parent Spring Shell’s CommandRegistry to avoid child context visibility issues.
Contents
- Understanding Spring Boot Component Scan and Static vs Runtime Behavior
- Why Runtime Scanning with ClassPathBeanDefinitionScanner Fails Dependencies
- Limitations of AnnotationConfigServletWebApplicationContext Refresh
- Solution 1: Using Child Contexts for Dynamic Package Scanning
- Solution 2: Programmatic Bean and @Configuration Processing Post-Scan
- Dynamic Shell Command Registration in Spring Shell
- Best Practices for Spring Boot Modular Application Scan at Runtime
- Troubleshooting Common Issues Like Missing Bean Exceptions
- Sources
- Conclusion
Understanding Spring Boot Component Scan and Static vs Runtime Behavior
Ever built a Spring Boot app that starts lean, then needs to load modules on demand? Static component scan via @SpringBootApplication(scanBasePackages = "hu.xyz.spring") handles this flawlessly during bootstrap. It kicks off ClassPathBeanDefinitionScanner under the hood, finds @Component, @Service, @Configuration classes, processes @Bean methods, wires dependencies, and instantiates everything in one go.
But runtime scanning? Your addPackage method grabs the registry post-refresh and calls scanner.scan(pkg). It adds definitions for “xy” and its @Configuration, yet getBean(“xy”) bombs with a missing dependency exception. Why? Static scanning ties into the full lifecycle: AnnotationConfigApplicationContext refresh() invokes BeanDefinitionPostProcessors like ConfigurationClassPostProcessor. This parses @Configuration, generates bean methods as internal factories, and resolves autowiring.
Runtime skips that. Spring’s classpath scanning docs note scanning happens early; post-refresh, it’s just registration—no re-processing. Think of static as a full meal prep; dynamic as dumping ingredients on the table without cooking.
Your setup with AnnotationConfigServletWebApplicationContext (web apps default) compounds this. It prohibits refresh() after startup for thread-safety. Baeldung breaks it down nicely: static @ComponentScan is declarative magic during init, runtime demands manual orchestration.
Why Runtime Scanning with ClassPathBeanDefinitionScanner Fails Dependencies
Let’s dissect your code:
private static void addPackage(String pkg) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) Main.ctx();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) cac.getBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.scan(pkg); // Registers definitions, but...
}
Scanner.scan(pkg) finds classes in “hu.xyz.spring.newpkg”, registers BeanDefinitions for “xy” (@Component depending on a @Bean from Config.class). But no instantiation happens. Spring’s ClassPathBeanDefinitionScanner API is clear: it only parses and registers. Post-refresh, the factory ignores new definitions until you force refresh() or instantiation.
“xy” fails because its @Configuration dependency isn’t processed. ConfigurationClassPostProcessor (runs once at startup) didn’t see the new @Configuration, so no synthetic bean methods. getBean(“xy”) triggers lazy init, but upstream deps are MIA—boom, NoSuchBeanDefinitionException.
Static scanBasePackages works because everything aligns during one refresh(): scan → post-process → instantiate. Runtime? You’re mid-meal, adding uncooked steak. Child contexts or manual post-processing fix this, but web contexts block parent refresh.
What if “xy” autowires something from the original scan? Still fails if new @Configuration provides it. Runtime scan packages demands lifecycle awareness.
Limitations of AnnotationConfigServletWebApplicationContext Refresh
Web apps use AnnotationConfigServletWebApplicationContext. Call refresh() twice? IllegalStateException: “Cannot refresh after close” or “Already refreshed.”
Why? Servlet lifecycle ties to web server startup. Refresh rebuilds the entire factory, risky with active requests. Your SpringApplication.run() creates this context; post-start, it’s locked.
Alternatives like GenericApplicationContext allow multiple refreshes (rarely used in Boot), but web forces servlet variant. Goal: dynamic package scanning spring without restart? Sidestep by child contexts or proxying.
StackOverflow threads echo this: “Hacky post-refresh scanning needs refresh, but web contexts say no.” OSGi modules sidestep, but that’s overkill for most.
Solution 1: Using Child Contexts for Dynamic Package Scanning
Child contexts shine here. Parent (your main web context) stays untouched; child handles new pkg scan and refresh.
public class DynamicLoader {
private final ApplicationContext parent;
public DynamicLoader(ApplicationContext parent) {
this.parent = parent;
}
public void addPackage(String pkg) {
GenericApplicationContext child = new GenericApplicationContext(parent);
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(child);
scanner.scan(pkg); // Now in child registry
// Refresh child to process @Configuration, instantiate "xy"
child.refresh();
// Bridge key beans to parent if needed (singleton)
child.getBeanFactory().registerSingleton("proxiedXy",
new ApplicationContextMethodProxy(parent, child, "xy"));
}
}
Inject DynamicLoader as @Bean. Child inherits parent’s beans, scans new pkg, refreshes safely (GenericApplicationContext allows it). “xy” loads with deps.
Shell issue? Child @ShellComponent/@Command won’t show in parent shell. Bridge via parent CommandRegistry (next section). Or use parent-child spring shell visibility hacks, but direct registration beats it.
Downside: Memory per child. But for modular spring boot, it’s lightweight. Test: addPackage(“hu.xyz.spring.newpkg”) → getBean(“xy”) from child works; proxy to parent.
Solution 2: Programmatic Bean and @Configuration Processing Post-Scan
No child? Hackier, but doable. Manually invoke post-processors.
public void addPackageManual(String pkg) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) parent.getBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.scan(pkg);
// Manually process @Configuration
ConfigurationClassPostProcessor processor = new ConfigurationClassPostProcessor();
processor.setBeanClassLoader(parent.getClassLoader());
processor.postProcessBeanFactory(registry);
// Force instantiation of new beans
String[] newBeans = registry.getBeanDefinitionNames();
for (String beanName : newBeans) {
if (registry.isSingletonCurrentlyInCreation(beanName)) continue;
parent.getBean(beanName); // Lazy init triggers deps
}
}
Tricky—postProcessBeanFactory mimics startup, but skips full refresh. Works for simple @Configuration; complex auto-config? Nah. Baeldung on component scanning warns manual is brittle.
Better for non-web: GenericWebApplicationContext, but Boot web sticks servlet. Use if avoiding child contexts spring shell demands.
Dynamic Shell Command Registration in Spring Shell
Shell commands in new pkg? @ShellComponent won’t auto-register post-scan. Parent shell scans once.
Fix: Autowire CommandCatalog/CommandRegistry in your loader.
@Service
public class ShellDynamicRegistrar {
private final CommandCatalog catalog;
public ShellDynamicRegistrar(CommandCatalog catalog) {
this.catalog = catalog;
}
public void registerShellCommands(String pkg) {
// After scanning/instantiating commands
CommandRegistration reg = CommandRegistration.getBuilder(parent.getBean("xyCommand", Command.class))
.group("Dynamic")
.build();
catalog.register(reg);
catalog.synchronize(); // Refresh shell
}
}
Spring Shell docs confirm runtime registration via catalog.register(). No child needed—direct parent visibility. For @ShellComponent in new pkg: Instantiate manually post-scan, wrap as CommandRegistration.
Pro tip: Listen to ApplicationReadyEvent, then addPackage + registerShellCommands. Shell advanced guide details CommandCatalogCustomizer for bulk dynamic shell commands.
GitHub issue #379 tracks evolution—now seamless.
Best Practices for Spring Boot Modular Application Scan at Runtime
Modular spring boot scan? Lazy load via profiles: @Profile(“moduleX”) on configs, activate runtime. But true dynamic?
- Child contexts for isolation.
- OSGi/JHipster modules for enterprise.
- Micronaut/Quarkus for hot-reload natives (Spring Boot 3+ devtools approximates).
- Cache scanners per pkg to avoid rescan.
- Expose via actuator endpoint: /actuator/loadpkg?pkg=hu.xyz.new.
Avoid full refresh; monitor memory. Test with @DirtiesContext for integration.
Troubleshooting Common Issues Like Missing Bean Exceptions
| Issue | Cause | Fix |
|---|---|---|
| NoSuchBeanDefinitionException on “xy” | @Configuration not processed | Child refresh or manual post-processor |
| Shell command invisible | Child @ShellComponent | Direct CommandCatalog.register() |
| scan(pkg) finds nothing | Wrong pkg name/classpath | Verify Thread.currentThread().getContextClassLoader() |
| Circular deps in new pkg | Missing @Lazy | Add @Lazy to “xy” |
| Web context refresh fail | Servlet lock | Always child GenericApplicationContext |
Log DEBUG org.springframework.context.annotation. Scan fails silently? Set scanner.setAutoRegister(false), check registry.getBeanDefinitionCount().
Sources
- Spring Framework Classpath Scanning — Explains scanning lifecycle and post-refresh limits: https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html
- ClassPathBeanDefinitionScanner API — Details scan() registration without instantiation/processing: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.html
- Spring Shell Command Registration — Covers runtime CommandCatalog registration for dynamic shell commands: https://docs.spring.io/spring-shell/reference/commands/registration.html
- Spring Shell Advanced Reference — CommandCatalog and synchronizing dynamic registrations: https://docs.spring.io/spring-shell/docs/2.1.2/site/reference/htmlsingle/
- Baeldung Spring Component Scanning — Static vs manual scanning with basePackages examples: https://www.baeldung.com/spring-component-scanning
- StackOverflow Spring Classpath Scanning — Community fixes for post-refresh bean issues: https://stackoverflow.com/questions/43720793/spring-classpath-component-scanning
- Spring Shell GitHub Issue 379 — Evolution of dynamic command registration support: https://github.com/spring-projects/spring-shell/issues/379
Conclusion
Dynamic component scan in Spring Boot shines with child contexts for full @Configuration processing and direct CommandCatalog registration for shell commands—mirroring static scanBasePackages without restarts. Skip parent-child pitfalls by bridging or programmatic registry; test thoroughly in your hu.xyz.spring modular setup. This keeps apps flexible, scalable, and shell-ready.