skip to content

What is an ImportBeanDefinitionRegistrar and when do you choose it over an ImportSelector?

level: seniorimportance: should knowfreq 35%

answer

  1. gives you the BeanDefinitionRegistry
  2. register N beans dynamically (per scanned interface)
  3. @MapperScan / @EnableFeignClients / Spring Data
  4. build with BeanDefinitionBuilder; parse-time, no bean injection
  5. 3-arg overload adds BeanNameGenerator

basics

~20 s

ImportBeanDefinitionRegistrar is a Spring hook referenced from @Import. Its registerBeanDefinitions method gives you the BeanDefinitionRegistry so you can build and register BeanDefinitions programmatically — ideal when the number or shape of beans is dynamic, like scanning for interfaces.

solid answer

~30 s

ImportBeanDefinitionRegistrar is a callback you list in @Import. Its method registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry[, BeanNameGenerator]) hands you the raw registry, so instead of returning class names (like ImportSelector) you construct BeanDefinition objects — typically via BeanDefinitionBuilder or a scanner — and register them under names you choose. You pick it over ImportSelector when the beans can't be expressed as a fixed set of @Configuration classes: e.g. registering a dynamic number of proxies for scanned interfaces, as @MapperScan (MyBatis) or @EnableFeignClients do. Like selectors it runs at parse time before beans exist, can implement EnvironmentAware/ResourceLoaderAware/BeanFactoryAware/BeanClassLoaderAware, and reads the triggering annotation's attributes from AnnotationMetadata.

code

java · 29 lines
java
public class FeatureFlagsRegistrar
        implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment env;
    @Override public void setEnvironment(Environment e) { this.env = e; }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata meta,
                                        BeanDefinitionRegistry registry) {
        // read attributes of the triggering @EnableFeatureFlags annotation
        var attrs = meta.getAnnotationAttributes(EnableFeatureFlags.class.getName());
        String[] flags = attrs != null ? (String[]) attrs.get("value") : new String[0];

        // register one bean per flag name -> variable number of beans
        for (String flag : flags) {
            boolean enabled = env.getProperty("features." + flag, Boolean.class, false);
            BeanDefinition bd = BeanDefinitionBuilder
                    .genericBeanDefinition(FeatureFlag.class)
                    .addConstructorArgValue(flag)
                    .addConstructorArgValue(enabled)
                    .getBeanDefinition();
            registry.registerBeanDefinition("featureFlag_" + flag, bd);
        }
    }
}

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)
@Import(FeatureFlagsRegistrar.class)
public @interface EnableFeatureFlags { String[] value(); }

go deeper

for a junior

Likely beyond junior; at most 'it registers beans programmatically'.

for a middle

Know it hands you the registry and you register BeanDefinitions yourself, unlike a selector's class names.

for a senior

Explain when to choose it (dynamic/variable beans, custom BeanDefinition shaping), Aware injection, and parse-time timing.

for a principal

Detail library patterns (@MapperScan/@EnableFeignClients/Spring Data) using ClassPath scanners, name generation, collision avoidance, and startup cost.

**`ImportBeanDefinitionRegistrar`** (`org.springframework.context.annotation.ImportBeanDefinitionRegistrar`) is the lowest-level `@Import` hook. Where `ImportSelector` says *"import these config classes by name,"* a registrar says *"here's the registry — put whatever BeanDefinitions you want into it yourself."* **The method(s):** ```java default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { ... } void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); ``` The two-arg form is the classic one; the three-arg default overload (added later) additionally hands you the `BeanNameGenerator` Spring is using, so generated bean names stay consistent. You override whichever you need. **Key parameters:** - **`AnnotationMetadata importingClassMetadata`** — metadata of the class carrying the `@Import` / `@EnableXxx`, so you can read annotation attributes (e.g. the `basePackages` a user put on `@EnableFeignClients`). - **`BeanDefinitionRegistry registry`** — the mutable registry; call `registry.registerBeanDefinition(name, beanDefinition)`. You typically build definitions with `BeanDefinitionBuilder` / `RootBeanDefinition` / `GenericBeanDefinition`, set the bean class, constructor args, property values, scope, autowire mode, etc. **Why it's more powerful than a selector:** an `ImportSelector` can only name *statically-known* classes. A registrar can: - register a **variable number** of beans (one per discovered interface/package), - register beans whose **class is a proxy/factory bean** computed at runtime, - set fine-grained `BeanDefinition` metadata (constructor args, `FactoryBean`, primary, lazy, qualifiers) that you can't express by merely naming a config class. **Canonical real-world uses:** MyBatis `@MapperScan` registers a `MapperFactoryBean` per mapper interface it finds; Spring Cloud OpenFeign `@EnableFeignClients` registers a `FeignClientFactoryBean` per `@FeignClient`; Spring Data's repository infrastructure registers a repository factory bean per repository interface. All of these need "N beans discovered at runtime," which only a registrar supports. Internally they often use a `ClassPathScanningCandidateComponentProvider` (or `ClassPathBeanDefinitionScanner`) to find candidates, then register a definition per hit. **Lifecycle & injection:** identical constraints to selectors — runs inside `ConfigurationClassPostProcessor` at configuration-parsing time, *before* any bean instances exist. So: - You **cannot** `@Autowired` beans into a registrar. - You **can** implement `EnvironmentAware`, `ResourceLoaderAware` (useful for scanning), `BeanFactoryAware`, `BeanClassLoaderAware`; Spring injects those callbacks before calling `registerBeanDefinitions`. - The registrar itself is **not** registered as a bean; Spring instantiates it directly. **Registrar vs selector vs @Bean — how to choose:** - Fixed, known set of beans expressible in Java → just write a `@Configuration` with `@Bean` methods, or `@Import` it. - Choice among known configs based on attributes/environment → **`ImportSelector`**. - Dynamic/variable set of beans, or definitions needing custom `BeanDefinition` shaping (proxies, factory beans, per-interface registration) → **`ImportBeanDefinitionRegistrar`**. **Gotchas:** - Name collisions: choose deterministic bean names (or use the provided `BeanNameGenerator`) to avoid accidental overrides. - It's easy to register too eagerly — respect the annotation's attributes (base packages, filters) so you don't scan the world. - Note: `GenericApplicationContext.registerBean` and the Kotlin bean DSL are *different*, application-side registration mechanisms (functional registration) — the registrar is the annotation-driven, library-facing one. - Keep it fast; it's on the startup critical path. **When to use:** authoring framework/library `@EnableXxx` or `@XxxScan` annotations that must materialize a dynamic set of beans (proxies for interfaces, one bean per discovered type).

  • Give a real Spring/library annotation implemented with an ImportBeanDefinitionRegistrar and explain why it needs one.
    MyBatis @MapperScan and Spring Cloud @EnableFeignClients. Both scan for interfaces at startup and must register one factory-bean proxy per discovered interface — a variable, runtime-determined set of beans. An ImportSelector can only name statically-known classes, so a registrar (with direct registry access) is required.
  • Can you inject an existing bean into an ImportBeanDefinitionRegistrar to help build definitions? If not, what do you use?
    No — registrars run during configuration parsing before beans are instantiated, so there are no beans to inject. Instead implement Aware interfaces: EnvironmentAware for properties/profiles, ResourceLoaderAware for classpath scanning, BeanFactoryAware/BeanClassLoaderAware as needed; Spring supplies these before registerBeanDefinitions is called.

saying these in an interview costs you the question

  • Saying a registrar returns class names or bean instances (it mutates the registry, returns void)
  • Claiming you can @Autowired beans into a registrar
  • Not being able to name a dynamic use case (per-interface proxies) that a selector can't do
  • Confusing it with GenericApplicationContext.registerBean / Kotlin bean DSL (that's functional registration)

context