Master Spring & Java Development!
Subscribe to Ram N Java for clean, beginner-friendly coding guides!
🔔 Subscribe to Ram N JavaOverview: Custom @Required Annotation in Spring
In the Spring Framework, dependency validation ensures that mandatory bean properties are populated at configuration time rather than failing at runtime with a NullPointerException. While Spring provides the built-in @Required annotation, developers can also create and register their own custom @Required annotations. This allows teams to enforce custom validation semantics, follow organizational naming standards, or integrate seamlessly with custom bean post-processors.
In this guide, you will learn how to define a custom required annotation, configure Spring's RequiredAnnotationBeanPostProcessor to recognize it, and enforce strict dependency checking during bean initialization.
How Custom Dependency Checking Works
Spring's dependency checking framework is highly modular and customizable:
-
Custom Annotation Declaration: You can define a standard Java retention annotation (e.g.,
@Mandatory) targeted at methods. -
Post-Processor Configuration: The
RequiredAnnotationBeanPostProcessorsupports setting custom annotation types via itssetCustomAnnotationType()property. -
Fail-Fast Property Enforcement: If any setter method annotated with your custom annotation does not receive a property value during bean configuration, Spring raises a
BeanInitializationExceptionimmediately during application startup. - Cleaner Architecture: Enforces architectural contracts across your service layer without coupling your domain code strictly to Spring's package namespaces.
Step-by-Step Implementation
-
Create the Custom Annotation: Define a retention annotation applicable to methods:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mandatory { } -
Configure RequiredAnnotationBeanPostProcessor: Register the processor in Spring XML and specify your custom annotation type:
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor">
<property name="customAnnotationType" value="com.example.Mandatory"/>
</bean> -
Annotate Setter Methods: Place your custom annotation on setter methods that require dependencies:
@Mandatory
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
Recommended Next Videos from Ram N Java
Click here to watch on Youtube:
https://www.youtube.com/watch?v=USltDCotopE&list=UUhwKlOVR041tngjerWxVccw
Click the below Image to Enlarge:
| Spring dependency checking with Custom @Required Annotation | Spring Tutorial |
| Spring dependency checking with Custom @Required Annotation | Spring Tutorial |
| Spring dependency checking with Custom @Required Annotation | Spring Tutorial |
pom.xml
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0%20http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ram.core</groupId>
<artifactId>SpringDemo</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>SpringDemo</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Spring 5 dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
Mandatory.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mandatory
{
}
Address.java
public class Address
{
private String city;
private String state;
public String getCity()
{
return city;
}
public void setCity(String city)
{
this.city = city;
}
public String getState()
{
return state;
}
public void setState(String state)
{
this.state = state;
}
@Override
public String toString()
{
return "Address [city=" + city + ", state=" + state + "]";
}
}
Employee.java
import com.ram.custom.Mandatory;
public class Employee
{
private int id;
private String name;
private Address address;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Address getAddress()
{
return address;
}
@Mandatory
public void setAddress(Address address)
{
this.address = address;
}
@Override
public String toString()
{
return "Employee [id=" + id + ", name=" + name + ", address=" + address
+ "]";
}
}
applicationContext.xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="address" class="com.ram.model.Address">
<property name="city" value="TamilNadu"></property>
<property name="state" value="Chennai"></property>
</bean>
<bean id="employee" class="com.ram.model.Employee">
<property name="id" value="100"></property>
<property name="name" value="Peter"></property>
<!-- <property name="address" ref="address"></property> -->
</bean>
<bean
class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor">
<property name="requiredAnnotationType" value="com.ram.custom.Mandatory" />
</bean>
</beans>
App.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.ram.model.Employee;
public class App
{
public static void main(String[] args)
{
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
System.out.println("---------------------------------------");
Employee employee = context.getBean("employee", Employee.class);
System.out.println(
"Got employee object from the ApplicationContext(Spring Container)");
System.out.println(employee);
}
}
Output
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5b37e0d2: startup date [Mon Aug 06 09:47:19 IST 2018]; root of context hierarchy
Aug 06, 2018 9:47:19 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Aug 06, 2018 9:47:20 AM org.springframework.context.support.AbstractApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'employee' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'address' is required for bean 'employee'
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'employee' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'address' is required for bean 'employee'
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:591)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:758)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.ram.core.App.main(App.java:12)
Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'address' is required for bean 'employee'
at org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.postProcessPropertyValues(RequiredAnnotationBeanPostProcessor.java:156)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1344)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
... 11 more
Click the below link to download the code:
https://sites.google.com/site/ramj2eev2/java_basics/SpringDemo_custom_required_annotation.zip?attredirects=0&d=1
Github Link:
https://github.com/ramram43210/javaee/tree/master/Spring_2018/SpringDemo_custom_required_annotation
Bitbucket Link:
https://bitbucket.org/ramram43210/spring/src/476437cc0ec4e46c307e790b2c10c4099b1c066b/Spring_2018/SpringDemo_custom_required_annotation/?at=master
See also: