You probably already know how to annotate a Java class so that it becomes a Spring-managed component. But did you know that you can give it conditional management?

In other words, you can tell the framework something like: "I only want this class to become a Spring-managed component if the following conditions are met."

And then you can specify those conditions.

If the conditions are met during startyo, Spring will manage that object as a component. That means it's eligible for auto-detection and dependency injection.

So hang with me here and I'll show you how to do that.

Based on a Property

The easiest way to add a conditional is with a simple property in application.properties. Consider the following property:

add.service=true

Then, you can include the conditional on the class like so:

@Service
@ConditionalOnProperty(
        value="add.service", 
        havingValue = "true")
public class ActivityService {
...
}

Note the use of the @ConditionalOnProperty annotation. That tells Spring to make this class a Spring-managed component only if the conditions are met.

The conditions are specified in the parentheses.

The value property tells the framework which property to look at in application.properties.

The havingValue property specifies the value that the property must be set at for the check to pass.

In this case, the code above tells Spring to check the add.service property and look for the value "true." Since that is, in fact, the value of add.service (see above), the boolean returns true and the instantiated object will be managed by Spring.

Now elsewhere in that project other objects can autowire that ActivityService instance.

However, if you change add.service property to false and restart the application, you'll see this kind of error if you try to autowire that service:

Field activityService in com.careydevelopment.crm.config.ApplicationListenerInitialize required a bean of type 'com.careydevelopment.crm.service.ActivityService' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.careydevelopment.crm.service.ActivityService' in your configuration.

That means you messed up.

Default Behavior

You can also set a defaut behavior (or a default setting) if the property isn't set at all. Do that like this:

@Service
@ConditionalOnProperty(
        value="add.service", 
        havingValue = "true",
        matchIfMissing = true)
public class ActivityService {
...

Note the matchIfMissing property. That tells Spring what to do if the add.service property isn't defined in the properties file.

Also note that the value there is a boolean not a string. That's because you're telling Spring how to interpret a missing value and not to place a value in the property.

Now if you remove the add.service property and restart the Spring Boot application, you can once again inject an instance of ActivityService as a dependency.

With a Condition Class

If you've got a more sophisticated check you need to perform, you can do that with a class that implements Condition.

Suppose, for example, that you only want to add the object as a Spring-managed component if the OS is Windows 10 and the Java version is 11. In that case, add this class:

class OsAndVersionCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os = System.getProperty("os.name");
        
        boolean matches = JavaVersion.getJavaVersion().equals(JavaVersion.ELEVEN) &&
                                        "Windows 10".equals(os);
        
        return matches;
    }
}

And then you'd annotate the class as follows:

@Service
@Conditional(OsAndVersionCondition.class)
public class ActivityService {
...

Instead of using @ConditionalOnProperty, the code above uses @Conditional.

But @Conditional requires a class specification. Here, it's using OsAndVersionCondition. That's the class in the code above.

Just remember that the class specified in the parentheses must implement Condition. The boolean gets returned by the matches() method.

All the code above will work, but you can also optimize it.

Note that the OsAndVersionCondition class uses an "and" conjunction. Instead of doing it that way, you could create two classes that each return a boolean for one condition and then specify both classes in the @Conditional annotation.

Something like this would work:

@Service
@Conditional({OsCondition.class, JavaVersionCondition.class})
public class ActivityService {
...

Now the code is using two (2) classes to check the criteria. Both must implement Condition and both matches() methods must return true.  

That means, by the way, that @Conditional with multiple classes uses an AND not an OR. 

Wrapping It Up

Now you know how to add conditional annotations to your Spring Boot classes.

Feel free to use what you've learned here to set Spring-managed objects based on the environment, language, OS, or a host of other conditions.

Have fun!

Photo by Kei Scampa from Pexels