How to Get All Spring-Managed Beans: ApplicationContext and Bean Definitions
Learn how to list all Spring beans using ApplicationContext, BeanFactory, and BeanDefinition.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Java 17+ installed
- ✓Spring Boot 3.x project (we'll use 3.2)
- ✓Basic understanding of dependency injection
• Use applicationContext.getBeanDefinitionNames() to get all bean names\n• Use applicationContext.getBeansOfType(YourClass.class) to filter by type\n• Use ListableBeanFactory for advanced iteration\n• Watch for proxies and lazy beans in production\n• Never list beans in hot paths—it's expensive
Think of Spring as a smart warehouse manager. When your app starts, Spring creates a catalog of every item (bean) it manages. Listing all beans is like asking the manager to read you the entire inventory list—useful for auditing, but don't do it every time a customer walks in.
In any non-trivial Spring Boot application, the IoC container manages hundreds or thousands of beans. From controllers and services to repositories and configuration classes, Spring wires everything together. But what happens when you need to introspect that container? Maybe you're debugging why a bean isn't being picked up, or you're building a monitoring dashboard that shows all registered components. Or perhaps you're writing a library that needs to discover beans dynamically. This article is your field guide to listing all Spring-managed beans using ApplicationContext, BeanFactory, and BeanDefinition. We'll cover the classic approaches, the gotchas that bite you in production (like proxies and lazy initialization), and real-world patterns from SaaS billing systems and payment-processing pipelines. By the end, you'll know exactly how to retrieve beans, filter them by type or annotation, and avoid the performance pitfalls that have taken down staging environments. We'll use Spring Boot 3.2 and Java 17 throughout, with code you can copy-paste into your own project.
Understanding ApplicationContext and BeanFactory
Before you can list beans, you need to understand the two core interfaces: ApplicationContext and BeanFactory. BeanFactory is the root interface for accessing the Spring IoC container. It provides basic methods like getBean() and containsBean(). ApplicationContext extends BeanFactory and adds enterprise features like event publishing, resource loading, and message i18n. In a typical Spring Boot application, you inject ApplicationContext directly. The interface you actually need for listing beans is ListableBeanFactory, which ApplicationContext inherits. ListableBeanFactory provides getBeanDefinitionNames() (returns all bean names as a String[]) and getBeansOfType(Class) (returns a Map<String, T> of beans matching a type). Here's the simplest way to get all bean names in a Spring Boot app: inject ApplicationContext into any Spring-managed component.
getBeansOfType(InvoiceGenerator.class) to dynamically discover all invoice strategies (e.g., PDF, CSV, API) without hardcoding. This made adding a new format a single class addition.ListableBeanFactory to get all bean names or filter by type. It's the most common approach for bean introspection.What the Official Docs Won't Tell You
The official Spring documentation teaches you getBeanDefinitionNames() and getBeansOfType(). What it doesn't tell you is that these methods can lie to you in production. First, beans created via @Bean methods in @Configuration classes show up with the method name, not the class name. So @Bean public DataSource dataSource() registers as dataSource, not DataSource. Second, AOP proxies create a second bean with a name like userService$$EnhancerBySpringCGLIB$$.... If you list beans by type, you'll get the proxy, not the original. Third, @Lazy beans are not initialized until first access, but they still appear in getBeanDefinitionNames(). The bean definition exists, but calling getBean() on it will trigger initialization—potentially causing side effects. Fourth, @Conditional annotations (like @ConditionalOnProperty) can silently exclude beans. Your getBeansOfType() returns an empty map, and you have no idea why. The fix? Always log the bean definition source. Use BeanDefinition to inspect metadata like getResourceDescription() and getScope(). Here's how to get the full picture:
@Lazy bean that created a Kafka connection on first access. When we listed beans for a health check, we accidentally triggered the connection, causing a production incident. We now exclude @Lazy beans from listing.@Lazy beans hide in definitions. Always inspect BeanDefinition to understand the source.Filtering Beans by Type and Annotation
Listing all beans is rarely useful on its own. More often, you need to find beans of a specific type or annotated with a custom annotation. ListableBeanFactory provides getBeansOfType(Class<T> type) which returns a Map<String, T> of all beans that are assignable to the given type. This includes subclasses and proxies. For annotation-based filtering, use getBeansWithAnnotation(Class<? extends Annotation> annotationType). This is extremely powerful for finding all beans marked with @Service, @Repository, or a custom annotation like @PaymentGateway. Here's a real-world example from a payment-processing system where we discover all payment gateway implementations at runtime:
getBeansWithAnnotation(TenantSpecific.class) to dynamically load tenant-specific configurations without restarting the JVM.getBeansOfType() for runtime discovery of implementations and getBeansWithAnnotation() for finding beans with specific annotations.Using BeanFactoryPostProcessor for Early Inspection
Sometimes you need to inspect or modify bean definitions before they're instantiated. This is where BeanFactoryPostProcessor comes in. It runs during the ApplicationContext refresh lifecycle, before any singleton beans are created. You can use it to log all bean definitions, add properties, or even register new beans dynamically. This is particularly useful for frameworks that need to wire beans based on external configuration. Here's an example that logs all bean definitions at startup and adds a custom property to beans of a certain type:
BeanFactoryPostProcessor to detect beans with deprecated @Autowired on fields and log warnings. It helped us track down 50+ violations before a critical release.BeanFactoryPostProcessor to inspect or modify bean definitions before they're created. It's the earliest hook into the container lifecycle.Exposing Beans via a REST Endpoint for Debugging
In production, you often need to see what beans are loaded without attaching a debugger. The cleanest approach is to create a dedicated REST endpoint that returns bean information. This is especially useful when combined with Spring Boot Actuator, but a custom endpoint gives you more control over formatting and filtering. Here's a controller that exposes all beans, with optional filtering by type:
@Primary bean was overridden by a @Qualifier mismatch. A quick curl to our bean endpoint revealed the duplicate immediately.Performance Implications and Best Practices
Calling getBeanDefinitionNames() or getBeansOfType() is not free. These methods iterate over the entire bean factory's internal registry. In a typical application with 200 beans, it's negligible (under 1ms). But in a large microservice with 2000+ beans, or if called repeatedly in a loop, it can become a bottleneck. The real danger is in hot paths: never call these methods inside a request handler that serves thousands of requests per second. The getBeansOfType() method also triggers bean initialization for any beans that are @Lazy and haven't been touched yet. This can cascade into full application initialization if you're not careful. Best practices: cache the results of bean listing if the set doesn't change at runtime (which it usually doesn't). Use @PostConstruct to capture bean references once. If you need dynamic discovery, consider using a registry pattern where beans register themselves on startup. Here's a safe pattern:
getBeansOfType() on every health check. When we scaled to 20 pods, the database connection pool was exhausted because @Lazy beans created new connections. Caching fixed it.getBeansOfType() in request-scoped code. Be aware of @Lazy bean initialization side effects.Advanced: Programmatic Bean Registration
Sometimes listing beans isn't enough—you need to register new beans dynamically at runtime. Spring allows this via BeanDefinitionRegistry.registerBeanDefinition() or the newer GenericApplicationContext.registerBean() method. This is useful for plugin systems, feature flags, or dynamic tenant configuration. Here's an example that registers a new bean based on a runtime condition:
DataSource bean registered dynamically, isolated from others.BeanDefinitionRegistry, but prefer static configuration unless you're building a plugin system.Comparing Approaches: When to Use What
You now have several ways to get Spring beans: ListableBeanFactory.getBeanDefinitionNames(), getBeansOfType(), getBeansWithAnnotation(), BeanDefinitionRegistry, BeanFactoryPostProcessor, and ApplicationContext.getBean(). Each has its place. Here's a comparison to help you choose: For simple listing of all bean names (debugging), use getBeanDefinitionNames(). For finding all implementations of an interface (e.g., payment gateways), use getBeansOfType(). For finding beans with a custom annotation (e.g., @Monitored), use getBeansWithAnnotation(). For inspecting properties like scope and source before instantiation, use BeanDefinitionRegistry. For modifying bean definitions at startup, use BeanFactoryPostProcessor. For accessing a single bean by name, use getBean() (but prefer injection). Never use getBean() in production code—it's a sign of poor design. The table below summarizes the trade-offs:
getBeansOfType(DataPipeline.class) to discover all pipeline stages. Adding a new stage meant just creating a new @Component class—no configuration changes needed.getBean() in business logic.The Case of the Vanishing Payment Processor
NoSuchBeanDefinitionException for FraudDetectionService in production only.@Configuration class that used @ConditionalOnProperty with a profile-specific property missing from production config.- Always verify bean definitions in the target environment using a debug endpoint that lists beans.
- Use
@ConditionalOnMissingBeanwith care—it can silently skip bean creation. - Include bean listing in your health check for critical services.
curl http://localhost:8080/actuator/beans | jq '.contexts.default.beans | keys'curl http://localhost:8080/actuator/beans | jq '.contexts.default.beans["userService"]'| File | Command / Code | Purpose |
|---|---|---|
| BeanListerService.java | @Service | Understanding ApplicationContext and BeanFactory |
| BeanDefinitionInspector.java | @Component | What the Official Docs Won't Tell You |
| PaymentGatewayRegistry.java | @Component | Filtering Beans by Type and Annotation |
| BeanDefinitionLoggerPostProcessor.java | @Component | Using BeanFactoryPostProcessor for Early Inspection |
| BeanDebugController.java | @RestController | Exposing Beans via a REST Endpoint for Debugging |
| CachedBeanRegistry.java | @Component | Performance Implications and Best Practices |
| DynamicBeanRegistrar.java | @Component | Advanced |
| ComparisonRunner.java | @Component | Comparing Approaches |
Key takeaways
ListableBeanFactory for all bean introspection needs—it's the right tool for the job.@Lazy side effects.BeanDefinition to understand bean source, scope, and conditional registration.Interview Questions on This Topic
What is the difference between `BeanFactory` and `ApplicationContext`?
BeanFactory is the basic IoC container providing getBean() and containsBean(). ApplicationContext extends BeanFactory and adds features like event publishing, AOP integration, message i18n, and ListableBeanFactory for iterating over beans. In practice, you always use ApplicationContext.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
3 min read · try the examples if you haven't