How to conditionally declare Bean when multiple profiles are not active?

SpringSpring Boot

Spring Problem Overview


In my Spring-Boot-App I want to conditionally declare a Bean, depending on (un)loaded spring-profiles.

The conditon:

Profile "a" NOT loaded  
AND  
Profile "b" NOT loaded 

My solution so far (which works):

@Bean
@ConditionalOnExpression("#{!environment.getProperty('spring.profiles.active').contains('a') && !environment.getProperty('spring.profiles.active').contains('b')}")
	public MyBean myBean(){/*...*/}

Is there a more elegant (and shorter) way to explain this condition?
Especially I want to get rid of the usage of Spring Expression Language here.

Spring Solutions


Solution 1 - Spring

Since Spring 5.1.4 (incorporated in Spring Boot 2.1.2) it is possible to use a profile expression inside profile string annotation. So:

In Spring 5.1.4 (Spring Boot 2.1.2) and above it is as easy as:

@Component
@Profile("!a & !b")
public class MyComponent {}

In Spring 4.x and 5.0.x:

There are many approaches for this Spring versions, each one of them has its pro's and con's. When there aren't many combinations to cover I personally like @Stanislav answer with the @Conditional annotation.

Other approaches can be found in this similar questions:

Spring Profile - How to include AND condition for adding 2 profiles?

Spring: How to do AND in Profiles?

Solution 2 - Spring

If you have a single profile you could simply use a @Profile annotation with the not operator. It also accepts multiple profiles, but with the OR condition.

So, the alternative solution is to use a custom Condition with the @Conditional annotation. Like so:

public class SomeCustomCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    // Return true if NOT "a" AND NOT "b"
    return !context.getEnvironment().acceptsProfiles("a") 
				  && !context.getEnvironment().acceptsProfiles("b");
  }
}

And then annotate your method with it, like:

@Bean
@Conditional(SomeCustomCondition.class)
public MyBean myBean(){/*...*/}

Solution 3 - Spring

I prefer this solution which is more verbose but still fine for two profiles only:

@Profile("!a")
@Configuration
public class NoAConfig {

    @Profile("!b")
    @Configuration
    public static class NoBConfig {
	    @Bean
	    public MyBean myBean(){
		    return new MyBean();
	    }
    }

}

Solution 4 - Spring

Unfortunately I don't have a shorter solution for you, but if it is suitable in your case to create the same beans for each profile, you may consider the following approach.

@Configuration
public class MyBeanConfiguration {

   @Bean
   @Profile("a")
   public MyBean myBeanForA() {/*...*/}

   @Bean
   @Profile("b")
   public MyBean myBeanForB() {/*...*/}

   @Bean
   @ConditionalOnMissingBean(MyBean.class)
   public MyBean myBeanForOthers() {/*...*/}

}

Solution 5 - Spring

A variant of @Stanislav answer which is a tiny bit more readable, for Spring versions up to 5.0.x / Spring Boot 2.0.x.

public class SomeCustomCondition implements Condition {
  @Override
  public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    final Environment environment = context.getEnvironment();

    // true if profile is NOT "a" AND NOT "b" AND NOT "c"
    return !environment.acceptsProfiles("a", "b", "c");
  }
}

For newer versions of Spring / Spring Boot, see the f-CJ answer.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike BoddinView Question on Stackoverflow
Solution 1 - Springf-CJView Answer on Stackoverflow
Solution 2 - SpringStanislavView Answer on Stackoverflow
Solution 3 - SpringwillerrView Answer on Stackoverflow
Solution 4 - SpringdmytroView Answer on Stackoverflow
Solution 5 - Springt0r0XView Answer on Stackoverflow