Spring Boot
@Autowired
Dependency Injection
Java
Package Structure

Spring Boot autowired does not work, classes in different package

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

Introduction

When @Autowired stops working across packages in Spring Boot, the problem is usually not the package boundary by itself. The real issue is that Spring is not creating the target class as a bean, or it is not scanning the package where that bean lives.

Spring Boot scans components starting from the package of the main application class and its subpackages. If your bean class lives outside that tree, or if it is missing a Spring stereotype annotation, dependency injection will fail.

Make Sure the Target Class Is a Bean

@Autowired can only inject objects that Spring knows how to create. That means the target class needs to be registered as a bean, either through an annotation or explicit configuration.

java
1package com.example.service;
2
3import org.springframework.stereotype.Service;
4
5@Service
6public class GreetingService {
7    public String greet() {
8        return "hello";
9    }
10}

And the consuming class:

java
1package com.example.web;
2
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.web.bind.annotation.GetMapping;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8public class GreetingController {
9
10    @Autowired
11    private GreetingService greetingService;
12
13    @GetMapping("/hello")
14    public String hello() {
15        return greetingService.greet();
16    }
17}

If GreetingService is not annotated with @Service, @Component, @Repository, or configured as a bean manually, Spring has nothing to inject.

Keep the Main Application Class at the Package Root

The most common package-related fix is to place the main application class high enough in the package tree so Spring Boot scans everything underneath it.

java
1package com.example;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6@SpringBootApplication
7public class Application {
8    public static void main(String[] args) {
9        SpringApplication.run(Application.class, args);
10    }
11}

If the main class sits in com.example, Spring Boot scans com.example.* by default. If you put it in com.example.web, then sibling packages such as com.example.service are no longer automatically included.

That is why package layout matters: it affects component scanning boundaries.

Use @ComponentScan When the Layout Is Different

If your package structure cannot be rearranged cleanly, specify the scan base packages explicitly.

java
1package com.example.web;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5import org.springframework.context.annotation.ComponentScan;
6
7@SpringBootApplication
8@ComponentScan(basePackages = {
9    "com.example.web",
10    "com.example.service"
11})
12public class Application {
13    public static void main(String[] args) {
14        SpringApplication.run(Application.class, args);
15    }
16}

This tells Spring where to look, even when the main class is not at the natural package root.

Constructor injection is also worth preferring in modern code because missing dependencies fail more explicitly:

java
1@RestController
2public class GreetingController {
3    private final GreetingService greetingService;
4
5    public GreetingController(GreetingService greetingService) {
6        this.greetingService = greetingService;
7    }
8}

Read the Error Message Closely

Spring usually tells you what went wrong. A message such as No qualifying bean of type means scanning or bean registration failed. A message about multiple beans means Spring found more than one candidate and now needs @Qualifier or a primary bean.

That distinction saves time. If zero beans are found, look at package scanning and annotations. If too many are found, the package setup is probably fine and the problem is bean selection instead.

Common Pitfalls

The biggest mistake is assuming package separation alone breaks @Autowired. The real issue is usually missing component scanning or missing bean registration.

Another common problem is placing the @SpringBootApplication class too deep in the package tree so some sibling packages are never scanned.

It is also easy to forget that plain Java objects are not beans automatically. Without a Spring stereotype or explicit @Bean method, injection cannot work.

Finally, field injection can hide problems until runtime. Constructor injection usually makes missing beans easier to diagnose.

Summary

  • '@Autowired only works for classes that Spring has registered as beans.'
  • Spring Boot scans from the package of the main application class downward.
  • Put the main class near the package root when possible.
  • Use @ComponentScan if your package layout falls outside the default scan path.
  • Prefer constructor injection for clearer dependency wiring.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

All Rights Reserved.