MimeMessage.saveChanges is really slow
The following test is taking around 5 seconds to execute due to the inclusion of m.saveChanges()
.
import org.junit.Before;
import org.junit.Test;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Test
public void test1() throws MessagingException, IOException {
Session s = Session.getDefaultInstance(new Properties());
MimeMessage m = new MimeMessage(s);
m.setContent("<b>Hello</b>", "text/html; charset=utf-8");
m.saveChanges();
assertEquals(m.getContent(), "<b>Hello</b>");
assertEquals(m.getContentType(), "text/html; charset=utf-8");
}
I have also mocked the Session with mockito but it doesn't help:
Session s = mock(Session.class);
when(s.getProperties()).thenReturn(new Properties());
What is the problem here? What can I mock to speed things up?
Fix the most common mistakes people make when using JavaMail in your code first.
DNS lookup can hurt performance on some machines. For the JDK you can change the security properties for caching DNS lookup networkaddress.cache.ttl
and networkaddress.cache.negative.ttl
or set the system properties sun.net.inetaddr.ttl
and sun.net.inetaddr.negative.ttl
. The default behavior in JDK 7 and later does a good job of caching so you shouldn't have to change these settings.
Preferably, you can use the session properties to avoid some these lookups.
- Set session property for
mail.from
ormail.host
(not the protocol versions) as that will prevent the name lookup onInternetAddress.getLocalAddress(Session)
. CallingMimeMessage.saveChanges()
,MimeMessage.updateHeaders()
,MimeMessage.updateMessageID()
, orMimeMessage.setFrom()
will trigger a call to get the local address. If the above properties are not set then this method will attempt to query for the host name. By setting the properties, this method will pull the host name string from the session instead of attempting the expensive DNS lookup. - Set the session property for
mail.smtp.localhost
ormail.smtps.localhost
to prevent name lookup on the HELO command. - Set session property for
mail.smtp.from
ormail.smtps.from
to prevent lookup on EHLO command. - Alternatively, you can set the system property
mail.mime.address.usecanonicalhostname
tofalse
if your code is relying on thesetFrom()
but this will be handled if you applied point #1. - For IMAP, you can try to set
mail.imap.sasl.usecanonicalhostname
ormail.imaps.sasl.usecanonicalhostname
tofalse
which is the default value.
Since your are not transporting a message, apply rule #1 by changing your code to:
@Test
public void test1() throws MessagingException, IOException {
Properties props = new Properties();
props.put("mail.host", "localhost"); //Or use IP.
Session s = Session.getInstance(props);
MimeMessage m = new MimeMessage(s);
m.setContent("<b>Hello</b>", "text/html; charset=utf-8");
m.saveChanges();
assertEquals(m.getContent(), "<b>Hello</b>");
assertEquals(m.getContentType(), "text/html; charset=utf-8");
}
If you are transporting a message then combine the rules #1, #2, and #3 which will prevent accessing the host system for a name lookup. If you want to prevent all DNS lookups during a transport then you have to use IP addresses.