Core dependency injection interfaces and components for the Micronaut Framework
—
Micronaut's annotation system provides comprehensive support for dependency injection, bean scoping, conditional loading, and configuration binding. It combines Jakarta Inject standard annotations with Micronaut-specific enhancements for advanced features.
Marks constructors, methods, and fields for dependency injection.
@Target({METHOD, CONSTRUCTOR, FIELD})
@Retention(RUNTIME)
public @interface Inject {
}Usage Examples:
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class UserService {
// Constructor injection (preferred)
private final UserRepository repository;
private final EmailService emailService;
@Inject
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
// Field injection
@Inject
private ValidationService validationService;
// Method injection
@Inject
public void setAuditService(AuditService auditService) {
this.auditService = auditService;
}
}Marks a class as a singleton bean - only one instance will be created.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Scope
public @interface Singleton {
}Usage Examples:
import jakarta.inject.Singleton;
@Singleton
public class ConfigurationService {
public String getConfigValue(String key) {
// Implementation
return "value";
}
}
// Singleton factory method
@Singleton
public class ServiceFactory {
@Singleton
public DatabaseService createDatabaseService() {
return new DatabaseService("jdbc:postgresql://localhost/db");
}
}Provides a name qualifier for bean selection when multiple beans of the same type exist.
@Target({TYPE, METHOD, FIELD, PARAMETER})
@Retention(RUNTIME)
@Qualifier
public @interface Named {
String value() default "";
}Usage Examples:
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
// Named bean definitions
@Singleton
@Named("mysql")
public class MySQLDatabaseService implements DatabaseService {
// MySQL implementation
}
@Singleton
@Named("postgresql")
public class PostgreSQLDatabaseService implements DatabaseService {
// PostgreSQL implementation
}
// Named injection
@Singleton
public class DataProcessor {
@Inject
@Named("mysql")
private DatabaseService primaryDb;
@Inject
@Named("postgresql")
private DatabaseService backupDb;
}Marks a method or class as a bean definition, typically used in factory classes.
@Target({METHOD, TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface Bean {
boolean typed() default true;
boolean preDestroy() default true;
}Usage Examples:
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Singleton;
@Factory
public class ServiceFactory {
@Bean
@Singleton
public HttpClient createHttpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
@Bean
public ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}Marks a class as a factory for creating beans, similar to @Configuration in Spring.
@Target({TYPE})
@Retention(RUNTIME)
public @interface Factory {
}Usage Examples:
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
import jakarta.inject.Singleton;
@Factory
public class DatabaseFactory {
@Bean
@Singleton
public DataSource createDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost/mydb");
config.setUsername("user");
config.setPassword("password");
return new HikariDataSource(config);
}
@Bean
@Singleton
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}Marks a bean as primary when multiple beans of the same type exist, making it the default choice.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Primary {
}Usage Examples:
import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;
// Multiple implementations
@Singleton
public class EmailService implements NotificationService {
// Email implementation
}
@Singleton
@Primary // This will be injected by default
public class SmsService implements NotificationService {
// SMS implementation
}
@Singleton
public class NotificationController {
@Inject
private NotificationService service; // SmsService will be injected due to @Primary
}Marks a bean as prototype scoped - a new instance is created for each injection.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Scope
public @interface Prototype {
}Usage Examples:
import io.micronaut.context.annotation.Prototype;
@Prototype
public class RequestHandler {
private final String requestId;
public RequestHandler() {
this.requestId = UUID.randomUUID().toString();
}
public void processRequest() {
System.out.println("Processing request: " + requestId);
}
}
@Singleton
public class RequestProcessor {
@Inject
private RequestHandler handler; // New instance each time
}Injects configuration values using property placeholders.
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface Value {
String value();
}Usage Examples:
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
public class ApiService {
@Value("${api.base-url}")
private String baseUrl;
@Value("${api.timeout:30}") // Default value of 30
private int timeoutSeconds;
@Value("${api.key}")
private String apiKey;
// Constructor injection with @Value
public ApiService(@Value("${api.version:v1}") String apiVersion) {
this.apiVersion = apiVersion;
}
}Injects specific property values with optional defaults.
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface Property {
String name();
String defaultValue() default "";
}Usage Examples:
import io.micronaut.context.annotation.Property;
import jakarta.inject.Singleton;
@Singleton
public class DatabaseService {
private final String url;
private final String username;
private final int maxConnections;
public DatabaseService(
@Property(name = "database.url") String url,
@Property(name = "database.username") String username,
@Property(name = "database.max-connections", defaultValue = "10") int maxConnections) {
this.url = url;
this.username = username;
this.maxConnections = maxConnections;
}
}Maps configuration properties to a bean, enabling type-safe configuration.
@Target({TYPE})
@Retention(RUNTIME)
public @interface ConfigurationProperties {
String value() default "";
boolean cliPrefix() default false;
}Usage Examples:
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Min;
@ConfigurationProperties("redis")
public class RedisConfiguration {
@NotBlank
private String host = "localhost";
@Min(1)
private int port = 6379;
private String password;
private int database = 0;
private int timeout = 2000;
// Getters and setters
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getDatabase() { return database; }
public void setDatabase(int database) { this.database = database; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
}
// Usage
@Singleton
public class RedisService {
private final RedisConfiguration config;
@Inject
public RedisService(RedisConfiguration config) {
this.config = config;
}
public void connect() {
System.out.println("Connecting to Redis at " + config.getHost() + ":" + config.getPort());
}
}Conditionally loads beans based on various criteria.
@Target({PACKAGE, TYPE, METHOD})
@Retention(RUNTIME)
@Repeatable(Requirements.class)
public @interface Requires {
String property() default "";
String[] value() default {};
String defaultValue() default "";
Class<?>[] beans() default {};
Class<?>[] missingBeans() default {};
String[] env() default {};
String[] notEnv() default {};
Class<? extends Condition>[] condition() default {};
String os() default "";
String[] notOs() default {};
String sdk() default "";
String[] classes() default {};
String[] missingClasses() default {};
String[] resources() default {};
String[] missingProperties() default {};
}Usage Examples:
import io.micronaut.context.annotation.Requires;
import jakarta.inject.Singleton;
// Conditional on property value
@Singleton
@Requires(property = "cache.enabled", value = "true")
public class CacheService {
// Only created if cache.enabled=true
}
// Conditional on environment
@Singleton
@Requires(env = "prod")
public class ProductionDatabaseService implements DatabaseService {
// Only loaded in production environment
}
// Conditional on missing bean
@Singleton
@Requires(missingBeans = DatabaseService.class)
public class DefaultDatabaseService implements DatabaseService {
// Only created if no other DatabaseService exists
}
// Conditional on class presence
@Singleton
@Requires(classes = "org.springframework.data.redis.core.RedisTemplate")
public class RedisIntegrationService {
// Only created if Redis classes are on classpath
}
// Multiple conditions
@Singleton
@Requires(property = "features.analytics", value = "true")
@Requires(env = {"prod", "staging"})
@Requires(beans = DatabaseService.class)
public class AnalyticsService {
// Created only if all conditions are met
}Groups multiple @Requires conditions.
@Target({PACKAGE, TYPE, METHOD})
@Retention(RUNTIME)
public @interface Requirements {
Requires[] value();
}Usage Examples:
import io.micronaut.context.annotation.Requirements;
import io.micronaut.context.annotation.Requires;
@Singleton
@Requirements({
@Requires(property = "email.enabled", value = "true"),
@Requires(classes = "javax.mail.internet.MimeMessage"),
@Requires(beans = EmailConfiguration.class)
})
public class EmailService {
// Created only if all requirements are satisfied
}import io.micronaut.context.annotation.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Database {
DatabaseType value();
}
public enum DatabaseType {
MYSQL, POSTGRESQL, H2
}
// Usage
@Singleton
@Database(DatabaseType.MYSQL)
public class MySQLService implements DatabaseService {
// MySQL implementation
}
@Singleton
@Database(DatabaseType.POSTGRESQL)
public class PostgreSQLService implements DatabaseService {
// PostgreSQL implementation
}
@Singleton
public class DataService {
@Inject
@Database(DatabaseType.MYSQL)
private DatabaseService mysqlService;
@Inject
@Database(DatabaseType.POSTGRESQL)
private DatabaseService postgresService;
}Marks methods to be called after bean construction and dependency injection.
@Target(METHOD)
@Retention(RUNTIME)
public @interface PostConstruct {
}Marks methods to be called before bean destruction.
@Target(METHOD)
@Retention(RUNTIME)
public @interface PreDestroy {
}Usage Examples:
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;
@Singleton
public class DatabaseConnectionManager {
private Connection connection;
@PostConstruct
public void initialize() {
System.out.println("Initializing database connection...");
this.connection = DriverManager.getConnection("jdbc:h2:mem:test");
}
@PreDestroy
public void cleanup() {
System.out.println("Closing database connection...");
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
System.err.println("Error closing connection: " + e.getMessage());
}
}
public void executeQuery(String sql) {
// Use connection
}
}import io.micronaut.context.scope.CustomScope;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@CustomScope
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScope {
}
// Implementation of the custom scope
@Singleton
public class RequestScopeImpl implements CustomScope<RequestScope> {
private final Map<String, Object> requestBeans = new ConcurrentHashMap<>();
@Override
public Class<RequestScope> annotationType() {
return RequestScope.class;
}
@Override
public <T> T get(BeanCreationContext<T> creationContext) {
String key = creationContext.id().toString();
return (T) requestBeans.computeIfAbsent(key, k ->
creationContext.create()
);
}
public void clearScope() {
requestBeans.clear();
}
}
// Usage
@RequestScope
public class RequestDataHolder {
private Map<String, Object> data = new HashMap<>();
public void setData(String key, Object value) {
data.put(key, value);
}
public Object getData(String key) {
return data.get(key);
}
}Utility class for creating qualifiers programmatically.
public final class Qualifiers {
public static <T> Qualifier<T> byName(String name);
public static <T> Qualifier<T> byType(Class<?>... types);
public static <T> Qualifier<T> byAnnotation(Annotation annotation);
public static <T> Qualifier<T> byTypeArguments(Class<?>... types);
public static <T> Qualifier<T> byTypeArgumentsClosest(Class<?>... types);
}Usage Examples:
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.Qualifier;
import io.micronaut.inject.qualifiers.Qualifiers;
public class QualifierExample {
public static void main(String[] args) {
try (ApplicationContext context = ApplicationContext.run()) {
// Get bean by name qualifier
Qualifier<DatabaseService> nameQualifier = Qualifiers.byName("mysql");
DatabaseService mysqlService = context.getBean(DatabaseService.class, nameQualifier);
// Get bean by annotation qualifier
Database databaseAnnotation = () -> DatabaseType.POSTGRESQL;
Qualifier<DatabaseService> annotationQualifier = Qualifiers.byAnnotation(databaseAnnotation);
DatabaseService postgresService = context.getBean(DatabaseService.class, annotationQualifier);
}
}
}Creates a bean for each bean of the specified type that exists in the context.
@Target({TYPE})
@Retention(RUNTIME)
@Documented
public @interface EachBean {
/**
* The bean type to iterate over
*/
Class<?> value();
}Usage Examples:
import io.micronaut.context.annotation.EachBean;
@EachBean(DataSource.class)
@Singleton
public class DatabaseService {
private final DataSource dataSource;
public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getConnection() {
return dataSource.getConnection();
}
}
// If there are 3 DataSource beans, 3 DatabaseService beans will be createdCreates a bean for each property prefix that exists in configuration.
@Target({TYPE})
@Retention(RUNTIME)
@Documented
public @interface EachProperty {
/**
* The property prefix to iterate over
*/
String value();
/**
* Primary bean marker
*/
boolean primary() default false;
}Usage Examples:
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.ConfigurationProperties;
@EachProperty("datasources")
@ConfigurationProperties("datasources")
public class DataSourceConfig {
private String url;
private String username;
private String password;
// getters and setters
}
// Configuration:
// datasources.primary.url=jdbc:h2:mem:primary
// datasources.secondary.url=jdbc:h2:mem:secondary
// Creates DataSourceConfig beans for "primary" and "secondary"Enables configuration of complex objects through builder pattern integration.
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface ConfigurationBuilder {
/**
* Property prefix for configuration values
*/
String value() default "";
/**
* Configuration prefix for nested configuration
*/
String configurationPrefix() default "";
/**
* Method prefixes to include
*/
String[] includes() default {};
/**
* Method prefixes to exclude
*/
String[] excludes() default {};
/**
* Whether to allow zero args methods
*/
boolean allowZeroArgs() default false;
}Usage Examples:
import io.micronaut.context.annotation.ConfigurationBuilder;
import io.micronaut.context.annotation.ConfigurationProperties;
@ConfigurationProperties("redis")
public class RedisConfig {
@ConfigurationBuilder(configurationPrefix = "jedis")
private final JedisPoolConfig jedisConfig = new JedisPoolConfig();
@ConfigurationBuilder(configurationPrefix = "lettuce",
includes = {"timeout", "database"})
private final LettuceConnectionFactory.Builder lettuceBuilder =
LettuceConnectionFactory.builder();
public JedisPoolConfig getJedisConfig() {
return jedisConfig;
}
public LettuceConnectionFactory.Builder getLettuceBuilder() {
return lettuceBuilder;
}
}
// Configuration:
// redis.jedis.max-total=20
// redis.jedis.max-idle=10
// redis.lettuce.timeout=5000
// redis.lettuce.database=0Marks methods as executable for reflection-free method invocation.
@Target({METHOD})
@Retention(RUNTIME)
@Documented
public @interface Executable {
/**
* Whether the method can be invoked when reflection is not available
*/
boolean processOnStartup() default false;
}Usage Examples:
import io.micronaut.context.annotation.Executable;
@Singleton
public class BusinessService {
@Executable
public String processData(String input) {
return "Processed: " + input;
}
@Executable(processOnStartup = true)
public void initialize() {
System.out.println("Service initialized");
}
// Non-executable method - won't be optimized
private void internalMethod() {
// Internal logic
}
}Enables automatic bean mapping functionality.
@Target({TYPE})
@Retention(RUNTIME)
@Documented
public @interface Mapper {
/**
* The mapping strategy
*/
Strategy strategy() default Strategy.DEFAULT;
/**
* Configuration for the mapper
*/
String config() default "";
enum Strategy {
DEFAULT, CONSTRUCTOR, SETTER, FIELD
}
}Usage Examples:
import io.micronaut.context.annotation.Mapper;
@Mapper
public interface UserMapper {
UserDto toDto(User user);
User fromDto(UserDto dto);
@Mapping(target = "fullName", source = "firstName,lastName")
UserSummary toSummary(User user);
}
// Micronaut generates implementation at compile timeQualifier that matches any bean of the specified type.
@Target({FIELD, PARAMETER, METHOD, TYPE})
@Retention(RUNTIME)
@Qualifier
public @interface Any {
}Marks a bean as secondary - used when @Primary is not available.
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Secondary {
}Usage Examples:
import io.micronaut.context.annotation.Secondary;
@Singleton
@Primary
public class PrimaryEmailService implements EmailService {
// Primary implementation
}
@Singleton
@Secondary
public class BackupEmailService implements EmailService {
// Secondary implementation - used if primary fails
}public interface AnnotationMetadata {
<T extends Annotation> Optional<T> getAnnotation(Class<T> annotationClass);
boolean hasAnnotation(Class<? extends Annotation> annotation);
boolean hasStereotype(Class<? extends Annotation> stereotype);
Set<String> getAnnotationNames();
Set<String> getDeclaredAnnotationNames();
OptionalValues<Object> getValues(String annotation);
<T> Optional<T> getValue(String annotation, Class<T> requiredType);
String[] stringValues(Class<? extends Annotation> annotation);
String[] stringValues(String annotation, String member);
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-micronaut--micronaut-inject