This article body in src/content/blog/ja/what-is-di.md is the Japanese original — translating it is a standalone content task, so I’ll just produce the English translation directly.

What is Dependency Injection

This article organizes the meaning and benefits of DI (Dependency Injection), along with how to write it in Spring Boot, in an easy-to-understand way, taking the first step toward designs that are easy to test.

What Is Dependency Injection

Dependency injection is a design approach where a class receives another object it needs from the outside, rather than creating it itself.

Dependency Injection (DI) is translated into Japanese as “依存性の注入” (or “依存性注入”, “依存関係の注入”).

Put simply, it’s the idea that “a class doesn’t create the other object it depends on (its dependency) by itself — it receives it from the outside instead.”

For example, suppose an OrderService that processes orders needs a PaymentGateway for payment processing. This PaymentGateway is the dependency, and passing it in from the outside is the injection.

What “Dependency” and “Injection” Actually Mean

These terms can feel stiff at first, so let’s break them down here.

  • Dependency: Another object that a class needs in order to function. Since OrderService can’t complete an order without PaymentGateway, PaymentGateway is a dependency of OrderService.
  • Dependency / Dependency relationship: Both are translations of the English word “dependency” and are used almost interchangeably in practice. “Dependency injection” refers to the same thing either way.
  • Injection: Passing that dependency in from the outside, rather than new-ing it up inside the class. In Spring Boot, the IoC container plays this “passing it in” role.

The Relationship Between Spring Framework DI and Spring Boot DI

“Spring’s DI” and “Spring Boot’s DI” aren’t separate things. The core of Dependency Injection is the Spring Framework’s IoC container (ApplicationContext), and Spring Boot uses that container as-is. What Spring Boot adds on top is automated component scanning via @SpringBootApplication and auto-configuration, which automatically registers Beans for commonly used libraries.

So your knowledge of Spring Framework’s dependency injection carries over directly to Spring Boot, and the content of this article applies equally well if you read it as being about Spring Framework alone. The distinctions between @Component / @Service / @Repository for registering Beans in the container are explained in detail in What is @Component? How It Differs from @Bean.

5 Benefits of Using DI

First, let’s summarize what you gain by using DI.

  • Easier to write tests: You can swap in fakes/mocks for unit testing instead of calling a real database or external API
  • Easier to swap implementations: Even when switching your payment platform from Stripe to PayPay, you don’t need to modify the OrderService code
  • Dependencies become visible: Since dependencies are listed as constructor arguments, it’s immediately clear what a class relies on
  • No wasted instances: Thanks to Spring’s Singleton scope, the same Bean is basically reused just once throughout the app
  • Natural separation of responsibilities: Writing code with the assumption that “dependencies are passed in from outside” discourages cramming too much into a single class

What Goes Wrong Without DI

The work of later rewriting existing code into this form is called “DI-ifying.” Let’s look at what problems tend to occur without it.

Without DI, you tend to new dependency objects directly inside a class.

public class OrderService {
    private final PaymentGateway paymentGateway = new StripePaymentGateway();

    public void checkout() {
        paymentGateway.pay();
    }
}

This looks simple at first glance, but it tends to cause the following problems:

  • If you want to change the payment method (e.g., Stripe → PayPay), you need to directly modify OrderService
  • There’s a risk of accidentally calling “real payment processing” during tests
  • The more dependencies you have, the harder it becomes to see what a class depends on
  • The more places use the same dependency, the more new calls are scattered around, creating unnecessary instances

DI is the fundamental technique for avoiding this “fragile to change” and “hard to test” state.

What You Gain by Passing Dependencies In From Outside

With DI, you pass the dependency in from outside.

public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public void checkout() {
        paymentGateway.pay();
    }
}

Just this change significantly improves the design.

  • OrderService doesn’t need to know the “concrete implementation” of PaymentGateway
  • You can swap in StripePaymentGateway in production and FakePaymentGateway in tests
  • Since dependencies appear as constructor arguments, the structure becomes easier to read

This sense of “depending on an abstraction (interface) rather than a concrete implementation” is a powerful one to develop alongside DI.

Spring Boot Avoids Creating Unnecessary Instances

This is precisely one of the major benefits of DI (or more accurately, Spring’s container management).

Classes registered with @Component / @Service / @Repository in Spring Boot become Singleton scoped unless otherwise specified. In other words, “while the app is running, basically only one instance of the same Bean is created, and it gets reused.”

@Service
public class OrderService {
    public String status() {
        return "ok";
    }
}

@RestController
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
}

@RestController
public class AdminController {
    private final OrderService orderService;

    public AdminController(OrderService orderService) {
        this.orderService = orderService;
    }
}

In this case, OrderService is basically created once, and the same instance is passed to both OrderController and AdminController.

On the other hand, if you skip DI and new OrderService() in various places, you end up with as many instances as there are call sites. For heavy objects (DB connections, external API clients, classes holding a large amount of configuration, etc.), this quietly affects startup time and memory usage too.

There Are Exceptions

It’s not that there’s “always exactly one” — scope can be changed as needed. For example, setting it to prototype creates a new instance every time it’s injected.

@Service
@Scope("prototype")
public class ReportBuilder {
}

So it’s more accurate to remember it as “the default is Singleton, so instances don’t get created unnecessarily.” The behavior and use cases for each scope — prototype / request / session, etc. — are summarized in The Complete Guide to Spring Boot Bean Scopes.

Main Methods of DI Injection

Comparing the Three Injection Methods

Here’s a quick-reference table for when you’re unsure which to choose. In practice, it’s safest to build around constructor injection.

Injection MethodRecommendationTestabilityCan Use finalMain Use Case
Constructor injection◎HighYesRequired dependencies, the standard form for production code
Field injection△LowNoMostly encountered when reading existing code
Setter injection○MediumNoOptional dependencies, swapping for tests

Constructor Injection

This is the most recommended approach. It makes clear that a dependency is required, is easy to make final, and is easy to test.

@Service
public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

Field Injection

This also appears to be widely used. It’s easy to write, but since the dependency doesn’t appear in the constructor, the relationship is harder to see, and you can’t make it final. You’d also need to use reflection to inject dependencies during testing, so it’s best avoided in new code.

@Service
public class OrderService {
    @Autowired
    private PaymentGateway paymentGateway;
}

Setter Injection

This is a viable option when a dependency is “optional,” but it’s not suited for required dependencies.

@Service
public class OrderService {
    private PaymentGateway paymentGateway;

    @Autowired
    public void setPaymentGateway(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

How DI Works in Spring Boot

In Spring Boot, the IoC container (Spring container) handles object creation and dependency resolution.

  • IoC (Inversion of Control) means “inversion of control” — the idea that the initiative for creating objects shifts from the application side to the framework side
  • DI is one of the concrete mechanisms for realizing that IoC

A common approach in Spring Boot is to annotate a class so it gets registered with the container.

public interface PaymentGateway {
    void pay();
}

@Component
public class StripePaymentGateway implements PaymentGateway {
    @Override
    public void pay() {
        System.out.println("Pay with Stripe");
    }
}

@Service
public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

When you start the app in this state, Spring does the following for you:

  • Creates StripePaymentGateway and registers it with the container
  • Notices that PaymentGateway is needed when creating OrderService, and automatically injects it
  • By default, creates one instance of a registered Bean and reuses it (Singleton)

The Pattern of Registering Dependencies with @Bean

The basics of @Configuration classes and @Bean methods are explained in detail in What is @Configuration?. Reading that alongside this article will give you the full picture of Bean registration.

For classes you can’t annotate, such as those from external libraries, use @Configuration and @Bean.

@Configuration
public class AppConfig {

    @Bean
    public PaymentGateway paymentGateway() {
        return new StripePaymentGateway();
    }
}

This brings PaymentGateway under Spring container management too, making it injectable into other classes.

Testing Is Where You Really Feel the Benefits of DI

For Spring Boot testing in general (@SpringBootTest / @WebMvcTest / how to use Mockito), see Getting Started with Spring Boot Unit Testing Using JUnit and Mockito. For @MockitoBean, used in place of @MockBean from Spring Boot 3.4 onward, see Migration Guide from @MockBean to @MockitoBean.

The benefit of DI is felt most strongly in testing.

For example, in tests you don’t want to call real payment processing, so you pass in a fake instead.

class FakePaymentGateway implements PaymentGateway {
    boolean called = false;

    @Override
    public void pay() {
        called = true;
    }
}

@Test
void checkout_calls_payment() {
    FakePaymentGateway fake = new FakePaymentGateway();
    OrderService service = new OrderService(fake);

    service.checkout();

    assertTrue(fake.called);
}

Simply being able to “pass it in from outside” makes tests safe, fast, and easy to write.

Points Where Beginners Tend to Get Stuck

When There Are Multiple Implementations, Spring Can’t Decide Which to Inject

A name specified like @Component("stripe") is called a Bean name. The naming rules and the relationship with @Qualifier are explained in detail in An Introduction to Spring Bean “Names”.

If there are two or more implementations of PaymentGateway, Spring can’t determine which one to inject, resulting in an error. In that case, use @Qualifier to specify which one.

@Component("stripe")
public class StripePaymentGateway implements PaymentGateway { /* ... */ }

@Component("paypal")
public class PaypalPaymentGateway implements PaymentGateway { /* ... */ }

@Service
public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(@Qualifier("stripe") PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

Circular References Preventing Startup

A situation where A needs B, and B needs A, is called a circular reference. From Spring Boot 2.6 onward, circular references are disabled by default, and startup halts with an error like the following:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  orderService defined in file [.../OrderService.class]
↑     ↓
|  inventoryService defined in file [.../InventoryService.class]
└─────┘

Internally, a BeanCurrentlyInCreationException is thrown. This is a strong sign of a design smell, so it’s recommended to consider addressing it in the following order:

  1. Split up responsibilities (the real fix): Extract the logic that both A and B use into a third class, C, and restructure the dependencies to be one-directional: A → C, B → C. Circular references are almost always the result of cramming too much responsibility into a single class.
  2. Break the dependency direction with events: Logic like “update inventory after an order is confirmed” can be handled by notifying via ApplicationEvent, so services don’t need to call each other directly.
  3. Delay creation with @Lazy (a stopgap): Adding @Lazy to one of the constructor arguments injects a proxy, allowing startup to succeed. However, the underlying design issue remains, so this should only be a temporary fix.

You can also disable the restriction with spring.main.allow-circular-references=true, but since this isn’t a real fix, it’s safer to avoid using it in new code.

Giving a Singleton Bean State

Understanding when a Bean is created, initialized, and destroyed (@PostConstruct / @PreDestroy) will help you grasp this issue faster. See Spring Boot Bean Lifecycle and @PostConstruct / @PreDestroy for details.

Since Spring Beans are Singleton by default, holding state in a field (e.g., a counter or temporary data) can be shared across multiple requests, leading to unexpected bugs.

It’s safe to remember: “services should basically be stateless.” If state is needed, confine it to a local variable within a method, or reconsider the scope.

Summary

  • DI is a design where a class receives the objects it depends on from the outside
  • Avoiding direct creation with new makes code more resilient to change and easier to test
  • In Spring Boot, the IoC container takes over creation and injection for you
  • The default Singleton scope keeps instances of the same class from being created unnecessarily
  • In practice, building around constructor injection as the default keeps things stable

Once DI clicks for you, Spring Boot code becomes noticeably easier to read. Next, start paying attention to “where turning something into an interface would make it easier to swap out” — that’s where your design skills will really take off!

Current Best Practices in Spring Boot 3.x

Note (also applies to Spring Boot 4.x): Everything covered here still applies in Spring Boot 4.x / Spring Framework 7. The official explanation is covered in the Spring Boot Reference: Spring Beans and Dependency Injection. For setup steps and caveats around Lombok, including @RequiredArgsConstructor, see How to Use Lombok in Spring Boot.

Finally, here are a few supplementary notes on current conventions (as of Spring Boot 3.x / Java 17+).

  • Omitting @Autowired is now the mainstream approach: Since Spring Framework 4.3, if a class has only one constructor, it gets injected automatically even without writing the annotation.
  • Pairs well with Lombok’s @RequiredArgsConstructor: If you only declare final fields, Lombok generates the constructor for you, dramatically reducing boilerplate.
  • Be careful not to confuse this with config binding via record: A record used for @ConfigurationProperties and a Bean targeted for DI serve different roles. It’s safe to keep in mind: services and repositories are what you want passed via DI, while holding configuration values is the job of record + @ConfigurationProperties.
@Service
@RequiredArgsConstructor // Lombok generates the constructor
public class OrderService {
    private final PaymentGateway paymentGateway;
}

If you want to further decouple your service layer after introducing DI, combining it with How to Decouple Modules in Spring Boot Using ApplicationEvent or How to Create Custom Validation Annotations for extracting input validation will expand your design options.