25. Spring Profiles & Environment Decoupling

Conditional Beans with @Profile & Production Patterns

Master conditional Spring bean registration using @Profile, resolving bean ambiguity, implementation switching, and production configuration anti-patterns.

Conditional Beans with @Profile & Production Patterns

While profile-specific configuration files change property values, the @Profile annotation changes which Spring Beans are instantiated in the ApplicationContext.


1. Values vs Implementations: The Critical Decision Rule

CRITICAL DECISION RULE:
├── Different Property Values? ──► Use Profile Files (application-dev.yml vs application-prod.yml)
└── Different Java Classes?   ──► Use @Profile Annotation (@Profile("dev") vs @Profile("prod"))

2. Conditional Bean Creation with @Profile

Suppose an application sends notifications. In development, notifications should be logged to the console without sending real emails. In production, real emails must be dispatched via SMTP:

Common Service Interface

public interface NotificationService {
    void sendNotification(String recipient, String message);
}

1. Development Implementation (@Profile("dev"))

@Service
@Profile("dev")
public class DevNotificationService implements NotificationService {

    @Override
    public void sendNotification(String recipient, String message) {
        System.out.println("[DEV MOCK NOTIFICATION] To: " + recipient + " | Msg: " + message);
    }
}

2. Production Implementation (@Profile("prod"))

@Service
@Profile("prod")
public class ProdNotificationService implements NotificationService {

    @Override
    public void sendNotification(String recipient, String message) {
        // Dispatch real email via SMTP
        System.out.println("[PROD SMTP EMAIL] Dispatched to: " + recipient);
    }
}

3. How @Profile Prevents Bean Ambiguity

Without @Profile, registering both DevNotificationService and ProdNotificationService as @Service beans would cause Spring DI to fail with NoUniqueBeanDefinitionException due to candidate ambiguity.

With @Profile:

  • When SPRING_PROFILES_ACTIVE=dev, only DevNotificationService is registered in ApplicationContext.
  • When SPRING_PROFILES_ACTIVE=prod, only ProdNotificationService is registered in ApplicationContext.
@RestController
@RequestMapping("/api/notifications")
public class NotificationController {

    private final NotificationService service;

    // Zero ambiguity! Exactly ONE NotificationService implementation exists for the active profile
    public NotificationController(NotificationService service) {
        this.service = service;
    }

    @PostMapping
    public String notifyUser(@RequestParam String email, @RequestParam String msg) {
        service.sendNotification(email, msg);
        return "Notification processed";
    }
}

4. Production Anti-Patterns Matrix

Anti-PatternOperational RiskRecommended Solution
1. Hardcoding Active Profile in SourcePackages spring.profiles.active=prod into application.yml, making JAR non-reusableActivate profiles externally via SPRING_PROFILES_ACTIVE
2. Mixing .properties and .ymlOverlapping files create unpredictable property override bugsStandardize strictly on application.yml
3. Using @Profile for Value ChangesCreating separate classes just to change DB URLs or portsUse application-dev.yml for values; @Profile for code
4. Excessive Individual @Value FieldsCreates bloated, fragile classes with repeated annotationsUse @ConfigurationProperties for grouped settings

❓ Interactive Self-Assessment

Knowledge Check

What is the difference between profile-specific files (application-dev.yml) and the @Profile annotation?

Designing One Reusable JAR for Multi-Environment Deployments

An enterprise system requires:

  1. Local Dev: Uses MySQL localhost:3306/dev_db on port 8081 with a mock DevPaymentService.
  2. Production: Uses Cloud MySQL prod-db:3306/prod_db on port 8080 with a real StripePaymentService.
  3. The Java Controller must remain 100% unchanged across both environments.

Design the file layout and Java implementation structure.

On this page