or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdapplication-context.mdbean-definition.mdbean-factory.mdbean-processing.mdbean-providers.mdenvironment-config.mdevents.mdexceptions.mdindex.mdscoping.md

annotations.mddocs/

0

# Annotations and Qualifiers

1

2

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.

3

4

## Jakarta Inject Annotations

5

6

### @Inject

7

8

Marks constructors, methods, and fields for dependency injection.

9

10

```java { .api }

11

@Target({METHOD, CONSTRUCTOR, FIELD})

12

@Retention(RUNTIME)

13

public @interface Inject {

14

}

15

```

16

17

**Usage Examples:**

18

19

```java

20

import jakarta.inject.Inject;

21

import jakarta.inject.Singleton;

22

23

@Singleton

24

public class UserService {

25

26

// Constructor injection (preferred)

27

private final UserRepository repository;

28

private final EmailService emailService;

29

30

@Inject

31

public UserService(UserRepository repository, EmailService emailService) {

32

this.repository = repository;

33

this.emailService = emailService;

34

}

35

36

// Field injection

37

@Inject

38

private ValidationService validationService;

39

40

// Method injection

41

@Inject

42

public void setAuditService(AuditService auditService) {

43

this.auditService = auditService;

44

}

45

}

46

```

47

48

### @Singleton

49

50

Marks a class as a singleton bean - only one instance will be created.

51

52

```java { .api }

53

@Target({TYPE, METHOD})

54

@Retention(RUNTIME)

55

@Scope

56

public @interface Singleton {

57

}

58

```

59

60

**Usage Examples:**

61

62

```java

63

import jakarta.inject.Singleton;

64

65

@Singleton

66

public class ConfigurationService {

67

public String getConfigValue(String key) {

68

// Implementation

69

return "value";

70

}

71

}

72

73

// Singleton factory method

74

@Singleton

75

public class ServiceFactory {

76

77

@Singleton

78

public DatabaseService createDatabaseService() {

79

return new DatabaseService("jdbc:postgresql://localhost/db");

80

}

81

}

82

```

83

84

### @Named

85

86

Provides a name qualifier for bean selection when multiple beans of the same type exist.

87

88

```java { .api }

89

@Target({TYPE, METHOD, FIELD, PARAMETER})

90

@Retention(RUNTIME)

91

@Qualifier

92

public @interface Named {

93

String value() default "";

94

}

95

```

96

97

**Usage Examples:**

98

99

```java

100

import jakarta.inject.Named;

101

import jakarta.inject.Singleton;

102

import jakarta.inject.Inject;

103

104

// Named bean definitions

105

@Singleton

106

@Named("mysql")

107

public class MySQLDatabaseService implements DatabaseService {

108

// MySQL implementation

109

}

110

111

@Singleton

112

@Named("postgresql")

113

public class PostgreSQLDatabaseService implements DatabaseService {

114

// PostgreSQL implementation

115

}

116

117

// Named injection

118

@Singleton

119

public class DataProcessor {

120

121

@Inject

122

@Named("mysql")

123

private DatabaseService primaryDb;

124

125

@Inject

126

@Named("postgresql")

127

private DatabaseService backupDb;

128

}

129

```

130

131

## Micronaut Bean Annotations

132

133

### @Bean

134

135

Marks a method or class as a bean definition, typically used in factory classes.

136

137

```java { .api }

138

@Target({METHOD, TYPE, ANNOTATION_TYPE})

139

@Retention(RUNTIME)

140

public @interface Bean {

141

boolean typed() default true;

142

boolean preDestroy() default true;

143

}

144

```

145

146

**Usage Examples:**

147

148

```java

149

import io.micronaut.context.annotation.Bean;

150

import io.micronaut.context.annotation.Factory;

151

import jakarta.inject.Singleton;

152

153

@Factory

154

public class ServiceFactory {

155

156

@Bean

157

@Singleton

158

public HttpClient createHttpClient() {

159

return HttpClient.newBuilder()

160

.connectTimeout(Duration.ofSeconds(10))

161

.build();

162

}

163

164

@Bean

165

public ObjectMapper createObjectMapper() {

166

ObjectMapper mapper = new ObjectMapper();

167

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

168

return mapper;

169

}

170

}

171

```

172

173

### @Factory

174

175

Marks a class as a factory for creating beans, similar to @Configuration in Spring.

176

177

```java { .api }

178

@Target({TYPE})

179

@Retention(RUNTIME)

180

public @interface Factory {

181

}

182

```

183

184

**Usage Examples:**

185

186

```java

187

import io.micronaut.context.annotation.Factory;

188

import io.micronaut.context.annotation.Bean;

189

import jakarta.inject.Singleton;

190

191

@Factory

192

public class DatabaseFactory {

193

194

@Bean

195

@Singleton

196

public DataSource createDataSource() {

197

HikariConfig config = new HikariConfig();

198

config.setJdbcUrl("jdbc:postgresql://localhost/mydb");

199

config.setUsername("user");

200

config.setPassword("password");

201

return new HikariDataSource(config);

202

}

203

204

@Bean

205

@Singleton

206

public JdbcTemplate createJdbcTemplate(DataSource dataSource) {

207

return new JdbcTemplate(dataSource);

208

}

209

}

210

```

211

212

### @Primary

213

214

Marks a bean as primary when multiple beans of the same type exist, making it the default choice.

215

216

```java { .api }

217

@Target({TYPE, METHOD})

218

@Retention(RUNTIME)

219

public @interface Primary {

220

}

221

```

222

223

**Usage Examples:**

224

225

```java

226

import io.micronaut.context.annotation.Primary;

227

import jakarta.inject.Singleton;

228

229

// Multiple implementations

230

@Singleton

231

public class EmailService implements NotificationService {

232

// Email implementation

233

}

234

235

@Singleton

236

@Primary // This will be injected by default

237

public class SmsService implements NotificationService {

238

// SMS implementation

239

}

240

241

@Singleton

242

public class NotificationController {

243

244

@Inject

245

private NotificationService service; // SmsService will be injected due to @Primary

246

}

247

```

248

249

### @Prototype

250

251

Marks a bean as prototype scoped - a new instance is created for each injection.

252

253

```java { .api }

254

@Target({TYPE, METHOD})

255

@Retention(RUNTIME)

256

@Scope

257

public @interface Prototype {

258

}

259

```

260

261

**Usage Examples:**

262

263

```java

264

import io.micronaut.context.annotation.Prototype;

265

266

@Prototype

267

public class RequestHandler {

268

private final String requestId;

269

270

public RequestHandler() {

271

this.requestId = UUID.randomUUID().toString();

272

}

273

274

public void processRequest() {

275

System.out.println("Processing request: " + requestId);

276

}

277

}

278

279

@Singleton

280

public class RequestProcessor {

281

282

@Inject

283

private RequestHandler handler; // New instance each time

284

}

285

```

286

287

## Configuration Annotations

288

289

### @Value

290

291

Injects configuration values using property placeholders.

292

293

```java { .api }

294

@Target({FIELD, PARAMETER, METHOD})

295

@Retention(RUNTIME)

296

public @interface Value {

297

String value();

298

}

299

```

300

301

**Usage Examples:**

302

303

```java

304

import io.micronaut.context.annotation.Value;

305

import jakarta.inject.Singleton;

306

307

@Singleton

308

public class ApiService {

309

310

@Value("${api.base-url}")

311

private String baseUrl;

312

313

@Value("${api.timeout:30}") // Default value of 30

314

private int timeoutSeconds;

315

316

@Value("${api.key}")

317

private String apiKey;

318

319

// Constructor injection with @Value

320

public ApiService(@Value("${api.version:v1}") String apiVersion) {

321

this.apiVersion = apiVersion;

322

}

323

}

324

```

325

326

### @Property

327

328

Injects specific property values with optional defaults.

329

330

```java { .api }

331

@Target({FIELD, PARAMETER, METHOD})

332

@Retention(RUNTIME)

333

public @interface Property {

334

String name();

335

String defaultValue() default "";

336

}

337

```

338

339

**Usage Examples:**

340

341

```java

342

import io.micronaut.context.annotation.Property;

343

import jakarta.inject.Singleton;

344

345

@Singleton

346

public class DatabaseService {

347

348

private final String url;

349

private final String username;

350

private final int maxConnections;

351

352

public DatabaseService(

353

@Property(name = "database.url") String url,

354

@Property(name = "database.username") String username,

355

@Property(name = "database.max-connections", defaultValue = "10") int maxConnections) {

356

357

this.url = url;

358

this.username = username;

359

this.maxConnections = maxConnections;

360

}

361

}

362

```

363

364

### @ConfigurationProperties

365

366

Maps configuration properties to a bean, enabling type-safe configuration.

367

368

```java { .api }

369

@Target({TYPE})

370

@Retention(RUNTIME)

371

public @interface ConfigurationProperties {

372

String value() default "";

373

boolean cliPrefix() default false;

374

}

375

```

376

377

**Usage Examples:**

378

379

```java

380

import io.micronaut.context.annotation.ConfigurationProperties;

381

import jakarta.validation.constraints.NotBlank;

382

import jakarta.validation.constraints.Min;

383

384

@ConfigurationProperties("redis")

385

public class RedisConfiguration {

386

387

@NotBlank

388

private String host = "localhost";

389

390

@Min(1)

391

private int port = 6379;

392

393

private String password;

394

private int database = 0;

395

private int timeout = 2000;

396

397

// Getters and setters

398

public String getHost() { return host; }

399

public void setHost(String host) { this.host = host; }

400

401

public int getPort() { return port; }

402

public void setPort(int port) { this.port = port; }

403

404

public String getPassword() { return password; }

405

public void setPassword(String password) { this.password = password; }

406

407

public int getDatabase() { return database; }

408

public void setDatabase(int database) { this.database = database; }

409

410

public int getTimeout() { return timeout; }

411

public void setTimeout(int timeout) { this.timeout = timeout; }

412

}

413

414

// Usage

415

@Singleton

416

public class RedisService {

417

418

private final RedisConfiguration config;

419

420

@Inject

421

public RedisService(RedisConfiguration config) {

422

this.config = config;

423

}

424

425

public void connect() {

426

System.out.println("Connecting to Redis at " + config.getHost() + ":" + config.getPort());

427

}

428

}

429

```

430

431

## Conditional Annotations

432

433

### @Requires

434

435

Conditionally loads beans based on various criteria.

436

437

```java { .api }

438

@Target({PACKAGE, TYPE, METHOD})

439

@Retention(RUNTIME)

440

@Repeatable(Requirements.class)

441

public @interface Requires {

442

String property() default "";

443

String[] value() default {};

444

String defaultValue() default "";

445

Class<?>[] beans() default {};

446

Class<?>[] missingBeans() default {};

447

String[] env() default {};

448

String[] notEnv() default {};

449

Class<? extends Condition>[] condition() default {};

450

String os() default "";

451

String[] notOs() default {};

452

String sdk() default "";

453

String[] classes() default {};

454

String[] missingClasses() default {};

455

String[] resources() default {};

456

String[] missingProperties() default {};

457

}

458

```

459

460

**Usage Examples:**

461

462

```java

463

import io.micronaut.context.annotation.Requires;

464

import jakarta.inject.Singleton;

465

466

// Conditional on property value

467

@Singleton

468

@Requires(property = "cache.enabled", value = "true")

469

public class CacheService {

470

// Only created if cache.enabled=true

471

}

472

473

// Conditional on environment

474

@Singleton

475

@Requires(env = "prod")

476

public class ProductionDatabaseService implements DatabaseService {

477

// Only loaded in production environment

478

}

479

480

// Conditional on missing bean

481

@Singleton

482

@Requires(missingBeans = DatabaseService.class)

483

public class DefaultDatabaseService implements DatabaseService {

484

// Only created if no other DatabaseService exists

485

}

486

487

// Conditional on class presence

488

@Singleton

489

@Requires(classes = "org.springframework.data.redis.core.RedisTemplate")

490

public class RedisIntegrationService {

491

// Only created if Redis classes are on classpath

492

}

493

494

// Multiple conditions

495

@Singleton

496

@Requires(property = "features.analytics", value = "true")

497

@Requires(env = {"prod", "staging"})

498

@Requires(beans = DatabaseService.class)

499

public class AnalyticsService {

500

// Created only if all conditions are met

501

}

502

```

503

504

### @Requirements

505

506

Groups multiple @Requires conditions.

507

508

```java { .api }

509

@Target({PACKAGE, TYPE, METHOD})

510

@Retention(RUNTIME)

511

public @interface Requirements {

512

Requires[] value();

513

}

514

```

515

516

**Usage Examples:**

517

518

```java

519

import io.micronaut.context.annotation.Requirements;

520

import io.micronaut.context.annotation.Requires;

521

522

@Singleton

523

@Requirements({

524

@Requires(property = "email.enabled", value = "true"),

525

@Requires(classes = "javax.mail.internet.MimeMessage"),

526

@Requires(beans = EmailConfiguration.class)

527

})

528

public class EmailService {

529

// Created only if all requirements are satisfied

530

}

531

```

532

533

## Qualifier Annotations

534

535

### Custom Qualifiers

536

537

```java

538

import io.micronaut.context.annotation.Qualifier;

539

import java.lang.annotation.Retention;

540

import java.lang.annotation.RetentionPolicy;

541

542

@Qualifier

543

@Retention(RetentionPolicy.RUNTIME)

544

public @interface Database {

545

DatabaseType value();

546

}

547

548

public enum DatabaseType {

549

MYSQL, POSTGRESQL, H2

550

}

551

552

// Usage

553

@Singleton

554

@Database(DatabaseType.MYSQL)

555

public class MySQLService implements DatabaseService {

556

// MySQL implementation

557

}

558

559

@Singleton

560

@Database(DatabaseType.POSTGRESQL)

561

public class PostgreSQLService implements DatabaseService {

562

// PostgreSQL implementation

563

}

564

565

@Singleton

566

public class DataService {

567

568

@Inject

569

@Database(DatabaseType.MYSQL)

570

private DatabaseService mysqlService;

571

572

@Inject

573

@Database(DatabaseType.POSTGRESQL)

574

private DatabaseService postgresService;

575

}

576

```

577

578

## Lifecycle Annotations

579

580

### @PostConstruct

581

582

Marks methods to be called after bean construction and dependency injection.

583

584

```java { .api }

585

@Target(METHOD)

586

@Retention(RUNTIME)

587

public @interface PostConstruct {

588

}

589

```

590

591

### @PreDestroy

592

593

Marks methods to be called before bean destruction.

594

595

```java { .api }

596

@Target(METHOD)

597

@Retention(RUNTIME)

598

public @interface PreDestroy {

599

}

600

```

601

602

**Usage Examples:**

603

604

```java

605

import jakarta.annotation.PostConstruct;

606

import jakarta.annotation.PreDestroy;

607

import jakarta.inject.Singleton;

608

609

@Singleton

610

public class DatabaseConnectionManager {

611

612

private Connection connection;

613

614

@PostConstruct

615

public void initialize() {

616

System.out.println("Initializing database connection...");

617

this.connection = DriverManager.getConnection("jdbc:h2:mem:test");

618

}

619

620

@PreDestroy

621

public void cleanup() {

622

System.out.println("Closing database connection...");

623

try {

624

if (connection != null && !connection.isClosed()) {

625

connection.close();

626

}

627

} catch (SQLException e) {

628

System.err.println("Error closing connection: " + e.getMessage());

629

}

630

}

631

632

public void executeQuery(String sql) {

633

// Use connection

634

}

635

}

636

```

637

638

## Scope Annotations

639

640

### Custom Scope

641

642

```java

643

import io.micronaut.context.scope.CustomScope;

644

import java.lang.annotation.Retention;

645

import java.lang.annotation.RetentionPolicy;

646

647

@CustomScope

648

@Retention(RetentionPolicy.RUNTIME)

649

public @interface RequestScope {

650

}

651

652

// Implementation of the custom scope

653

@Singleton

654

public class RequestScopeImpl implements CustomScope<RequestScope> {

655

656

private final Map<String, Object> requestBeans = new ConcurrentHashMap<>();

657

658

@Override

659

public Class<RequestScope> annotationType() {

660

return RequestScope.class;

661

}

662

663

@Override

664

public <T> T get(BeanCreationContext<T> creationContext) {

665

String key = creationContext.id().toString();

666

return (T) requestBeans.computeIfAbsent(key, k ->

667

creationContext.create()

668

);

669

}

670

671

public void clearScope() {

672

requestBeans.clear();

673

}

674

}

675

676

// Usage

677

@RequestScope

678

public class RequestDataHolder {

679

private Map<String, Object> data = new HashMap<>();

680

681

public void setData(String key, Object value) {

682

data.put(key, value);

683

}

684

685

public Object getData(String key) {

686

return data.get(key);

687

}

688

}

689

```

690

691

## Qualifier Utility Classes

692

693

### Qualifiers

694

695

Utility class for creating qualifiers programmatically.

696

697

```java { .api }

698

public final class Qualifiers {

699

public static <T> Qualifier<T> byName(String name);

700

public static <T> Qualifier<T> byType(Class<?>... types);

701

public static <T> Qualifier<T> byAnnotation(Annotation annotation);

702

public static <T> Qualifier<T> byTypeArguments(Class<?>... types);

703

public static <T> Qualifier<T> byTypeArgumentsClosest(Class<?>... types);

704

}

705

```

706

707

**Usage Examples:**

708

709

```java

710

import io.micronaut.context.ApplicationContext;

711

import io.micronaut.context.Qualifier;

712

import io.micronaut.inject.qualifiers.Qualifiers;

713

714

public class QualifierExample {

715

public static void main(String[] args) {

716

try (ApplicationContext context = ApplicationContext.run()) {

717

// Get bean by name qualifier

718

Qualifier<DatabaseService> nameQualifier = Qualifiers.byName("mysql");

719

DatabaseService mysqlService = context.getBean(DatabaseService.class, nameQualifier);

720

721

// Get bean by annotation qualifier

722

Database databaseAnnotation = () -> DatabaseType.POSTGRESQL;

723

Qualifier<DatabaseService> annotationQualifier = Qualifiers.byAnnotation(databaseAnnotation);

724

DatabaseService postgresService = context.getBean(DatabaseService.class, annotationQualifier);

725

}

726

}

727

}

728

```

729

730

## Advanced Configuration Annotations

731

732

### @EachBean

733

734

Creates a bean for each bean of the specified type that exists in the context.

735

736

```java { .api }

737

@Target({TYPE})

738

@Retention(RUNTIME)

739

@Documented

740

public @interface EachBean {

741

/**

742

* The bean type to iterate over

743

*/

744

Class<?> value();

745

}

746

```

747

748

**Usage Examples:**

749

750

```java

751

import io.micronaut.context.annotation.EachBean;

752

753

@EachBean(DataSource.class)

754

@Singleton

755

public class DatabaseService {

756

757

private final DataSource dataSource;

758

759

public DatabaseService(DataSource dataSource) {

760

this.dataSource = dataSource;

761

}

762

763

public Connection getConnection() {

764

return dataSource.getConnection();

765

}

766

}

767

768

// If there are 3 DataSource beans, 3 DatabaseService beans will be created

769

```

770

771

### @EachProperty

772

773

Creates a bean for each property prefix that exists in configuration.

774

775

```java { .api }

776

@Target({TYPE})

777

@Retention(RUNTIME)

778

@Documented

779

public @interface EachProperty {

780

/**

781

* The property prefix to iterate over

782

*/

783

String value();

784

785

/**

786

* Primary bean marker

787

*/

788

boolean primary() default false;

789

}

790

```

791

792

**Usage Examples:**

793

794

```java

795

import io.micronaut.context.annotation.EachProperty;

796

import io.micronaut.context.annotation.ConfigurationProperties;

797

798

@EachProperty("datasources")

799

@ConfigurationProperties("datasources")

800

public class DataSourceConfig {

801

private String url;

802

private String username;

803

private String password;

804

805

// getters and setters

806

}

807

808

// Configuration:

809

// datasources.primary.url=jdbc:h2:mem:primary

810

// datasources.secondary.url=jdbc:h2:mem:secondary

811

// Creates DataSourceConfig beans for "primary" and "secondary"

812

```

813

814

### @ConfigurationBuilder

815

816

Enables configuration of complex objects through builder pattern integration.

817

818

```java { .api }

819

@Target({FIELD, METHOD, PARAMETER})

820

@Retention(RUNTIME)

821

@Documented

822

public @interface ConfigurationBuilder {

823

/**

824

* Property prefix for configuration values

825

*/

826

String value() default "";

827

828

/**

829

* Configuration prefix for nested configuration

830

*/

831

String configurationPrefix() default "";

832

833

/**

834

* Method prefixes to include

835

*/

836

String[] includes() default {};

837

838

/**

839

* Method prefixes to exclude

840

*/

841

String[] excludes() default {};

842

843

/**

844

* Whether to allow zero args methods

845

*/

846

boolean allowZeroArgs() default false;

847

}

848

```

849

850

**Usage Examples:**

851

852

```java

853

import io.micronaut.context.annotation.ConfigurationBuilder;

854

import io.micronaut.context.annotation.ConfigurationProperties;

855

856

@ConfigurationProperties("redis")

857

public class RedisConfig {

858

859

@ConfigurationBuilder(configurationPrefix = "jedis")

860

private final JedisPoolConfig jedisConfig = new JedisPoolConfig();

861

862

@ConfigurationBuilder(configurationPrefix = "lettuce",

863

includes = {"timeout", "database"})

864

private final LettuceConnectionFactory.Builder lettuceBuilder =

865

LettuceConnectionFactory.builder();

866

867

public JedisPoolConfig getJedisConfig() {

868

return jedisConfig;

869

}

870

871

public LettuceConnectionFactory.Builder getLettuceBuilder() {

872

return lettuceBuilder;

873

}

874

}

875

876

// Configuration:

877

// redis.jedis.max-total=20

878

// redis.jedis.max-idle=10

879

// redis.lettuce.timeout=5000

880

// redis.lettuce.database=0

881

```

882

883

### @Executable

884

885

Marks methods as executable for reflection-free method invocation.

886

887

```java { .api }

888

@Target({METHOD})

889

@Retention(RUNTIME)

890

@Documented

891

public @interface Executable {

892

/**

893

* Whether the method can be invoked when reflection is not available

894

*/

895

boolean processOnStartup() default false;

896

}

897

```

898

899

**Usage Examples:**

900

901

```java

902

import io.micronaut.context.annotation.Executable;

903

904

@Singleton

905

public class BusinessService {

906

907

@Executable

908

public String processData(String input) {

909

return "Processed: " + input;

910

}

911

912

@Executable(processOnStartup = true)

913

public void initialize() {

914

System.out.println("Service initialized");

915

}

916

917

// Non-executable method - won't be optimized

918

private void internalMethod() {

919

// Internal logic

920

}

921

}

922

```

923

924

### @Mapper

925

926

Enables automatic bean mapping functionality.

927

928

```java { .api }

929

@Target({TYPE})

930

@Retention(RUNTIME)

931

@Documented

932

public @interface Mapper {

933

/**

934

* The mapping strategy

935

*/

936

Strategy strategy() default Strategy.DEFAULT;

937

938

/**

939

* Configuration for the mapper

940

*/

941

String config() default "";

942

943

enum Strategy {

944

DEFAULT, CONSTRUCTOR, SETTER, FIELD

945

}

946

}

947

```

948

949

**Usage Examples:**

950

951

```java

952

import io.micronaut.context.annotation.Mapper;

953

954

@Mapper

955

public interface UserMapper {

956

957

UserDto toDto(User user);

958

User fromDto(UserDto dto);

959

960

@Mapping(target = "fullName", source = "firstName,lastName")

961

UserSummary toSummary(User user);

962

}

963

964

// Micronaut generates implementation at compile time

965

```

966

967

## Conditional and Validation Annotations

968

969

### @Any

970

971

Qualifier that matches any bean of the specified type.

972

973

```java { .api }

974

@Target({FIELD, PARAMETER, METHOD, TYPE})

975

@Retention(RUNTIME)

976

@Qualifier

977

public @interface Any {

978

}

979

```

980

981

### @Secondary

982

983

Marks a bean as secondary - used when @Primary is not available.

984

985

```java { .api }

986

@Target({TYPE, METHOD})

987

@Retention(RUNTIME)

988

public @interface Secondary {

989

}

990

```

991

992

**Usage Examples:**

993

994

```java

995

import io.micronaut.context.annotation.Secondary;

996

997

@Singleton

998

@Primary

999

public class PrimaryEmailService implements EmailService {

1000

// Primary implementation

1001

}

1002

1003

@Singleton

1004

@Secondary

1005

public class BackupEmailService implements EmailService {

1006

// Secondary implementation - used if primary fails

1007

}

1008

```

1009

1010

## Types

1011

1012

### Annotation Metadata Types

1013

1014

```java { .api }

1015

public interface AnnotationMetadata {

1016

<T extends Annotation> Optional<T> getAnnotation(Class<T> annotationClass);

1017

boolean hasAnnotation(Class<? extends Annotation> annotation);

1018

boolean hasStereotype(Class<? extends Annotation> stereotype);

1019

Set<String> getAnnotationNames();

1020

Set<String> getDeclaredAnnotationNames();

1021

OptionalValues<Object> getValues(String annotation);

1022

<T> Optional<T> getValue(String annotation, Class<T> requiredType);

1023

String[] stringValues(Class<? extends Annotation> annotation);

1024

String[] stringValues(String annotation, String member);

1025

}

1026

}

1027

```