What is functional (programmatic) bean registration in Spring, and how does it differ from declaring beans with @Bean or @Component?
answer
- registerBean(Class, Supplier, Customizer...)
- GenericApplicationContext base
- no scanning, no reflection
- lambda constructs instance directly
- imperative vs declarative
basics
~10 sIt registers beans by calling code — e.g. context.registerBean(MyService.class, MyService::new) — instead of using @Component scanning or @Bean methods. You give Spring a name, a type, and a lambda that creates the instance.
solid answer
~40 sFunctional registration means you tell the container about a bean by calling an API at runtime rather than by annotations. The core entry point is GenericApplicationContext.registerBean(Class, Supplier, BeanDefinitionCustomizer...) (AnnotationConfigApplicationContext extends GenericApplicationContext, so it has it too). You pass the bean type, a Supplier lambda that constructs the instance, and optional customizers that tweak the BeanDefinition (scope, primary, lazy). Unlike @Component, there is no classpath scanning; unlike @Bean, there is no reflective invocation of a factory method — the lambda is called directly. It is fully programmatic, so registration can be conditional on plain if-statements. Trade-off: you lose annotation discoverability and must wire dependencies explicitly. It shines for library code, dynamic bean sets, and reflection-free startup (AOT/GraalVM native).
code
java · 10 linesGenericApplicationContext ctx = new AnnotationConfigApplicationContext();
// Explicit supplier: you build the instance, no constructor autowiring
ctx.registerBean(GreetingService.class, () -> new GreetingService("hi"));
// Let Spring instantiate + autowire (no supplier)
ctx.registerBean(OrderService.class);
ctx.refresh();
GreetingService svc = ctx.getBean(GreetingService.class);go deeper
Know that registerBean(Class, Supplier) exists and is an alternative to @Component/@Bean where you provide the instance via a lambda.
Explain the Supplier-disables-autowiring nuance and the BeanDefinitionCustomizer varargs, and that it lives on GenericApplicationContext.
Discuss when to prefer it (libraries, dynamic bean sets, reflection-free startup) and the refresh() ordering.
Frame it around AOT/native-image reflection avoidance, startup cost, and API design for libraries that shouldn't force scanning on consumers.
**The two familiar ways to declare a bean** - `@Component`/`@Service`/`@Repository`: a class is annotated, and *component scanning* discovers it via classpath scanning + reflection, then the container instantiates it reflectively. - `@Bean`: a method on a `@Configuration` class returns an object; Spring calls that method (reflectively) to obtain the bean. Both are *declarative* — metadata (annotations) drives registration. **Functional / programmatic registration** is *imperative*: you call an API to add a bean definition yourself. The primary API is on `GenericApplicationContext`: ```java void registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) ``` - `GenericApplicationContext` is the flexible base context that supports programmatic registration. `AnnotationConfigApplicationContext` and `AnnotationConfigServletWebServerApplicationContext` (Boot's default web context) extend it, so `registerBean` is available in typical apps. - The **`Supplier<T>`** is the *instance supplier*: a lambda Spring calls to create the object. Because you call the constructor yourself (`MyService::new`), there is **no reflection** for instantiation and **no autowiring of the constructor** — you are responsible for passing dependencies. - The **`BeanDefinitionCustomizer`** is a functional interface `void customize(BeanDefinition bd)` letting you adjust the generated `BeanDefinition`: `bd.setScope`, `bd.setPrimary(true)`, `bd.setLazyInit(true)`, etc. **How it differs, concretely** | Aspect | @Component | @Bean | registerBean(Supplier) | |---|---|---|---| | Discovery | classpath scan | config class parsing | explicit API call | | Instantiation | reflection | reflective method call | direct lambda call | | Conditional logic | @Conditional | @Conditional / if in method | plain Java `if` | | Dependency wiring | autowired | method params autowired | you supply manually (or omit supplier to autowire) | **Where you call it** Register beans **before** `refresh()`. In Spring Boot the idiomatic hook is an `ApplicationContextInitializer<GenericApplicationContext>`, or you build a bare `GenericApplicationContext`, call `registerBean(...)` repeatedly, then `context.refresh()`. **When to use it** - Library/framework code that must register beans without forcing users to scan a package. - Registering *many similar* beans in a loop (e.g., one bean per configured tenant). - Reflection-free startup for **GraalVM native images / Spring AOT**, where suppliers are AOT-friendly. - Faster startup by skipping scanning. **Gotchas** - Providing a `Supplier` disables constructor autowiring for that bean — if you want Spring to autowire, use the overload *without* a supplier (`registerBean(MyService.class)`), which lets the container instantiate and inject. - Programmatic beans are not annotation-discoverable, so tooling/`@ComponentScan` won't see them. - Out of scope here: `@Import`/`ImportBeanDefinitionRegistrar` and `@Bean` factory methods are *other* registration mechanisms.
- Does registerBean trigger classpath scanning?No. It adds a single bean definition directly. Scanning is only triggered by @ComponentScan / scan(). registerBean is a targeted, imperative call.
- If you pass a Supplier, will Spring autowire the constructor arguments?No. The supplier is the instance factory, so it fully owns construction. To get autowiring, use the overload without a supplier so the container instantiates the class and injects dependencies.
saying these in an interview costs you the question
- Claiming registerBean does component scanning under the hood
- Thinking the Supplier is still autowired by the container
- Confusing registerBean with @Import / ImportBeanDefinitionRegistrar
- Saying it only works on AnnotationConfigApplicationContext (it's defined on GenericApplicationContext)