Spring @Conditional Annotation

This Spring tutorial is to learn about @Conditional annotation that was introduced in Spring 4.0 We shall see about what @Conditional annotation is, in what scenarios it can be used, difference between @Conditional vs @Profile annotations and an example Spring application using @Conditional annotation.

Spring-Conditional

Prerequisites

We need to have some basic knowledge of the following concepts before leaning @Conditional annotation.

Please go through “Spring @Profile Annotation” topic to get knowledge on Spring’s Java-based Configuration and Spring Profiles.

Introduction to @Conditional Annotation

Spring 4.0 has introduced a new annotation @Conditional. It is used to develop an “If-Then-Else” type of conditional checking for bean registration.
Till Spring 3.x Framework something similar to @Conditional annotation are,

Spring 4.0 @Conditional annotation is at more higher level when compared to @Profile annotation. @Profile annotation should be used for loading application configuration based on conditional logic.
@Profile annotation is restricted to write conditional checking based on predefined properties. @Conditional annotation does not have this restriction.

Difference between @Conditional and @Profile Annotations

Both Spring @Profiles and @Conditional annotations are used to develop an “If-Then-Else” conditional checking. However, Spring 4 @Conditional  is more generalized version of @Profile annotation.

Spring @Conditional Annotation

Spring Boot module makes heavy use of @Conditional annotation.
We can use Spring @Conditional annotation for the following scenarios:

This list is just a set of examples.


Spring Condition Interface

We need to have a class implementing an interface named Condition provided by Spring. It has a method matches and our conditional logic should go inside this method. Then we can use the class we have defined in the @Conditional annotation to check for a condition. Following the Condition interface that we need to implement in our custom condition class.

public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Spring @Conditional Annotation Example 1

In this example let us see how we can use @Conditional annotation to check a property value is dev or prod from environment. For learning we will use Java based configurations in this example.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.javapapers.spring4</groupId>
  <artifactId>Spring4.0Conditional</artifactId>
  <version>1.0.0</version>
  
  <properties>
		<spring-framework.version>4.0.9.RELEASE</spring-framework.version>
		<junit.version>4.11</junit.version>
	</properties>	
		
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring-framework.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>	
</project>

package com.javapapers.spring4.domain;

public class Employee {

	private int id;
	private String name;
	private double sal;
	
	public Employee(int id,String name,double sal) {
		this.id = id;
		this.name = name;
		this.sal = sal;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public double getSal() {
		return sal;
	}		
}

Create Utility classes to represent in-memory Databases. For simplicity, we are not interacting with Databases and also are using our DataSource interface rather than javax.sql.DataSource. DevDatabaseUtil class represents DEV database and ProductionDatabaseUtil class represents PROD database with some data.

package com.javapapers.spring4.util;

import java.util.List;
import com.javapapers.spring4.domain.Employee;

public interface DataSource {
	List<Employee> getEmployeeDetails();
}
package com.javapapers.spring4.util;

import java.util.ArrayList;
import java.util.List;
import com.javapapers.spring4.domain.Employee;

public class DevDatabaseUtil implements DataSource {

	@Override
	public List<Employee> getEmployeeDetails(){
		List<Employee> empDetails = new ArrayList<>();
		Employee emp1 = new Employee(111,"Abc",11000);
		Employee emp2 = new Employee(222,"Xyz",22000);
		empDetails.add(emp1);
		empDetails.add(emp2);
		
		return empDetails;
	}
}

package com.javapapers.spring4.util;

import java.util.ArrayList;
import java.util.List;
import com.javapapers.spring4.domain.Employee;

public class ProductionDatabaseUtil implements DataSource {

	@Override
	public List<Employee> getEmployeeDetails(){
		List<Employee> empDetails = new ArrayList<>();
		Employee emp1 = new Employee(9001,"Ramu",45000);
		Employee emp2 = new Employee(9002,"Charan",35000);
		Employee emp3 = new Employee(9003,"Joe",55000);
		empDetails.add(emp1);
		empDetails.add(emp2);
		empDetails.add(emp3);
		
		return empDetails;
	}
}

Create a EmployeeDAO class to represent DAO Layer.

package com.javapapers.spring4.dao;

import java.util.List;
import com.javapapers.spring4.domain.Employee;
import com.javapapers.spring4.util.DataSource;

public class EmployeeDAO {

	private DataSource dataSource;
	public EmployeeDAO(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	public List<Employee> getEmployeeDetails() {
		return dataSource.getEmployeeDetails();
	}

}

Create a EmployeeService class to represent Service layer. It interacts with DAO Layer class to get Employee Details data.

package com.javapapers.spring4.service;

import java.util.List;
import com.javapapers.spring4.dao.EmployeeDAO;
import com.javapapers.spring4.domain.Employee;

public class EmployeeService {
	
	private EmployeeDAO employeeDAO;
	public EmployeeService(EmployeeDAO employeeDAO) {
		this.employeeDAO = employeeDAO;
	}
	
	public List<Employee> getEmployeeDetails(){
		return employeeDAO.getEmployeeDetails();
	}

}

Condition Classes

Now it’s time to implement Spring 4 Conditional checking by using @Conditional annotation. Both condition checking classes implements Spring’s Condition interface matches() method. Both Conditional classes checks for “database.name” from Environement. If this values is “dev”, then DevDataSourceCondition’s matches() method returns true. Otherwise false. In the same way, if this property value is “prod”, then ProdDataSourceCondition’s matches() method returns true. Otherwise false.

package com.javapapers.spring4.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class DevDataSourceCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String dbname = context.getEnvironment().getProperty("database.name");
		return dbname.equalsIgnoreCase("dev");
	}

}
package com.javapapers.spring4.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ProdDataSourceCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String dbname = context.getEnvironment().getProperty("database.name");
		return dbname.equalsIgnoreCase("prod");
	}

}

Create Spring’s Java-based configuration classes

getDevDataSource() defines @Conditional annotation with DevDataSourceCondition class. That means if DevDataSourceCondition’s matches() method returns true, then EmployeeDataSourceConfig creates DataSource object to refer to DEV Database.

In the same way, getProdDataSource() defines @Conditional annotation with ProdDataSourceCondition class. That means if ProdDataSourceCondition’s matches() method returns true, then EmployeeDataSourceConfig creates DataSource object to refer to PRDO Database.

EmployeeConfig class configures other beans except DataSource related beans.

package com.javapapers.spring4.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.javapapers.spring4.dao.EmployeeDAO;
import com.javapapers.spring4.service.EmployeeService;
import com.javapapers.spring4.util.DataSource;

@Configuration
public class EmployeeConfig{
	  @Autowired
	  private DataSource dataSource;
	  
	  @Bean
	  public EmployeeService employeeService() {
		  return new EmployeeService(employeeDAO());
	  }
	  
	  @Bean
	  public EmployeeDAO employeeDAO() {
		  System.out.println("EmployeeDAO employeeDAO()" + dataSource);
		  return new EmployeeDAO(dataSource);
	  }

}
package com.javapapers.spring4.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.javapapers.spring4.condition.DevDataSourceCondition;
import com.javapapers.spring4.condition.ProdDataSourceCondition;
import com.javapapers.spring4.util.DataSource;
import com.javapapers.spring4.util.DevDatabaseUtil;
import com.javapapers.spring4.util.ProductionDatabaseUtil;

@Configuration
public class EmployeeDataSourceConfig {
	

	  @Bean(name="dataSource")
	  @Conditional(value=DevDataSourceCondition.class)
	  public DataSource getDevDataSource() {
		  return new DevDatabaseUtil();
	  }

	  @Bean(name="dataSource")
	  @Conditional(ProdDataSourceCondition.class)
	  public DataSource getProdDataSource() {
		  return new ProductionDatabaseUtil();
	  }
}

Now it’s time to write JUnits to test Spring’s @Conditional classes.

import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.javapapers.spring4.config.EmployeeConfig;
import com.javapapers.spring4.config.EmployeeDataSourceConfig;
import com.javapapers.spring4.domain.Employee;
import com.javapapers.spring4.service.EmployeeService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {EmployeeConfig.class,EmployeeDataSourceConfig.class},loader=AnnotationConfigContextLoader.class)
public class Spring4DevConditionalTest {

	@Autowired
	private ApplicationContext applicationContext;
	  
	  @Test	 
	  public void testDevDataSource() {
		  EmployeeService service = (EmployeeService)applicationContext.getBean("employeeService");
		  assertNotNull(service);
		  List<Employee> employeeDetails = service.getEmployeeDetails();
		  assertEquals(2, employeeDetails.size());
		  assertEquals("Abc", employeeDetails.get(0).getName());
		  assertEquals("Xyz", employeeDetails.get(1).getName());
	  }
}
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.javapapers.spring4.config.EmployeeConfig;
import com.javapapers.spring4.config.EmployeeDataSourceConfig;
import com.javapapers.spring4.domain.Employee;
import com.javapapers.spring4.service.EmployeeService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {EmployeeConfig.class,EmployeeDataSourceConfig.class},loader=AnnotationConfigContextLoader.class)
public class Spring4ProdConditionalTest {

	@Autowired
	private ApplicationContext applicationContext;

	  @Test	  
	  public void testProdDataSource() {
		  EmployeeService service = (EmployeeService)applicationContext.getBean("employeeService");
		  assertNotNull(service);
		  List<Employee> employeeDetails = service.getEmployeeDetails();
		  assertEquals(3, employeeDetails.size());
		  assertEquals("Ramu", employeeDetails.get(0).getName());
		  assertEquals("Charan", employeeDetails.get(1).getName());
		  assertEquals("Joe", employeeDetails.get(2).getName());
	  }
}

Now the project structure looks as below

Spring-Conditional-Project-Example-Structure

Run the Example @Conditional Project

To run this class in Eclipse or Spring STS IDE, please set some Junit Run Configuration as shown below -Ddatabase.name=dev

Spring-Conditional-Project-Example-Test

Similarly you can do for “prod”.

Spring @Conditional Annotation Example 2

In this example, we will write a Spring’s @Conditional checking based on whether a Bean object is present or not in Spring Application Context.

We are going to use same Example-1 project files with some minor changes.

Create a new Spring 4 Condition to check a bean is available in Application Context or not. If bean is available, BeanPresennceCondition’s matches() method return true. Otherwise, it throws NoSuchBeanDefinitionException exception. Just for testing purpose, use a catch block to handle this exception and continue the control flow to end of the matches() method and return false as shown below.

package com.javapapers.spring4.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import com.javapapers.spring4.config.EmployeeBeanConfig;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

public class BeanPresennceCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		EmployeeBeanConfig employeeBeanConfig = null;
		try {
			employeeBeanConfig = (EmployeeBeanConfig)context.getBeanFactory().getBean("employeeBeanConfig");	
		}catch(NoSuchBeanDefinitionException ex) {
			
		}
		return employeeBeanConfig != null;
	}

}

Create a new Java-based Configuration class. It defines an Employee bean with some data. This bean configuration is using BeanPresennceCondition. If this condition returns true, then only it configures this bean. Otherwise no bean is created.

package com.javapapers.spring4.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.javapapers.spring4.condition.BeanPresennceCondition;
import com.javapapers.spring4.domain.Employee;

@Configuration
public class EmployeeBeanConfig{
 
	  @Bean
	  @Conditional(BeanPresennceCondition.class)
	  public Employee employee() {
		  return new Employee(222,"Popeye",55000);
	  }
}

Create Junit test class to test this. Here we are trying to test “employee” bean data. If BeanPresennceCondition returns true, then only this bean is created.

import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.javapapers.spring4.config.EmployeeBeanConfig;
import com.javapapers.spring4.domain.Employee;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {EmployeeBeanConfig.class},loader=AnnotationConfigContextLoader.class)
public class Spring4DevBeanPresenceConditionalTest {

	@Autowired
	private ApplicationContext applicationContext;
	  
	  @Test	 
	  public void testDevDataSource() {
		  Employee emp = (Employee)applicationContext.getBean("employee");
		  assertNotNull(emp);
		  assertEquals(222, emp.getId());
		  assertEquals("Popeye", emp.getName());
	  }
}

Now change BeanPresennceCondition class matches() method logic as show below to check the failure case of the Condition.

package com.javapapers.spring4.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import com.javapapers.spring4.config.EmployeeBeanConfig;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

public class BeanPresennceCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		EmployeeBeanConfig employeeBeanConfig = null;
		try {
			employeeBeanConfig = (EmployeeBeanConfig)context.getBeanFactory().getBean("employeeBeanConfig2");	
		}catch(NoSuchBeanDefinitionException ex) {
			
		}
		return employeeBeanConfig != null;
	}

}

Here we are checking for “employeeBeanConfig2” bean instead of “employeeBeanConfig”. It will definitely fail because our application does not have this bean configuration. When BeanPresennceCondition fails, it returns false value to EmployeeBeanConfig’s employee() method where we are using @Conditional annotation. When this condition fails (that is ELSE block in “IF-ELSE” conditional checking), Spring IOC Container does not load “employee” bean into Spring Application Context.

When we run our Junit now, we will get Junit RED bar showing that failed status with the following error. org.springframework. beans.factory.NoSuchBeanDefinitionException: No bean named 'employee' is defined.

Download the example Project Spring4.0Conditional

Spring 4 @Profile Annotation Updates

In the previous tutorial on Spring, we learnt about @Profile annotation. Now let us see how @Profile annotation is updated using @Conditional annotation.

Spring 3.x @Profile annotation declaration:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE })
@Documented
public @interface Profile {
	String[] value();
}

Spring 4.0 @Profile annotation declaration:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
	String[] value();
}

If we observe both declaration, we can find the following changes to @Profile definition.

This Spring tutorial was added on 11/08/2015.

Comments on "Spring @Conditional Annotation" Tutorial:

  1. Nirav Modi says:

    Easy to understand through your example.

Comments are closed for this "Spring @Conditional Annotation" tutorial.