new instead of letting Spring inject, (2) missing @Component/@Service, (3) calling injected fields before construction completes, (4) accessing a scoped bean outside its scope.null field access, then verify the class is Spring-managed (not new-instantiated).@Autowired on fields in classes created with new MyClass().new never receive injection. [src1]jakarta.* namespace — javax.inject annotations will not work. [src8]@Autowired(required=false) silently sets fields to null without any startup error — never use on required dependencies. [src7]| # | NPE Pattern | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | @Autowired field is null |
~40% | this.someService is null |
Class instantiated with new — let Spring manage the bean [src3] |
| 2 | @Autowired field null (bean exists) |
~20% | Bean class has no stereotype annotation | Add @Component/@Service/@Repository [src1] |
| 3 | NPE in constructor | ~10% | NPE in <init> method |
Move to @PostConstruct or constructor injection [src1] |
| 4 | NPE in @Scheduled |
~5% | Method is static or class not a bean |
Ensure @Component and non-static [src2] |
| 5 | NPE accessing Optional.get() |
~8% | NoSuchElementException |
Use Optional.orElseThrow() [src5] |
| 6 | NPE in filter/interceptor | ~5% | Filter outside Spring context | Register via FilterRegistrationBean [src6] |
| 7 | NPE in test | ~8% | Using @Mock without @InjectMocks |
Use @SpringBootTest + @MockBean [src4] |
| 8 | NPE with @Value |
~4% | Property not set or class not a bean | Check application.properties [src2] |
START
├── Is the NPE on a @Autowired / injected field?
│ ├── YES → Was the object created with "new"?
│ │ ├── YES → ROOT CAUSE: Spring doesn't inject into manually-created objects [src3]
│ │ │ └── FIX: Inject the bean instead of using "new"
│ │ └── NO → Does the class have @Component/@Service/@Repository?
│ │ ├── NO → ROOT CAUSE: Missing stereotype annotation [src1]
│ │ │ └── FIX: Add @Service or @Component to the class
│ │ └── YES → Is the field accessed in the constructor?
│ │ ├── YES → ROOT CAUSE: Fields not yet injected during construction [src1]
│ │ │ └── FIX: Use constructor injection or @PostConstruct
│ │ └── NO → Is class in a package scanned by @SpringBootApplication?
│ │ ├── NO → ROOT CAUSE: Class outside component scan range [src2]
│ │ │ └── FIX: Move class or add @ComponentScan
│ │ └── YES → Check for @Autowired(required=false) or @Conditional
│ └── NO → Is it Optional.get() without isPresent check?
│ ├── YES → FIX: Use Optional.orElseThrow() [src5]
│ └── NO → Is this a GraalVM native image build?
│ ├── YES → FIX: Add @RegisterReflectionForBinding or RuntimeHints [src1]
│ └── NO → Standard Java NPE — check for null references
└── DEFAULT → logging.level.org.springframework=DEBUG
The NPE stack trace tells you exactly which field is null and where. Java 17+ provides enhanced NPE messages with the exact null reference. [src2]
java.lang.NullPointerException: Cannot invoke "com.example.UserService.findById(Long)"
because "this.userService" is null
at com.example.UserController.getUser(UserController.java:25)
This means userService field is null at line 25 of UserController.
// ❌ THIS CAUSES NPE — "new" bypasses Spring DI
UserController controller = new UserController();
controller.getUser(1L); // userService is null!
// ✅ CORRECT — let Spring inject
@RestController // Spring manages this
public class UserController {
private final UserService userService;
public UserController(UserService userService) { // Constructor injection
this.userService = userService;
}
}
Verify: Check that the class has a stereotype annotation (@Component, @Service, @Repository, @Controller, @RestController, @Configuration). [src1]
// ❌ Missing @Service — Spring won't create this bean
public class UserService {
public User findById(Long id) { ... }
}
// ✅ CORRECT — @Service marks it as a Spring bean [src1]
@Service
public class UserService {
public User findById(Long id) { ... }
}
Verify: Run with --debug flag and search the auto-configuration report for the bean name.
// ❌ Field injection — harder to test, NPE if constructed manually
@RestController
public class UserController {
@Autowired
private UserService userService; // null if new UserController()
}
// ✅ Constructor injection — fail-fast, testable [src1]
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService; // guaranteed non-null
}
}
Verify: Application fails to start if the dependency is missing (fail-fast behavior). [src7]
// Main class in com.example
@SpringBootApplication // Scans com.example and sub-packages
public class MyApp { ... }
// ❌ This bean is OUTSIDE the scan range
package com.other; // NOT under com.example
@Service
public class UserService { ... } // Will NOT be discovered
// ✅ FIX: Add explicit component scan
@SpringBootApplication
@ComponentScan(basePackages = {"com.example", "com.other"})
public class MyApp { ... }
Verify: logging.level.org.springframework=DEBUG — look for "Scanning" messages. [src2]
Full script: java-complete-example-common-npe-scenario-and-fix.java (57 lines)
// SCENARIO: @Autowired field is null
// Input: Controller with field injection, instantiated manually
// Output: NullPointerException at runtime
// ❌ BAD — the helper is created with "new", so Spring doesn't inject
@RestController
public class OrderController {
@Autowired private OrderService orderService;
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
OrderValidator validator = new OrderValidator(); // PROBLEM HERE
validator.validate(id); // NPE — validator.orderRepo is null
return orderService.findById(id);
}
}
public class OrderValidator {
@Autowired private OrderRepository orderRepo; // ALWAYS NULL
public void validate(Long id) {
orderRepo.existsById(id); // NullPointerException!
}
}
// ✅ GOOD — inject the validator as a bean
@Component // Now Spring manages it
public class OrderValidator {
private final OrderRepository orderRepo;
public OrderValidator(OrderRepository orderRepo) {
this.orderRepo = orderRepo; // Guaranteed non-null
}
public void validate(Long id) {
if (!orderRepo.existsById(id)) {
throw new IllegalArgumentException("Order not found: " + id);
}
}
}
@RestController
public class OrderController {
private final OrderService orderService;
private final OrderValidator validator; // Injected by Spring
public OrderController(OrderService orderService, OrderValidator validator) {
this.orderService = orderService;
this.validator = validator;
}
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
validator.validate(id); // Works!
return orderService.findById(id);
}
}
// ❌ BAD — lateinit with field injection, NPE if not initialized
@Service
class NotificationService {
@Autowired
lateinit var emailSender: EmailSender // NPE if accessed before injection
fun notify(user: User) {
emailSender.send(user.email, "Hello") // UninitializedPropertyAccessException
}
}
// ✅ GOOD — constructor injection, null-safe
@Service
class NotificationService(
private val emailSender: EmailSender // Injected, non-null
) {
fun notify(user: User) {
emailSender.send(user.email, "Hello") // Always works
}
}
// ❌ BAD — reflection-based injection fails in native image without hints
@Service
public class ReportService {
@Autowired
private ReportGenerator generator; // NPE in native image — no reflection metadata
}
// ✅ GOOD — constructor injection + runtime hints for native image
@Service
@RegisterReflectionForBinding(ReportGenerator.class) // Spring Boot 3.x
public class ReportService {
private final ReportGenerator generator;
public ReportService(ReportGenerator generator) {
this.generator = generator; // Works in JVM and native image
}
}
new to create Spring beans// ❌ BAD — Spring can't inject into objects you create with new [src3]
OrderService service = new OrderService();
service.processOrder(orderId); // NPE — service.repo is null
// ✅ GOOD — let Spring wire everything [src1]
@Service
public class PaymentProcessor {
private final OrderService orderService; // Injected
public PaymentProcessor(OrderService orderService) {
this.orderService = orderService;
}
}
// ❌ BAD — @Autowired fields are null during construction [src1]
@Service
public class CacheWarmer {
@Autowired private UserRepository userRepo;
public CacheWarmer() {
userRepo.findAll(); // NPE — field not yet injected!
}
}
// ✅ GOOD — @PostConstruct runs after injection is complete [src1]
@Service
public class CacheWarmer {
private final UserRepository userRepo;
public CacheWarmer(UserRepository userRepo) {
this.userRepo = userRepo;
}
@PostConstruct
public void warmCache() {
userRepo.findAll(); // Works — injection is complete
}
}
// ❌ BAD — silently null if bean missing, NPE at runtime [src7]
@Service
public class PaymentService {
@Autowired(required = false)
private PaymentGateway gateway; // null without warning
public void charge(Order order) {
gateway.charge(order.getTotal()); // NPE!
}
}
// ✅ GOOD — constructor injection fails at startup if bean missing [src1]
@Service
public class PaymentService {
private final PaymentGateway gateway;
public PaymentService(PaymentGateway gateway) {
this.gateway = Objects.requireNonNull(gateway); // Belt-and-suspenders
}
public void charge(Order order) {
gateway.charge(order.getTotal()); // Always works
}
}
new keyword bypasses Spring DI: The #1 cause of @Autowired NPEs. Fix: always inject beans through constructors. [src3]@Component scan: Class in a package not scanned by @SpringBootApplication. Fix: ensure same or sub-package. [src2]@Autowired fields. Fix: make non-static. [src2]@Lazy or restructure. [src1]@Autowired in tests without @SpringBootTest results in null. Fix: use @SpringBootTest or @ExtendWith(MockitoExtension.class). [src4]Optional.get() without checking: JPA findById() returns Optional. Fix: use .orElseThrow(). [src5]@RegisterReflectionForBinding or provide RuntimeHints. [src1]# Enable Spring debug logging to see bean creation
# application.properties:
logging.level.org.springframework=DEBUG
# List all beans in the application context
# Add to main class:
@Bean
CommandLineRunner printBeans(ApplicationContext ctx) {
return args -> Arrays.stream(ctx.getBeanDefinitionNames()).sorted().forEach(System.out::println);
}
# Check component scan base packages
# Look for @SpringBootApplication or @ComponentScan in your main class
# Run with debug to see auto-configuration report
java -jar app.jar --debug
# Check if a specific bean is registered (Spring Boot Actuator)
curl http://localhost:8080/actuator/beans | jq '.contexts[].beans | keys[] | select(contains("UserService"))'
# Verify AOT processing for native image (Spring Boot 3.x)
./gradlew processAot # or: mvn spring-boot:process-aot
# Check generated sources in build/generated/aotSources
| Spring Version | Status | Key DI Changes | Migration Notes |
|---|---|---|---|
| Spring Boot 3.4+ / Spring 6.2+ | Current | Revised autowiring algorithm, stricter generic matching, @Fallback beans [src8] |
Verify generic type signatures at injection points |
| Spring Boot 3.0-3.3 / Spring 6.0-6.1 | Maintained | Jakarta namespace (javax -> jakarta), Java 17+, AOT compilation |
Update all javax.inject to jakarta.inject |
| Spring Boot 2.7 / Spring 5.3 | EOL (Nov 2023) | Single-constructor injection without @Autowired (since 4.3) |
Upgrade to Boot 3.x for continued support |
| Spring Boot 2.x / Spring 5.x | EOL | Auto-configuration, @ConditionalOnMissingBean |
— |
| Spring 4.3+ | Legacy | Implicit constructor injection for single-constructor beans [src1] | — |
| Use Constructor Injection When | Use Field Injection When | Use Setter Injection When |
|---|---|---|
| Always (recommended default) [src7] | Legacy code only (migration in progress) | Optional dependencies |
| Production code | Never in new code | Circular dependency breaking (with @Lazy) |
| Test-friendly code needed | Quick throwaway prototypes | Reconfigurable beans at runtime |
@Autowired(required = false) can introduce NPEs silently — the field will be null without any startup error. [src7]@RegisterReflectionForBinding where needed.@Qualifier matches now overrule @Priority ranking (reversed from 6.1). This can change which bean gets injected. [src8]@Fallback beans — a fallback bean is used only when no other bean of that type is defined, which can mask injection issues if not understood. [src8]