Skip to content
Chapter 10 · Spring Boot

Conditional Beans with @ConditionalOnProperty ​

Chapter 9 used a property to set a value in a bean. A property can also decide whether a bean is created at all. That is @ConditionalOnProperty.

Why does it matter? A large application has thousands of beans, and the singletons among them are all created at startup, needed or not. They clutter the application context (the IoC container), cost memory and slow startup. @ConditionalOnProperty lets configuration switch beans off.

Step 1: the starting point ​

Two connection classes, and a class that uses both:

java
@Component
public class MySQLConnection {
    MySQLConnection() {
        System.out.println("initialization of MySQLConnection bean");
    }
}

@Component
public class NoSQLConnection {
    NoSQLConnection() {
        System.out.println("initialization of NoSQLConnection bean");
    }
}

@Component
public class DBConnection {

    @Autowired
    MySQLConnection mySQLConnection;

    @Autowired
    NoSQLConnection noSQLConnection;

    @PostConstruct
    public void init() {
        System.out.println("DBConnection bean created with dependencies below:");
        System.out.println("is MySQLConnection object null: " + (mySQLConnection == null));
        System.out.println("is NoSQLConnection object null: " + (noSQLConnection == null));
    }
}

All three are singletons, so all three are created at startup, and both connections are injected:

text
initialization of MySQLConnection bean
initialization of NoSQLConnection bean
DBConnection bean created with dependencies below:
is MySQLConnection object null: false
is NoSQLConnection object null: false

Step 2: why you would want only one ​

Two situations that come up in real companies:

  1. Migration. The company moves from MySQL to NoSQL. Once the move is done, the MySQLConnection bean should no longer be created. While migrating, you want to flip between them without changing code.
  2. Two applications, one shared codebase. Application 1 and application 2 both depend (in their pom.xml) on a common library that contains both connection classes. Application 1 needs only NoSQLConnection; application 2 only MySQLConnection. Each application should create only the bean it needs.

In both cases, the same code must create only one of the beans, chosen by configuration.

Step 3: add @ConditionalOnProperty ​

java
@Component
@ConditionalOnProperty(prefix = "sqlconnection", value = "enabled",
                       havingValue = "true", matchIfMissing = false)
public class MySQLConnection { ... }

@Component
@ConditionalOnProperty(prefix = "nosqlconnection", value = "enabled",
                       havingValue = "true", matchIfMissing = false)
public class NoSQLConnection { ... }
properties
# application.properties
sqlconnection.enabled=true

The four attributes:

AttributeMeaning
prefix + valuetogether they form the property key: prefix.value, here sqlconnection.enabled (name is an alias of value)
havingValuethe text the property's value must equal for the bean to be created. Just a string: "true" here, but "sql" works too if the property says sqlconnection.enabled=sql
matchIfMissingwhat to do when the key is not in the properties at all: false = do not create the bean, true = create it anyway

The rule: if the property's value equals havingValue, the bean is created; otherwise it is not.

Start the application:

text
initialization of MySQLConnection bean

Description:
Field noSQLConnection in com.example.demo.DBConnection required a bean of type 'com.example.demo.NoSQLConnection' that could not be found.

MySQLConnection was created (its key exists and equals "true"). NoSQLConnection was not (its key is missing, and matchIfMissing = false). So far so good. But DBConnection still requires a NoSQLConnection, and startup fails.

Step 4: let the injection point accept a missing bean ​

@Autowired means @Autowired(required = true): this dependency must be found, otherwise fail. A conditional bean may legitimately not exist, so wherever you inject one, say that it is optional:

java
@Component
public class DBConnection {

    @Autowired(required = false)
    MySQLConnection mySQLConnection;

    @Autowired(required = false)
    NoSQLConnection noSQLConnection;

    @PostConstruct
    public void init() { ... as before ... }
}

With required = false, a missing bean is not an error: the field is simply left null. Every use of @ConditionalOnProperty has these two parts: the condition on the class, and required = false (or another way of handling absence) where it is injected.

Start again, with sqlconnection.enabled=true:

text
initialization of MySQLConnection bean
DBConnection bean created with dependencies below:
is MySQLConnection object null: false
is NoSQLConnection object null: true

What Spring did:

  1. MySQLConnection: singleton, so eligible for creation at startup. It has a condition: build the key sqlconnection.enabled, look it up, find true, compare with havingValue "true": equal, so create it.
  2. NoSQLConnection: key nosqlconnection.enabled is missing, matchIfMissing = false: not created.
  3. DBConnection: created; MySQLConnection is injected; there is no NoSQLConnection bean, which required = false allows, so that field stays null.

Step 5: switch with configuration only ​

Finished migrating to NoSQL? Change the properties and restart:

properties
sqlconnection.enabled=false
nosqlconnection.enabled=true
text
initialization of NoSQLConnection bean
DBConnection bean created with dependencies below:
is MySQLConnection object null: true
is NoSQLConnection object null: false

No code changed. For the two-applications case, application 1 puts nosqlconnection.enabled=true in its application.properties, application 2 puts sqlconnection.enabled=true in its own.

havingValue is an exact string match. With sqlconnection.enabled=sql (and havingValue = "true"), the values differ, so neither connection is created:

text
DBConnection bean created with dependencies below:
is MySQLConnection object null: true
is NoSQLConnection object null: true

(If you leave havingValue out, the bean is created whenever the property exists and is not false.)

Advantages and disadvantages ​

Advantages

  1. Feature toggling. Switch a feature or an implementation on or off from application.properties, as in the migration.
  2. An uncluttered application context. Beans that are not needed are never created.
  3. Less memory. Every bean created at startup is kept in memory; fewer beans, less memory.
  4. Faster startup. Creating thousands of beans takes time; skipping unneeded ones shortens startup.

Disadvantages

  1. Misconfiguration. A value that does not match (sql in the property, "true" in havingValue) silently switches a bean off, as the last example showed.
  2. Complexity when overused. With the annotation on hundreds of classes, you must keep checking the property files to know which beans exist.
  3. One key controlling several beans is confusing. Using sqlconnection.enabled for both the MySQL and the NoSQL bean makes it hard to reason about which exists. Give each its own key.
  4. More to manage. Property files and annotation values have to be kept in sync.

Next: The most common reason configuration differs is the environment: dev, QA and production need different databases, URLs and limits. Chapter 11 covers profiles, Spring's built-in way to switch a whole set of properties, and beans, at once.

Check yourself ​

  1. What does @ConditionalOnProperty decide, and why does it matter in a large application?
  2. Give the two use cases from this chapter.
  3. With prefix = "sqlconnection" and value = "enabled", which property key is checked?
  4. The property is sqlconnection.enabled=sql and havingValue = "true". Is the bean created?
  5. The key is missing and matchIfMissing = false. Is the bean created? And with matchIfMissing = true?
  6. In step 3, the condition worked but startup still failed. Why, and what is the fix?
  7. What does required = false do when the bean does not exist?
  8. How do you switch from MySQL to NoSQL, and what has to change in the code?
  9. Name two advantages and two disadvantages.
Answers
  1. Whether a bean is created at all, based on a property. Unneeded beans then do not clutter the context, use memory or slow startup.
  2. Migrating between implementations (MySQL to NoSQL), and two applications sharing a codebase where each needs a different bean.
  3. sqlconnection.enabled.
  4. No: "sql" does not equal "true".
  5. No. Yes.
  6. The injection point (@Autowired, required = true by default) demanded a NoSQLConnection, which was not created. Use @Autowired(required = false).
  7. Startup continues and the field stays null.
  8. Change the properties (sqlconnection.enabled=false, nosqlconnection.enabled=true) and restart. Nothing in the code.
  9. Advantages: feature toggling, uncluttered context, less memory, faster startup. Disadvantages: silent misconfiguration, complexity when overused, confusion when one key controls several beans, property files and annotations to keep in sync.