Constructor Confusion in Spring Framework

Constructor Confusion in Spring Framework

Constructor Confusion and how to handle it in Spring Framework

Constructor confusion happens when the class or component has more than one constructor. So, the Spring IoC container doesn't know which constructor is to be used for dependency injection.
Please explore our other tutorials:

Constructor Confusion Example

Let's look at the sample example:
java
package org.csbyte.constructorConfusion;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

public class ConstructorConfusion {
    public static void main(String[] args) {
        ApplicationContext container = new AnnotationConfigApplicationContext(NotificationAppConfig.class);
        NotificationService manager = container.getBean(NotificationService.class);

        manager.sendAlert("+1-555-0199", "Your package has shipped!");
    }
}
@Configuration
@ComponentScan
class NotificationAppConfig {
}


@Component("smsService")
class SMSService  {
    public void sendNotification(String recipient, String message) {
        System.out.println("SMS Sent to " + recipient + ": " + message);
    }
}

@Component("emailService")
class EmailService  {
    public void sendNotification(String recipient, String message) {
        System.out.println("Email Sent to " + recipient + ": " + message);
    }
}

@Service
class NotificationService {

    private SMSService smsSender;
    private EmailService emailSender;

    // No-args constructor
    public NotificationService() {
        System.out.println("Executing No-Args Constructor");
    }

    // Single dependency constructor
    public NotificationService(SMSService smsService) {
        System.out.println("Executing Single-Arg Constructor (SMS)");
        this.smsSender = smsService;
    }

    // Multi-dependency constructor
    public NotificationService(SMSService smsService, EmailService emailService) {
        System.out.println("Executing Multi-Arg Constructor (SMS & Email)");
        this.smsSender = smsService;
        this.emailSender = emailService;
    }

    public void sendAlert(String phoneNumber, String message) {
        System.out.println("\n--- Initiating Broadcast ---");
        if (smsSender != null) smsSender.sendNotification(phoneNumber, message);
        if (emailSender != null) emailSender.sendNotification(phoneNumber, message);
    }
}
Here, we do have three constructors: no-args, single-arg, and multiple-arg constructors.
Spring by default looks for a single primary constructor. If it finds multiple constructors, it tries to use based on the beans available in context, but it gets confused or becomes ambiguous which constructor needs to be used for multiple constructors.
Basically, the constructor confusion arises
  • Ambiguous data type: Multiple constructors accept different data types, e.g String vs Int
java
    private String someValue;

    public ConstructorConfusion(String someValue) {
        System.out.println("String called");
        this.someValue = someValue;
    }

    public ConstructorConfusion(@Value("90") int someValue) {
        System.out.println("int called");
        this.someValue = "Number: " + Integer.toString(someValue);
    }
  • Multiple Bean Matches: If multiple constructors match beans.
In our above notification system code, Spring will fall back to the default no-arg constructor because any other constructors are not injected via the @Autowire annotation.
If you remove the no-args constructor and run the code, we will get the following error:
bash
Failed to instantiate [org.csbyte.constructorConfusion.NotificationService]: No default constructor found

Adding @Autowired

What happens if we mark the multiple constructors as autowired? Spring will crash during context initialization.
java
    @Autowired
    public NotificationService(SMSService smsService) {
        System.out.println("Executing Single-Arg Constructor (SMS)");
        this.smsSender = smsService;
    }

    // Multi-dependency constructor
    @Autowired
    public NotificationService(SMSService smsService, EmailService emailService) {
        System.out.println("Executing Multi-Arg Constructor (SMS & Email)");
        this.smsSender = smsService;
        this.emailSender = emailService;
    }
If we are using IntelliJ IDEA, it shows the compile-time error
bash
Only one constructor can have @Autowire annotation 

Use a Single @Autowired Annotation

In order to resolve these issues, we need to create a single constructor that will be annotated by Autowired
java
    @Autowired
    public NotificationService(SMSService smsService, EmailService emailService) {
        System.out.println("Executing Multi-Arg Constructor (SMS & Email)");
        this.smsSender = smsService;
        this.emailSender = emailService;
    }

Best Practice

  • Place a single @Autowired annotation for the desired constructor.
  • Or create a single constructor for dependency injection; no @Autowired is needed.