Close

Spring Core Testing - @BeanOverride Annotation Example

[Last Updated: Feb 15, 2026]

The @BeanOverride annotation is a core infrastructure introduced to provide a consistent way of overriding beans within a ApplicationContext for testing. It acts as a meta-annotation for more specific styles like @TestBean , @MockitoBean, MockitoSpyBean or custom implementations.

Before this feature, bean overriding was often fragmented between Spring Boot's specific annotations and manual context customizers. This unified approach allows developers to define exactly how a bean should be replaced or wrapped during the test lifecycle.

Java source and doc

Definition of BeanOverride

Version: 7.0.4
 package org.springframework.test.context.bean.override;
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 @Documented
 @Reflective(BeanOverrideReflectiveProcessor.class)
 public @interface BeanOverride {
     Class<? extends BeanOverrideProcessor> value(); 1
 }
1The BeanOverrideProcessor implementation to use.

Definition of BeanOverrideProcessor

Version: 7.0.4
 package org.springframework.test.context.bean.override;
 public interface BeanOverrideProcessor {
     BeanOverrideHandler createHandler(Annotation overrideAnnotation, 1
                                       Class<?> testClass,
                                       Field field);
     default List<BeanOverrideHandler> createHandlers(Annotation
         overrideAnnotation, 2
                                         Class<?> testClass);
 }
1Create a BeanOverrideHandler for the given annotated field.
2Create a list of BeanOverrideHandler instances for the given override annotation and test class. (Since 6.2.2)

When Should You Create a Custom @BeanOverride Annotation?

  • When you need reusable override logic across multiple tests.
  • When the override behavior is domain-specific and should be encapsulated in a dedicated annotation.
  • When you want a strongly-typed, expressive test DSL that improves readability and maintainability.

Example

In this example we will implement a custom strategy by creating your own annotation and a corresponding processor.

Creating custom annotation

package com.logicbig.example;

import org.springframework.test.context.bean.override.BeanOverride;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@BeanOverride(TestStringBeanOverrideProcessor.class)
public @interface TestString {

    String value();
}

The processor

package com.logicbig.example;

import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

public class TestStringBeanOverrideProcessor implements BeanOverrideProcessor {

    @Override
    public BeanOverrideHandler createHandler(Annotation overrideAnnotation,
                                             Class<?> testClass,
                                             Field field) {
        TestString annotation = field.getAnnotation(TestString.class);
        String testValue = annotation.value();

        return new TestStringBeanOverrideHandler( testValue, field);
    }

    private static class TestStringBeanOverrideHandler extends BeanOverrideHandler {

        private final String testValue;

        public TestStringBeanOverrideHandler(String testValue,
                                             Field field) {
            super(field, ResolvableType.forType(field.getGenericType()), field.getName(),
                  "", BeanOverrideStrategy.REPLACE);
            this.testValue = testValue;
        }

        @Override
        protected Object createOverrideInstance(String beanName,
                                                @Nullable BeanDefinition existingBeanDefinition,
                                                @Nullable Object existingBeanInstance) {
            // Simply return the test value, ignoring the existing bean

            return testValue;
        }
    }
}

The Test Class

package com.logicbig.example;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringJUnitConfig(ApiServiceTest.AppConfig.class)
class ApiServiceTest {

    @TestString("https://api.test.com")
    private String apiUrl;

    @Test
    void testApiCall() {
        // The apiUrl bean in the context is now "https://api.test.com"
        System.out.println("overridden: " + apiUrl);
        assertEquals("https://api.test.com", apiUrl);
    }

    @Configuration
    static class AppConfig {

        @Bean
        public String apiUrl() {
            return "https://api.production.com";
        }

    }
}

Output

$ mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -----------< com.logicbig.example:bean-override-annotation >------------
[INFO] Building bean-override-annotation 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ bean-override-annotation ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\bean-override-annotation\src\main\resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ bean-override-annotation ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ bean-override-annotation ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\bean-override-annotation\src\test\resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ bean-override-annotation ---
[INFO] Recompiling the module because of added or removed source files.
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 3 source files with javac [debug target 21] to target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ bean-override-annotation ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.ApiServiceTest
https://api.test.com
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.381 s -- in com.logicbig.example.ApiServiceTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.592 s
[INFO] Finished at: 2026-02-15T21:50:58+08:00
[INFO] ------------------------------------------------------------------------

Conclusion

In this tutorial, we created a custom annotation built on top of @BeanOverride to replace a bean inside the Spring Test ApplicationContext. We implemented a custom BeanOverrideProcessor and provided a corresponding BeanOverrideHandler that returned a test-specific value using the REPLACE strategy.

This approach allows you to define reusable, domain-specific test annotations instead of repeatedly configuring overrides in individual test classes. By encapsulating the override logic inside a custom annotation, tests become cleaner, more expressive, and easier to maintain.

Example Project

Dependencies and Technologies Used:

  • spring-context 7.0.4 (Spring Context)
     Version Compatibility: 6.2.6 - 7.0.4Version List
    ×

    Version compatibilities of spring-context with this example:

    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4

    Versions in green have been tested.

  • spring-test 7.0.4 (Spring TestContext Framework)
  • junit-jupiter 6.0.0 (Module "junit-jupiter" of JUnit)
  • JDK 25
  • Maven 3.9.11

Spring Core Testing - Using @BeanOverride Annotation Select All Download
  • bean-override-annotation
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • TestStringBeanOverrideProcessor.java

    See Also

    Join