Skip to content
TaeyoungKim.dev

Why @Autowired is null in a new object: Spring beans vs manual construction

Java/SpringWritten 3 min readTaeyoungKim
LinkedInX

You add @Autowired, but service is null. The class works inside the application yet fails in a test that constructs it directly. The annotation has not changed behavior. Start by asking who created this particular object.

Who created the object with the null field?

MessagePrinter needs MessageService. Assume both annotated classes are within the component-scan scope:

java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Service
class MessageService {
    String text() { return "ready"; }
}

@Component
class MessagePrinter {
    @Autowired
    MessageService service;

    String print() { return service.text(); }
}

new MessagePrinter() constructs a plain Java object. Nothing assigns its service field, so that field remains null and print() throws NullPointerException. By contrast, when Spring creates and registers the MessagePrinter bean, it injects the registered MessageService. The Spring @Autowired API describes field injection after bean construction.

The diagram compares the construction paths. A direct new does not perform Spring's field injection; the scanned and registered bean receives its dependency and returns ready.

Does manual construction mean no dependency injection?

No. Passing a dependency explicitly, as in new MessagePrinter(service), is also dependency injection. The caller supplies it rather than the Spring container. A manually built instance does not automatically become a Spring bean.

Make a required dependency visible in the constructor instead of hiding it in a field:

java
@Component
class MessagePrinter {
    private final MessageService service;

    MessagePrinter(MessageService service) {
        this.service = java.util.Objects.requireNonNull(service, "service");
    }

    String print() { return service.text(); }
}

For a managed bean with one constructor, Spring can provide the dependency without an explicit @Autowired on that constructor. In a unit test, call new MessagePrinter(new MessageService()). Passing null now fails at construction rather than much later in print().

Separate two questions: was a dependency supplied, and is this object managed by Spring? A plain Java object can have an injected constructor argument without being a Spring bean.

What if a bean still seems to have a null dependency?

Search for new MessagePrinter() first. A test or utility may be using a different instance from the one held by the container. If the instance really came from Spring, check:

  1. Was MessagePrinter registered? An annotated class outside the scan scope is not discovered.
  2. Was MessageService also registered? A class definition alone is not a bean.
  3. Are you inspecting the same instance that the container supplied, rather than another manually constructed object?

The Spring component-scanning guide explains the discovery boundary. A configuration @Bean method may itself call new MessageService(), but its returned instance becomes a bean because the container registers that return value. That does not make arbitrary new calls elsewhere container-managed.

Use direct constructor injection with a test double for unit tests of behavior. Use a context test when you need to verify bean registration and wiring. Passing the unit test alone does not verify scan configuration.

Key takeaways

When an @Autowired field is null, inspect the creation path of that instance before adding annotations. A normal manual new call bypasses Spring's field injection. Constructor injection makes required dependencies explicit; if you expect a managed bean, check the scan scope and registration of both the consumer and dependency.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#Spring#@Autowired#Spring bean#Dependency injection#Constructor injection

Read next