Close

Spring Core Testing - Swapping beans with @ActiveProfiles and @Profile

[Last Updated: Feb 2, 2026]

This example shows how @ActiveProfiles can be used with @Profile to swap real beans for stubs in tests. By activating a test profile, we wire a StubEmailService instead of the real SmtpEmailService, with no mocking libraries required.

Example

App Configuration and services

package com.logicbig.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class AppConfig {

    // Real email service for production
    @Bean
    @Profile("prod")
    public EmailService emailService() {
        return new SmtpEmailService();
    }

    @Bean
    public UserService userService() {
        return new UserService();
    }
}
package com.logicbig.example;

public interface EmailService {
    void send(String to, String message);
}
package com.logicbig.example;

public class SmtpEmailService implements EmailService {
    @Override
    public void send(String to, String message) {
        System.out.println("Sending real email to: " + to +
                                   ", message: " + message);
        // Actual SMTP logic would go here in a real application
    }
}
package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;

public class UserService {
    @Autowired
    private EmailService emailService;

    public void registerUser(String email) {
        // registration logic ...
        emailService.send(email, "Welcome to our app!");
    }
}

Test Folder

Stub class to replace EmailService

package com.logicbig.example;

import java.util.ArrayList;
import java.util.List;

public class StubEmailService implements EmailService {
    public final List<String> sentEmails = new ArrayList<>();

    @Override
    public void send(String to, String message) {
        System.out.println("Stub: capturing email to: " + to);
        sentEmails.add(to + ": " + message);
    }
}
package com.logicbig.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;

@Configuration
//import because UserService should be available
@Import(AppConfig.class)
public class TestAppConfig {

    // Stub email service for testing
    @Profile("test")
    @Bean
    public EmailService emailService() {
        return new StubEmailService();
    }
}

The JUnit test

package com.logicbig.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import static org.junit.jupiter.api.Assertions.*;

@SpringJUnitConfig(classes = TestAppConfig.class)
@ActiveProfiles("test")
public class UserServiceTest {

    @Autowired
    private UserService userService;

    // will be StubEmailService
    // when 'test' profile is active
    @Autowired
    private EmailService emailService;

    @Test
    public void testUserRegistration() {
        userService.registerUser("john@example.com");

        assertTrue(emailService instanceof StubEmailService,
                   "EmailService should be the stub when "
                           + "'test' profile is active");
        StubEmailService stub = (StubEmailService) emailService;
        assertEquals(1, stub.sentEmails.size());
        assertTrue(stub.sentEmails.get(0).contains("john@example.com"));
    }
}

Output

D:\example-projects\spring-core-testing\spring-active-profiles-swap-beans-example>mvn test -Dtest=UserServiceTest
[INFO] Scanning for projects...
[INFO]
[INFO] ---< com.logicbig.example:spring-active-profiles-swap-beans-example >---
[INFO] Building spring-active-profiles-swap-beans-example 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ spring-active-profiles-swap-beans-example ---
[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\spring-active-profiles-swap-beans-example\src\main\resources
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ spring-active-profiles-swap-beans-example ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-active-profiles-swap-beans-example ---
[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\spring-active-profiles-swap-beans-example\src\test\resources
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ spring-active-profiles-swap-beans-example ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ spring-active-profiles-swap-beans-example ---
[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] -------------------------------------------------------
Stub: capturing email to: john@example.com
[INFO] +--com.logicbig.example.UserServiceTest - 0.312 ss
[INFO] | '-- [OK] testUserRegistration - 0.020 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.864 s
[INFO] Finished at: 2026-02-02T06:44:13+08:00
[INFO] ------------------------------------------------------------------------

Alternative: instead of declaring beans in @Configuration, annotate the implementations with @Component and @Profile (component scan should be configured in that case):

@Component
@Profile("prod")
public class SmtpEmailService implements EmailService { ... }


@Component
@Profile("test")
public class StubEmailService implements EmailService { ... }

Conclusion

This example demonstrates how Spring's @ActiveProfiles and @Profile annotations provide a clean, dependency-free approach to bean swapping in tests. By defining separate configurations for production (prod) and testing (test) environments, we can replace complex dependencies like SmtpEmailService with simplified test versions like StubEmailService. This technique eliminates the need for mocking libraries while maintaining clear separation between real and test implementations, resulting in more maintainable and self-documenting test code.

Example Project

Dependencies and Technologies Used:

  • spring-context 7.0.3 (Spring Context)
     Version Compatibility: 5.0.0.RELEASE - 7.0.3Version List
    ×

    Version compatibilities of spring-context with this example:

    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • Compatible Java Version: 17+
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 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
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3

    Versions in green have been tested.

  • spring-test 7.0.3 (Spring TestContext Framework)
  • junit-jupiter-engine 6.0.2 (Module "junit-jupiter-engine" of JUnit)
  • JDK 25
  • Maven 3.9.11

Spring Core Testing - Swapping beans with @ActiveProfiles Select All Download
  • spring-active-profiles-swap-beans-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example
                • UserServiceTest.java

    See Also

    Join