user2316667
user2316667

Reputation: 5634

'Bean does not have a public constructor that does not take parameters' error despite clearly having one?

I have an EmailService EJB that has a very simple 'send_email' method. I'm receving the error in the title despite clearly having a public constructor that does not take parameters. Below is the exact error and the class code. This is very confusing.

Error:

[ERROR ] CNTR5007E: The websphere.jaxrs.service.EmailService bean class for the WebApiConsole#WebApiConsole.war#EmailService bean does not have a public constructor that does not take parameters.

See here for error details (not much to see): http://pic.dhe.ibm.com/infocenter/wxdinfo/v6r1/index.jsp?topic=%2Fcom.ibm.websphere.messages.doc%2Fcom.ibm.ejs.container.container.html

Code:

package websphere.jaxrs.service;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;

import websphere.jaxrs.helpers.ContextFinder;

    /**
     * This class provides an interface for emailing messages. It uses server environment variables to configure SMTP server and port and authentication.
     * 
     * @author me
     *
     */
    @Stateless
    @LocalBean
    public class EmailService {

        @EJB ContextFinder ctf;

        private static final String EMAIL_PASSWORD_JNDI_NAME = "EMAIL_PASSWORD";
        private static final String EMAIL_USERNAME_JNDI_NAME = "EMAIL_USERNAME";
        private static final String SMTP_SERVER_JNDI_NAME = "SMTP_SERVER";
        private static final String SMTP_PORT_JNDI_NAME = "SMTP_PORT";

        private String username;
        private String password;
        private String server;
        private Integer port;


        public EmailService() { 
            username = (String) ctf.lookup(EMAIL_USERNAME_JNDI_NAME);
            password = (String) ctf.lookup(EMAIL_PASSWORD_JNDI_NAME);
            server = (String) ctf.lookup(SMTP_SERVER_JNDI_NAME);
            port = (Integer) ctf.lookup(SMTP_PORT_JNDI_NAME);
        }

        /**
         * Sends an email to a specific user.
         * 
         * @param sendTo
         * @param subject
         * @param message
         */
        public void sendMail(String sendTo, String subject, String message) {

            try {
                Email email = new SimpleEmail();
                email.setHostName(server);
                email.setSmtpPort(port);
                email.setAuthentication(username, password);
                email.setSSLOnConnect(true);
                email.setFrom(username);
                email.setSubject(subject);
                email.setMsg(message);
                email.addTo(sendTo);
                email.send();
            } catch (EmailException e) {
                System.err.println("Failed to email");
                e.printStackTrace();
            }

        }

    }

I'm inclined to think this is a bug. I might be wrong but but everything is pretty self-contained in the class above (no other configurations that I know of required) and I keep getting this error. I tried re-building the project.

[EDIT] I did some experimentation and very specifically, removing sendMail() causes it to be error free.

Upvotes: 2

Views: 7162

Answers (4)

Capricorn1
Capricorn1

Reputation: 849

Just in case you are arriving here several years late as I did, it also turns out this error message may be misleading. I got the error,

[ERROR   ] CNTR4006E: The EVENT_MANAGER_BEAN enterprise bean in the MySpecialEJB.jar module of the some-ear-DEVELOPMENT-SNAPSHOT application failed to start. Exception: com.ibm.ejs.container.EJBConfigurationException: EJB class com.notarealcompany.event.EventManagerEJB must have a public constructor that takes no parameters : some-ear-DEVELOPMENT-SNAPSHOT#MySpecialEJB.jar#EVENT_MANAGER_BEAN
    at com.ibm.ws.ejbcontainer.jitdeploy.EJBUtils.validateEjbClass(EJBUtils.java:346)
    at [internal classes]
Caused by: java.lang.NoClassDefFoundError: commonj/work/Work
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
    at java.lang.Class.getConstructor0(Class.java:3075)
    at java.lang.Class.getConstructor(Class.java:1825)
    at com.ibm.ws.ejbcontainer.jitdeploy.EJBUtils.validateEjbClass(EJBUtils.java:337)
    ... 1 more

I don't think the error message is trying to say that such a method does not exist. Rather it's saying that the loader couldn't find one that does exist that can be successfully loaded. The actual error (caused by) is the NoClassDefFoundError on the class commonj.work.Work. It turns out that there is also a warning earlier in the log:

[WARNING ] CWNEN0047W: Resource annotations on the fields of the com.notarealcompany.app.ApplicationEJB class will be ignored. The annotations could not be obtained because of the exception : java.lang.NoClassDefFoundError: Lcommonj/work/WorkManager;

So the Application EJB didn't get its resources injected either because commonj.work.WorkManager could not be found. Why that's a warning and not an error is a good question. I'm sure the app will do just fine without those resources, right? Basically, the jar with the APIs for commonj.work just didn't get included with the deployment. In our case, that's because the old application server provided it whereas the new application server did not. A little adjustment to the POM file and we got past that issue. Very misleading error message in my opinion.

Upvotes: 3

Petr Hunka
Petr Hunka

Reputation: 330

I would prefer to annotate your constructor with @Inject instead:

@Stateless
@LocalBean
public class EmailService {

    ....
    @Deprecated
    public EmailService(){}       

    @Inject 
    public EmailService(ContextFinder ctf) { 
        username = (String) ctf.lookup(EMAIL_USERNAME_JNDI_NAME);
        password = (String) ctf.lookup(EMAIL_PASSWORD_JNDI_NAME);
        server = (String) ctf.lookup(SMTP_SERVER_JNDI_NAME);
        port = (Integer) ctf.lookup(SMTP_PORT_JNDI_NAME);
    }

...
}

>> Using @PostConstruct is not exactly the same as you had. init() method will not work outside the container like in JUnit test.

You still need to provide the default not parametrized constructor (part of the The EJB 3.1 spec, section 4.9.2 Bean Classes).

Upvotes: 2

Koenighotze
Koenighotze

Reputation: 41

The injection has not yet happened.

In short:

  1. container calls the constructor
  2. then the injected fields get injected
  3. then the postconstruct stuff happens

Thus extract the lookups, as suggested, into a postconstruct method

Upvotes: 4

Magic Wand
Magic Wand

Reputation: 1760

Why you don't remove initialization from constructor into a @PostConstruct method?

@Stateless
public class EmailService {
    @EJB ContextFinder ctf;

    private static final String EMAIL_PASSWORD_JNDI_NAME = "EMAIL_PASSWORD";
    private static final String EMAIL_USERNAME_JNDI_NAME = "EMAIL_USERNAME";
    private static final String SMTP_SERVER_JNDI_NAME = "SMTP_SERVER";
    private static final String SMTP_PORT_JNDI_NAME = "SMTP_PORT";

    private String username;
    private String password;
    private String server;
    private Integer port;

    @PostConstruct
    public void init() { 
        username = (String) ctf.lookup(EMAIL_USERNAME_JNDI_NAME);
        password = (String) ctf.lookup(EMAIL_PASSWORD_JNDI_NAME);
        server = (String) ctf.lookup(SMTP_SERVER_JNDI_NAME);
        port = (Integer) ctf.lookup(SMTP_PORT_JNDI_NAME);
    }

    public void sendMail(String sendTo, String subject, String message) {
        // send mail
    }

}

Upvotes: 2

Related Questions