skip to content

How does Spring map the JSR-330 annotations @Inject and @Named onto its own DI model?

level: middleimportance: should knowfreq 50%

answer

  1. @Inject ≈ @Autowired (by type)
  2. @Named on point = @Qualifier
  3. @Named on class = @Component
  4. jakarta.inject not bundled — add it
  5. @Inject has no required attribute

basics

~10 s

Spring treats @Inject (jakarta.inject.Inject) like @Autowired — by-type injection — and @Named like @Qualifier or @Component's bean name. You need the jakarta.inject dependency on the classpath.

solid answer

~40 s

JSR-330 is the standard 'Dependency Injection for Java' API (jakarta.inject / formerly javax.inject). Spring supports it as an alternative to its native annotations. @Inject behaves like @Autowired: injection is by-type, and it's mandatory (no 'required' attribute). @Named plays two roles: on an injection point it acts like @Qualifier("beanName") to disambiguate by name; on a class it acts like @Component, registering the bean under that name (or a default name if omitted). Spring's AutowiredAnnotationBeanPostProcessor recognizes @Inject alongside @Autowired. You must add the jakarta.inject-api dependency, since Spring doesn't bundle it. The benefit is standard, framework-agnostic annotations; the cost is losing Spring-specific features like required=false and constructor-less optionality tricks.

code

java · 20 lines
java
import jakarta.inject.Inject;
import jakarta.inject.Named;

// @Named on a class == @Component named "auditRepo"
@Named("auditRepo")
class AuditRepository implements Repository { /* ... */ }

@Named("primaryRepo")
class PrimaryRepository implements Repository { /* ... */ }

@Named
class RecordService {
    private final Repository repo;

    // @Inject == @Autowired (by type); @Named == @Qualifier("auditRepo")
    @Inject
    RecordService(@Named("auditRepo") Repository repo) {
        this.repo = repo;
    }
}

go deeper

for a junior

Knows @Inject is 'like @Autowired' at a high level.

for a middle

Explains both roles of @Named and that the API must be added to the classpath.

for a senior

Covers the no-required-attribute difference and javax→jakarta namespace pitfalls.

for a principal

Weighs portability vs Spring-native expressiveness and knows the processor internals mapping qualifiers by name.

### What JSR-330 is JSR-330 ('Dependency Injection for Java') defines a tiny set of **standard** DI annotations independent of any framework: `@Inject`, `@Named`, `@Qualifier` (meta-annotation), `@Scope`, `@Singleton`, and the `Provider<T>` interface. The package was `javax.inject`; in the Jakarta era it is **`jakarta.inject`**. Spring, Guice, and CDI all understand these, so code annotated with them is portable across containers. Spring does **not** bundle this API — you add the dependency explicitly (e.g. `jakarta.inject:jakarta.inject-api`). ### How Spring maps each annotation **`@Inject`** → treated **exactly like `@Autowired`**: resolves **by type**, works on constructors, fields, and setters/methods. Processed by the same `AutowiredAnnotationBeanPostProcessor`. Key difference from @Autowired: **@Inject has no `required` attribute**, so it is always mandatory. For optional dependencies with @Inject you'd wrap in `Optional<T>` or `Provider<T>`. **`@Named`** has two distinct uses: 1. **On an injection point** (field/param): acts like Spring's `@Qualifier` — it selects the bean whose name matches the `@Named("...")` value, disambiguating among same-typed beans. `@Inject @Named("auditDataSource")` ≈ `@Autowired @Qualifier("auditDataSource")`. 2. **On a class** (as a stereotype): acts like `@Component`, making the class a Spring-managed bean. `@Named("fooService")` registers it under that name; `@Named` with no value uses the default (decapitalized class name), just like `@Component`. **`@Singleton`** (JSR-330) maps to Spring's singleton scope. Note JSR-330's *default* scope is 'prototype-like' (a new instance per injection) per the spec, but Spring's default for a @Named/@Component bean remains **singleton** unless you add another scope. (Spring keeps its own default.) ### Processing internals `AutowiredAnnotationBeanPostProcessor` is constructed to recognize `@Autowired`, `@Value`, **and** `@Inject` if `jakarta.inject.Inject` is on the classpath. `@Named` as a qualifier is understood because it is meta-annotated with the JSR-330 `@Qualifier`, and Spring's `QualifierAnnotationAutowireCandidateResolver` treats such qualifiers by bean name. `@Named`/`@ManagedBean` as stereotypes are picked up by component scanning via `ClassPathScanningCandidateComponentProvider`. ### Gotchas - **Missing dependency:** without `jakarta.inject-api` on the classpath, `@Inject`/`@Named` are simply ignored — no injection happens and you get null/unwired beans or startup failures. - **javax vs jakarta:** older code uses `javax.inject`. After the Jakarta EE 9 namespace move, Spring 6 / Boot 3 expect `jakarta.inject`. Mixing them silently breaks (the wrong-namespace annotation isn't recognized). - **No required=false equivalent** on @Inject — a common surprise for people migrating from @Autowired(required=false). - **@Named default name** follows the same decapitalization rule as @Component. ### When to use Use JSR-330 annotations when you want **container-portable** code or your team standardizes on the Jakarta standard. For Spring-only projects, native `@Autowired`/`@Qualifier`/`@Component` are more expressive (required flag, @Primary interplay, richer qualifiers). Functionally, in Spring the two sets are near-interchangeable.

  • What's a functional difference between @Inject and @Autowired?
    @Autowired has a 'required' attribute (required=false makes the dependency optional); @Inject has no such attribute and is always mandatory. For optionality with @Inject you use Optional<T> or Provider<T>.
  • Why might @Inject silently do nothing at runtime?
    Because the jakarta.inject-api dependency isn't on the classpath (Spring doesn't bundle it), or the code uses the old javax.inject namespace on a Spring 6/Boot 3 app that expects jakarta.inject.

saying these in an interview costs you the question

  • Saying Spring bundles jakarta.inject by default
  • Claiming @Inject supports required=false
  • Thinking @Named only registers beans and can't act as a qualifier

context