Spring Framework 6.2 — Fallback Annotation

This post explores the usage of the Fallback annotation introduced in Spring Framework 6.2.

· Prerequisites
· Overview
· Example
∘ @Primary Annotation
∘ @Fallback Annotation
∘ Fine-tuning Annotation-based Autowiring with Qualifiers
· Conclusion
· References


Prerequisites

This is the list of all the prerequisites:

  • Spring Boot 3.4+
  • Java 17 or later

Overview

Spring 6.2 added support for fallback beans. A fallback bean is used if no bean of that type has been provided. It is a substitute for the @Primary annotation.

If all beans but one among multiple matching candidates are marked as a fallback, the remaining bean will be selected.

Example

@Primary Annotation

The @Primary annotation in Spring is used to indicate that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency.

Consider the following scenario where we have multiple implementations of a repository interface (UserRepository).

public interface UserRepository {
void getUserInfo();
}

@Primary
@Component
public class JdbcUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcUserRepository.class);


@Override
public void getUserInfo() {
logger.info("Fetching user info from jdbc.");
}
}

@Component
public class HibernateUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(HibernateUserRepository.class);


@Override
public void getUserInfo() {
logger.info("Fetching user info from Hibernate.");
}
}

@Component
public class CacheUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(CacheUserRepository.class);


@Override
public void getUserInfo() {
logger.info("Fetching user info from Cached.");
}
}

Because JdbcUserRepository is marked with @Primary, it will be injected preferentially as a dependency, assuming there are three present as beans within the same Spring application context, which is often the case when component scanning is applied liberally.

@Fallback Annotation

Like primary beans, fallback beans only have an effect when multiple candidates are found for a single injection point. All type-matching beans are included when autowiring arrays, collections, maps, or ObjectProvider streams.

As of Spring Framework 6.2.0, we can rewrite the previous example with the Fallback annotation.

public interface UserRepository {
void getUserInfo();
}

@Component
public class JdbcUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from jdbc.");
}
}

@Fallback
@Component
public class HibernateUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(HibernateUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Hibernate.");
}
}

@Fallback
@Component
public class CacheUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(CacheUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Cached.");
}
}

This will produce the same result as the Primary annotation because JdbcUserRepository is the only remaining regular bean, and it is automatically considered primary.

It’s also possible to mark the remaining bean with the Primary annotation.

public interface UserRepository {
void getUserInfo();
}

@Primary
@Component
public class JdbcUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from jdbc.");
}
}

@Fallback
@Component
public class HibernateUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(HibernateUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Hibernate.");
}
}

@Fallback
@Component
public class CacheUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(CacheUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Cached.");
}
}

The primary annotation has higher priority. JdbcUserRepository will be injected preferentially.

If multiple @Fallback are defined without a default, then it will report a runtime error.

public interface UserRepository {
void getUserInfo();
}

@Fallback
@Component
public class JdbcUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from jdbc.");
}
}

@Fallback
@Component
public class HibernateUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(HibernateUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Hibernate.");
}
}

@Fallback
@Component
public class CacheUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(CacheUserRepository.class);

@Override
public void getUserInfo() {
logger.info("Fetching user info from Cached.");
}
}
***************************
APPLICATION FAILED TO START
*
**
************************

Description:

Field userRepository in ... required a single bean, but 3 were found:
- cacheUserRepository: defined in file [.../CacheUserRepository.class]
- hibernateUserRepository: defined in file [.../HibernateUserRepository.class]
- jdbcUserRepository: defined in file [.../JdbcUserRepository.class]

This may be due to missing parameter name information

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Fine-tuning Annotation-based Autowiring with Qualifiers

@Primary and @Fallback are effective ways to use autowiring by type, particularly when there is a single primary (or non-fallback) candidate that can be determined.

When you need more control over the selection process, you can use Spring’s @Qualifier annotation. You can associate qualifier values with specific arguments, narrowing the set of type matches so that a specific bean is chosen for each argument.

 @Autowired
@Qualifier("hibernateUserRepository")
private UserRepository userRepository;

hibernateUserRepository will be injected even if there are Primary or Fallback annotations.

Conclusion

Well done! Spring Framework 6.2 offers additional options for selecting a preferred bean when multiple beans are candidates for automatic connection to a single-valued dependency.

Support me through GitHub Sponsors.

Thank you for reading!! See you in the next post.

References

👉 Link to Medium blog

** Cover image by Tijana Drndarski on Unsplash

Related Posts