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
StringvsInt
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 foundAdding @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
@Autowiredannotation for the desired constructor. - Or create a single constructor for dependency injection; no
@Autowiredis needed.
