How to Use Dependency Injection in Micronaut

Micronaut Dependency Injection

In this article, we will explore the different types of dependency injection available in Micronaut.

Table of contents

Technologies used:

  • Java 21
  • Micronaut 4.7.6
  • Maven 3.x

The Micronaut Framework implements the JSR-330 – Dependency Injection for Java specification

1. Constructor Injection

Constructor injection is the recommended way to inject dependencies in Micronaut. It ensures that the required dependencies are available when an object is created.

Injecting a Single Dependency

In constructor-based injection, Micronaut automatically provides the necessary dependencies when an instance is created.

Service Interface

GreetingService.java

package com.mkyong.Services;

public interface GreetingService {
    String speak(String words);
}

Implementation Class

EnglistGreetingService.java

package com.mkyong.Services;

import jakarta.inject.Singleton;

@Singleton
public class EnglistGreetingService implements GreetingService{
    @Override
    public String speak(String words) {
        return "Hello " + words;
    }
}

Controller

AppController.java

package com.mkyong.controllers;

import com.mkyong.Services.GreetingService;
import com.mkyong.Services.TimeService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Named;

@Controller("/app")
public class App2Controller {
    private final GreetingService greetingService;

    // injects EnglistGreetingService
    public AppController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @Get("/say")
    public String sayHello() {
        return greetingService.speak("Micronaut");
    }

}

Injecting Multiple Dependencies

Additional Service Interface

TimeService.java

package com.mkyong.Services;

public interface TimeService {
    String getCurrentTime();
}

Implementation Class

DefaultTimeService.java

package com.mkyong.Services;

import jakarta.inject.Singleton;
import java.time.LocalTime;

@Singleton
public class DefaultTimeService implements TimeService {
    public String getCurrentTime() {
        return LocalTime.now().toString();
    }
}

Updated Controller

AppController.java

package com.mkyong.controllers;

import com.mkyong.Services.GreetingService;
import com.mkyong.Services.TimeService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/app")
public class AppController {
private final GreetingService greetingService;
private final TimeService timeService;

    // Micronaut find and injects required dependencies
    public AppController(GreetingService greetingService, TimeService timeService) {
        this.greetingService = greetingService;
        this.timeService = timeService;
    }

    @Get("/say")
    public String sayHello() {
        return greetingService.speak("Micronaut") + " at " + timeService.getCurrentTime();
    }

}

Handling Multiple Implementations

When multiple implementations exist, we can use @Named("BeanName") to specify which one to inject. Alternatively, @Primary marks the default implementation.

English Greeting Service (Default)

EnglistGreetingService.java

package com.mkyong.Services;

import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;

@Singleton
@Primary // default
public class EnglistGreetingService implements GreetingService{
    @Override
    public String speak(String words) {
        return "Hello " + words;
    }
}

Spanish Greeting Service

SpanishGreetingService.java

package com.mkyong.Services;

import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Singleton
@Named("Spanish")
public class SpanishGreetingService implements GreetingService {
    @Override
    public String speak(String words) {
        return "Hola " + words;
    }
}

Controller Using Spanish Service

AppController.java

package com.mkyong.controllers;

import com.mkyong.Services.GreetingService;
import com.mkyong.Services.TimeService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Named;

@Controller("/app")
public class AppController {
    private final GreetingService greetingService;
    private final TimeService timeService;

    public AppController(@Named("Spanish") GreetingService greetingService, 
                         TimeService timeService) {
        this.greetingService = greetingService;
        this.timeService = timeService;
    }

    @Get("/say")
    public String sayHello() {
        return greetingService.speak("Micronaut") + " at " + timeService.getCurrentTime();
    }

}

2. Field Injection

Field injection allows dependencies to be injected directly into class fields using the @Inject annotation. However, it is not recommended as it makes testing difficult.

App3Controller.java

package com.mkyong.controllers;

import com.mkyong.Services.GreetingService;
import com.mkyong.Services.TimeService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;

@Controller("/app3")
public class App3Controller {

    // field injection
    @Inject
    private GreetingService greetingService;

    @Inject
    private TimeService timeService;

    @Get("/say")
    public String sayHello() {
        return greetingService.speak("Micronaut") + " at "
                + timeService.getCurrentTime();
    }

}

Why Avoid Field Injection?

  • Harder to test because dependencies cannot be directly provided in constructors.
  • Breaks immutability as injected fields must be non-final.

3. Method Injection

Method injection enables dependency injection via setter methods using the jakarta.inject.Inject annotation.

App4Controller.java

package com.mkyong.controllers;

import com.mkyong.Services.GreetingService;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;

@Controller("/app4")
public class App4Controller {

    private GreetingService greetingService;

    // method injection
    @Inject
    public void setGreetingService(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @Get("/say")
    public String sayHello() {
        return greetingService.speak("Micronaut");
    }

}

When to Use Method Injection?

  • When dependencies are optional.
  • When a class has existing setter methods.

4. Compile-Time vs. Runtime Dependency Injection in Micronaut

Micronaut’s dependency injection happens at compile-time, unlike Spring, which resolves dependencies at runtime using reflection. This improves startup time and reduces memory consumption.

How does Micronaut achieve compile-time DI?

  • Ahead-of-Time (AOT) Compilation: Micronaut processes annotations at compile-time to generate dependency injection metadata.
  • No Reflection: Since dependencies are resolved during compilation, there is no runtime scanning, reducing memory usage and startup time.
  • Fast Execution: Applications start faster and consume fewer resources compared to runtime-based DI frameworks.

When does runtime DI still occur?

While most DI happens at compile-time, some scenarios still involve runtime processing:

  • Dynamic Bean Resolution: If a bean is looked up dynamically via ApplicationContext.getBean(Class<T>), it happens at runtime.
  • Conditional Beans: Beans that depend on runtime conditions (e.g., environment variables) may be resolved at runtime.
  • Lazy Initialization: When using Provider<T>, the actual instantiation happens only when get() is called, leading to a form of runtime injection.

5. Unit Testing Dependency Injection

Using constructor injection makes unit testing easy by allowing dependencies to be replaced with mocks.

AppControllerTest.java

package com.mkyong;

import com.mkyong.Services.GreetingService;
import com.mkyong.Services.TimeService;
import com.mkyong.controllers.AppController;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.*;

@MicronautTest
class AppControllerTest {

    @Test
    void testGreeting() {
        GreetingService mockService = mock(GreetingService.class);
        TimeService mockTimeService = mock(TimeService.class);

        when(mockService.speak("Micronaut")).thenReturn("Hello, Micronaut");
        when(mockTimeService.getCurrentTime()).thenReturn("10:00 AM");

        AppController controller = new AppController(mockService, mockTimeService);
        controller.sayHello();

        verify(mockService, times(1)).speak("Micronaut");
        verify(mockTimeService, times(1)).getCurrentTime();
    }

}

6. Download Source Code

7. References

mkyong

Founder of Mkyong.com, passionate Java and open-source technologies. If you enjoy my tutorials, consider making a donation to these charities.

0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments