skip to content

Functional & Programmatic Bean Registration

registerBean with a supplier, BeanDefinitionBuilder and the Kotlin bean DSL declare beans as lambdas instead of annotations, avoiding reflection entirely. Interviewers ask about it in the context of startup speed and native images.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is functional (programmatic) bean registration in Spring, and how does it differ from declaring beans with @Bean or @Component?

level: juniorimportance: must knowfreq 55%

answer

  1. registerBean(Class, Supplier, Customizer...)
  2. GenericApplicationContext base
  3. no scanning, no reflection
  4. lambda constructs instance directly
  5. imperative vs declarative

basics

~10 s

It 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 s

Functional 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 lines
java
GenericApplicationContext 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

for a junior

Know that registerBean(Class, Supplier) exists and is an alternative to @Component/@Bean where you provide the instance via a lambda.

for a middle

Explain the Supplier-disables-autowiring nuance and the BeanDefinitionCustomizer varargs, and that it lives on GenericApplicationContext.

for a senior

Discuss when to prefer it (libraries, dynamic bean sets, reflection-free startup) and the refresh() ordering.

for a principal

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)

context

open as a page

Walk through the registerBean overload that takes a Supplier and BeanDefinitionCustomizer varargs. What does each argument do and what can you customize?

level: middleimportance: must knowfreq 45%

basics

~20 s

registerBean(Class, Supplier, BeanDefinitionCustomizer...): the Class is the bean type, the Supplier is a lambda that creates the instance, and each BeanDefinitionCustomizer is a lambda that tweaks the BeanDefinition — e.g. set scope, primary, or lazy.

open as a page

What is BeanDefinitionBuilder and how do you use it to register a bean programmatically with a BeanDefinitionRegistry?

level: middleimportance: should knowfreq 30%

basics

~10 s

BeanDefinitionBuilder is a fluent helper to build a BeanDefinition. You call BeanDefinitionBuilder.genericBeanDefinition(MyBean.class), chain addPropertyValue/addConstructorArgValue/setScope, call getBeanDefinition(), then register it via registry.registerBeanDefinition(name, definition).

open as a page

Explain Spring's Kotlin bean-definition DSL (the beans { } block). How is it structured, how does it wire dependencies, and how do you plug it into an application?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The beans { } DSL builds bean definitions in Kotlin without annotations. Inside it you write bean<Foo>() to register a type, or bean { Bar(ref()) } to construct one and pull dependencies with ref(). It returns a BeanDefinitionDsl you initialize against a GenericApplicationContext.

open as a page

When would you choose functional bean registration (registerBean / Kotlin DSL) over annotations, and what are the performance, AOT/native-image, and design trade-offs?

level: principalimportance: should knowfreq 22%

basics

~20 s

Choose functional registration for library code that shouldn't force scanning, dynamic sets of beans (one per config entry), and reflection-free startup for GraalVM native / Spring AOT. Trade-off: you lose annotation discoverability and must wire dependencies explicitly.

open as a page