Search code examples
spring-bootjakarta-mail

How to have JavaMailSender not fail the application context loading on a wrong mail password?


I'm using JavaMailSender in a Sring Boot application and if a property like the password for example, is wrong so as to prevent the logging in the mail gateway, then it canot autowire the bean and the application context fails to load.

The Spring mail properties:

spring.mail.host=smtp.gmail.com
spring.mail.port=465
spring.mail.protocol=smtp
[email protected]
spring.mail.password=xxx
[email protected]

The properties are loaded by a property reader:

public abstract class AbstractUserProperties implements ApplicationProperties {

    @Value("${" + PropertyNames.CONFIG_AUTH_TOKEN_PRIVATE_KEY + "}")
    private String authenticationTokenPrivateKey;

    @Value("${" + PropertyNames.CONFIG_MAIL_HOST + "}")
    private String host;

    @Value("${" + PropertyNames.CONFIG_MAIL_PORT + "}")
    private String port;

    @Value("${" + PropertyNames.CONFIG_MAIL_PROTOCOL + "}")
    private String protocol;

    @Value("${" + PropertyNames.CONFIG_MAIL_USERNAME + "}")
    private String username;

    @Value("${" + PropertyNames.CONFIG_MAIL_PASSWORD + "}")
    private String password;

    @Value("${" + PropertyNames.CONFIG_MAIL_FROM + "}")
    private String mailFrom;

    @Value("${" + PropertyNames.CONFIG_MAIL_TEST_CONNECTION + "}")
    private boolean mailTestConnection;

    @Value("${" + PropertyNames.CONFIG_MAILING_ENABLED + "}")
    private boolean mailingEnabled;

    public String getAuthenticationTokenPrivateKey() {
        return authenticationTokenPrivateKey;
    }

    @Override
    public String getHost() {
        return host;
    }

    @Override
    public String getPort() {
        return port;
    }

    @Override
    public String getProtocol() {
        return protocol;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getMailFrom() {
        return mailFrom;
    }

    @Override
    public boolean getMailTestConnection() {
        return mailTestConnection;
    }

    @Override
    public boolean getMailingEnabled() {
        return mailingEnabled;
    }

}

How to have it so that a wrong mail password does not prevent the application from starting ?

The dependencies:

<version>2.0.3.RELEASE</version>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

I'm on Spring Boot 2.0.3.

Here is the mail configuration of the application:

@Configuration
public class MailConfig {

    @Autowired
    private ApplicationProperties applicationProperties;

    @Bean
    public JavaMailSender javaMailSender() {
        String host = applicationProperties.getHost();
        String port = applicationProperties.getPort();
        String protocol = applicationProperties.getProtocol();
        String username = applicationProperties.getUsername();
        String password = applicationProperties.getPassword();
        if (!password.isEmpty() && !username.isEmpty() && !protocol.isEmpty() && !port.isEmpty() && !host.isEmpty()) {
            JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
            javaMailSender.setHost(host);
            javaMailSender.setPort(Integer.parseInt(port));
            javaMailSender.setProtocol(protocol);
            javaMailSender.setUsername(username);
            javaMailSender.setPassword(password);
            javaMailSender.setJavaMailProperties(getMailProperties());
            return javaMailSender;
        } else {
            return null;
        }
    }

    @Bean
    public SimpleMailMessage simpleMailMessage() {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setFrom(applicationProperties.getMailFrom());
        return simpleMailMessage;
    }

    private Properties getMailProperties() {
        Properties properties = new Properties();
        properties.setProperty("mail.smtp.auth", "true");
        properties.setProperty("mail.smtp.starttls.enable", "false");
        properties.setProperty("mail.smtp.quitwait", "false");
        properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        properties.setProperty("mail.smtp.socketFactory.fallback", "false");
        properties.setProperty("mail.debug", "true");
        return properties;
    }

}

UPDATE: I have placed the spring.mail.test-connection=false property in the application.properties file and the console log shows it is copied:

[INFO] Copying 1 resource
[DEBUG] Copying file application.properties

I have also removed the MailConfig class so as to use the one provided by Spring by default. The error message remains identical though. As a side note, I wonder how I could use that property in my own bean configuration instead of having to use the default Spring bean.

UPDATE: I could add the mail.test-connection property in my configuration bean as in:

private Properties getMailProperties() {
    Properties properties = new Properties();
    properties.setProperty("mail.smtp.auth", "true");
    properties.setProperty("mail.smtp.starttls.enable", "false");
    properties.setProperty("mail.smtp.quitwait", "false");
    properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    properties.setProperty("mail.smtp.socketFactory.fallback", "false");
    properties.setProperty("mail.debug", "true");
    properties.setProperty("mail.test-connection", String.valueOf(applicationProperties.getMailTestConnection()));
    return properties;
}

@Bean
public JavaMailSender javaMailSender() {
    String host = applicationProperties.getHost();
    String port = applicationProperties.getPort();
    String protocol = applicationProperties.getProtocol();
    String username = applicationProperties.getUsername();
    String password = applicationProperties.getPassword();
    JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
    javaMailSender.setHost(host);
    javaMailSender.setPort(Integer.parseInt(port));
    javaMailSender.setProtocol(protocol);
    javaMailSender.setUsername(username);
    javaMailSender.setPassword(password);
    javaMailSender.setJavaMailProperties(getMailProperties());
    return javaMailSender;
}

Now, even if no password is provided, the connection being not checked, the application context can load just fine.


Solution

  • You could add the following line to your application.properties file, so that the mail connection is not tested on the application startup:

    spring.mail.test-connection=false

    With this configuration, your mail connection will be checked if you try to send a mail for the first time and if e.g. your password is incorrect throw an exception.

    But I am not sure why you exactly want to do this :D

    UPDATE: If you want to know how Spring Boot does it, have a look at the XYZAutoConfiguration classes for the MailSender they test the connection with the following code:

    /**
     * {@link EnableAutoConfiguration Auto configuration} for testing mail service
     * connectivity on startup.
     *
     * @author Eddú Meléndez
     * @author Stephane Nicoll
     * @since 1.3.0
     */
    @Configuration
    @AutoConfigureAfter(MailSenderAutoConfiguration.class)
    @ConditionalOnProperty(prefix = "spring.mail", value = "test-connection")
    @ConditionalOnSingleCandidate(JavaMailSenderImpl.class)
    public class MailSenderValidatorAutoConfiguration {
    
        private final JavaMailSenderImpl mailSender;
    
        public MailSenderValidatorAutoConfiguration(JavaMailSenderImpl mailSender) {
            this.mailSender = mailSender;
        }
    
        @PostConstruct
        public void validateConnection() {
            try {
                this.mailSender.testConnection();
            }
            catch (MessagingException ex) {
                throw new IllegalStateException("Mail server is not available", ex);
            }
        }
    
    }