skip to content

How are bean names assigned during component scanning, and how do you customize them with BeanNameGenerator?

level: seniorimportance: should knowfreq 30%

answer

  1. AnnotationBeanNameGenerator: explicit value else decapitalized simple name
  2. two leading caps → unchanged (URLParser stays URLParser)
  3. nameGenerator = FullyQualifiedAnnotationBeanNameGenerator → FQN names
  4. solves same-name-across-packages collision
  5. names drive @Resource / name-based @Autowired fallback

basics

~20 s

By default a scanned bean's name is the class's simple name with a lowercased first letter (OrderService → orderService), or the value you pass to the stereotype (@Service("x")). You override the strategy with a BeanNameGenerator via @ComponentScan(nameGenerator = ...).

solid answer

~40 s

Scanning names beans through a BeanNameGenerator. The default, AnnotationBeanNameGenerator, first checks whether the stereotype annotation carries an explicit value (@Component("foo"), @Named("foo")) and uses it; otherwise it builds the default name = uncapitalized simple class name (OrderService → orderService). Edge case: if the simple name has two leading capitals (e.g. URLParser) Java Beans rules leave it unchanged. You customize by passing @ComponentScan(nameGenerator = MyGenerator.class) where MyGenerator implements BeanNameGenerator (generateBeanName(BeanDefinition, BeanDefinitionRegistry)). A common real-world choice is FullyQualifiedAnnotationBeanNameGenerator, which uses the fully-qualified class name as the bean name to avoid collisions when scanning multiple packages that contain same-named classes. Spring Boot exposes the same idea via a property. Names matter because they're the default qualifier for @Autowired-by-name and @Resource resolution.

code

java · 14 lines
java
// Avoid duplicate 'orderService' beans across packages by using FQN names
@Configuration
@ComponentScan(
    basePackages = {"com.app.sales", "com.app.billing"},
    nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
class AppConfig { }

// Custom generator: keep explicit values, prefix defaults with module tag
public class ModulePrefixNameGenerator extends AnnotationBeanNameGenerator {
    @Override
    protected String buildDefaultBeanName(BeanDefinition definition) {
        return "mod_" + super.buildDefaultBeanName(definition);
    }
}

go deeper

for a junior

Should know default name = uncapitalized class name and @Service("x") overrides it.

for a middle

Should know BeanNameGenerator is configurable via @ComponentScan(nameGenerator=...) and that explicit values win.

for a senior

Should cite FullyQualifiedAnnotationBeanNameGenerator for cross-package name collisions and the decapitalization edge case.

for a principal

Should reason about naming conventions for modular systems, override conflicts under allow-bean-definition-overriding=false, and impact on name-based resolution.

## Where bean names come from Every bean definition needs a **name** (its primary id in the registry). During scanning this is decided by a **`BeanNameGenerator`** — a strategy interface with one method: `String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry)`. ### Default: AnnotationBeanNameGenerator The scanner uses `AnnotationBeanNameGenerator` (singleton `INSTANCE`). Its logic: 1. **Explicit value wins.** If the stereotype (or any annotation meta-annotated with `@Component`, or `jakarta.inject.Named`/`jakarta.annotation.ManagedBean`) has a non-empty `value`, that string is the bean name. So `@Service("orders")` → bean name `orders`, `@Repository("userDao")` → `userDao`. 2. **Otherwise a default name** is derived from the short class name via `Introspector.decapitalize`: `OrderService` → `orderService`, `MyController` → `myController`. ### The decapitalization edge case `Introspector.decapitalize` follows JavaBeans rules: if the **first two** characters are both uppercase, the name is left **unchanged**. So `URLParser` → `URLParser` (NOT `uRLParser`), and `ABCService` → `ABCService`. Candidates often get this wrong. ### Customizing via @ComponentScan(nameGenerator = ...) ```java @ComponentScan(basePackages = "com.app", nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class) ``` `nameGenerator` takes a `Class<? extends BeanNameGenerator>` that must have a no-arg constructor (Spring instantiates it). ### FullyQualifiedAnnotationBeanNameGenerator A built-in subclass whose default (no explicit value) name is the **fully-qualified class name** (`com.app.order.OrderService`) instead of the short name. **Why it matters:** if you scan two packages each containing an `OrderService`, the short-name generator produces two beans both named `orderService` → a **BeanDefinitionStoreException / override conflict**. FQN names are unique, eliminating the clash. Spring Boot lets you switch to it via `spring.main.name-generator` / setting it on the `SpringApplication`. ### Writing your own Implement `BeanNameGenerator` for custom conventions (e.g. prefixing by module): ```java public class ModulePrefixNameGenerator extends AnnotationBeanNameGenerator { @Override protected String buildDefaultBeanName(BeanDefinition d) { return "mod_" + super.buildDefaultBeanName(d); } } ``` You can override just `buildDefaultBeanName` to keep explicit-value handling. ### Why bean names matter - They're the key in the singleton registry and what `getBean(name)` uses. - `@Autowired` resolves **by type**, but on ambiguity falls back to the **field/parameter name matched against the bean name** (via the default candidate resolution) — and `@Resource` resolves **by name first**. So a nonstandard generator can break name-based injection expectations. - Duplicate names across scans cause **overriding** (or an error when `spring.main.allow-bean-definition-overriding=false`, the Boot default). ### Related - `DefaultBeanNameGenerator` — used for XML/generic definitions (delegates differently). - For programmatic registration you can pass a generator to `AnnotationConfigApplicationContext` / `ClassPathBeanDefinitionScanner.setBeanNameGenerator(...)`. ### Gotchas - The generator must be resolvable with a default constructor when referenced by class in `@ComponentScan`. - Changing to FQN names breaks any code/config that references beans by their short name. - Explicit `@Component("x")` values are honored regardless of the generator's default-name logic (unless you override the value-handling too).

  • You scan two packages that each contain a class named OrderService and startup fails with a bean override/conflict. How do you fix it cleanly?
    Use FullyQualifiedAnnotationBeanNameGenerator (or a custom generator) so default names become fully-qualified class names and no longer collide, or give one an explicit @Service("...") value.
  • What bean name does a class named XMLParser get by default?
    XMLParser — Introspector.decapitalize leaves names with two leading capitals unchanged, so it is NOT xMLParser.

saying these in an interview costs you the question

  • Saying the default name is always the fully-lowercased class name
  • Not knowing the two-leading-capitals decapitalization exception
  • Believing an explicit @Service("x") value is ignored in favor of the generated name
  • Thinking bean names never affect injection (they affect @Resource and name-based fallback)

context