Programming

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.

1 answer 1 view

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:
java
@SpringBootApplication(scanBasePackages = "hu.xyz.spring")
@Command(group = "DeToX base commands")
@ShellComponent
  • Application started via:
java
SpringApplication application = new SpringApplication(any);
context = application.run(args);

Dynamic Scanning Attempt

java
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 pkg contains bean "xy" depending on beans from a @Configuration in the same package.
  • After scanner.scan(pkg), getBean("xy") fails with missing dependency exception.
  • Static inclusion in scanBasePackages works 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 @Bean CommandRegistration (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

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:

java
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.

java
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.

java
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.

java
@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

  1. Spring Framework Classpath Scanning — Explains scanning lifecycle and post-refresh limits: https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html
  2. ClassPathBeanDefinitionScanner API — Details scan() registration without instantiation/processing: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.html
  3. Spring Shell Command Registration — Covers runtime CommandCatalog registration for dynamic shell commands: https://docs.spring.io/spring-shell/reference/commands/registration.html
  4. Spring Shell Advanced Reference — CommandCatalog and synchronizing dynamic registrations: https://docs.spring.io/spring-shell/docs/2.1.2/site/reference/htmlsingle/
  5. Baeldung Spring Component Scanning — Static vs manual scanning with basePackages examples: https://www.baeldung.com/spring-component-scanning
  6. StackOverflow Spring Classpath Scanning — Community fixes for post-refresh bean issues: https://stackoverflow.com/questions/43720793/spring-classpath-component-scanning
  7. 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.

Authors
Verified by moderation
Spring Boot Dynamic Component Scan at Runtime Guide