Apache Commons Email 配置参数详解

逐项讲解 Apache Commons Email 1.6.0 的可配置参数:SMTP 端口、SSL/STARTTLS 加密开关、认证、超时与字符集的真实默认值,含默认值探测程序、STARTTLS 失败与 SMTPS 成功两类实测输出。

Apache Commons EmailApache Commons Email2026/10/14

Apache Commons Email 配置参数详解

目标:系统梳理 Apache Commons Email 1.6.0 真实存在的可配置项——连接参数、加密参数、认证与超时参数、内容与头部参数——把每一项的真实默认值用探测程序取出来,并给配加密两类行为的实测输出。全部示例已在 Ubuntu 22.04(JDK 17 + Maven)实测通过。

一、环境信息

本章实验基于以下环境。Commons Email 是纯 Java 库,无常驻服务,参数全部通过 Email 家族对象上的 setter 传入;1.6.0 要求 Java 8 及以上运行环境。验证环境:

java -version 2>&1 | head -1
mvn -v | head -1

实测回显(Ubuntu 22.04):

openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3

本文示例统一使用以下 Maven 依赖(坐标与版本为 1.6.0 官方 pom 实测值):

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-email</artifactId>
  <version>1.6.0</version>
</dependency>

说明:commons-email 是本体,com.sun.mail:jakarta.mail:1.6.7 与 com.sun.activation:jakarta.activation:1.2.1 是必需传递依赖,Maven 会自动带入,无需显式声明。

💡 技术拓展:配置 Commons Email 前,先分清「邮件协议」和「Java 邮件库」两个层面。协议是客户端与邮件服务器之间收发信的标准(SMTP/SMTPS/IMAP/POP3);库是帮你按这些协议对话的编程接口。常见协议与 Java 库的对比如下:

名称归属作用典型端口
SMTP协议客户端把邮件交给发信服务器25(明文)、587(提交)
SMTPS协议从连接起就用 TLS 加密的 SMTP465
STARTTLS协议扩展明文连接上协商升级为加密25/587
IMAP协议客户端从服务器读取/保持邮件143(明文)、993(TLS)
POP3协议客户端从服务器下载邮件110(明文)、995(TLS)
JavaMail(jakarta.mail)库协议底层实现,Commons Email 的依赖不直接暴露
Commons Email库在 JavaMail 之上封装发信 API不直接暴露
Spring Mail库Spring 体系封装,底层同样用 JavaMail不直接暴露

本文的参数分属两类:一类是「会话参数」(端口、加密、认证、超时),在 send() 建立连接时生效;一类是「报文参数」(字符集、收件人、头部),在组装 MIME 报文时生效。本文用的实测 SMTP 服务器监听 25(明文)与 465(TLS 自签证书)两个端口。

二、连接参数:默认值探测

Commons Email 的连接参数分属 Email 基类,创建对象后即带有默认值。写一个探测程序把它们逐项打印出来:

package net.quzuo.demo;

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

public class ConfigProbe {
    public static void main(String[] args) {
        Email email = new SimpleEmail();
        System.out.println("hostName                  : " + email.getHostName());
        System.out.println("smtpPort                  : " + email.getSmtpPort());
        System.out.println("sslSmtpPort               : " + email.getSslSmtpPort());
        System.out.println("socketTimeout(ms)         : " + email.getSocketTimeout());
        System.out.println("socketConnectionTimeout   : " + email.getSocketConnectionTimeout());
        System.out.println("startTLSEnabled           : " + email.isStartTLSEnabled());
        System.out.println("startTLSRequired          : " + email.isStartTLSRequired());
        System.out.println("sslOnConnect              : " + email.isSSLOnConnect());
        System.out.println("sslCheckServerIdentity    : " + email.isSSLCheckServerIdentity());
        System.out.println("debug                     : " + email.isDebug());
        System.out.println("charset                   : " + email.getCharsetName());
    }
}

程序逻辑:新建一个 SimpleEmail,用各 getter 打印出厂默认值。注意 getCharsetName() 是 1.6.0 才引入的字符集 getter(旧版本没有这个方法)。编译运行:

mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.ConfigProbe

实测回显:

hostName                  : null
smtpPort                  : 25
sslSmtpPort               : 465
socketTimeout(ms)         : 60000
socketConnectionTimeout   : 60000
startTLSEnabled           : false
startTLSRequired          : false
sslOnConnect              : false
sslCheckServerIdentity    : false
debug                     : false
charset                   : null

整理成默认值速查表:

参数默认值含义
hostNamenull发信服务器地址,必填
smtpPort25明文/STARTTLS 发信端口
sslSmtpPort465SSL 直连端口
socketTimeout60000(毫秒)socket 读写超时
socketConnectionTimeout60000(毫秒)socket 建连超时
startTLSEnabledfalse是否请求 STARTTLS
startTLSRequiredfalse是否强制要求 STARTTLS
sslOnConnectfalse是否从连接起用 SSL
sslCheckServerIdentityfalse是否校验服务器证书身份(防中间人)
debugfalse是否打印 JavaMail 调试日志
charsetnull报文编码,null 时用平台默认

两个实测确认的细节:一是超时默认值来自源码常量 EmailConstants.SOCKET_TIMEOUT = Duration.ofMinutes(1)(即 60000 毫秒),并非「无超时」;二是老的 protected int socketTimeout 字段已标注 @Deprecated,但 getter 仍返回同一数值。修改端口的实测(setSmtpPort 与 setSSLOnConnect 联动的行为):

SimpleEmail a = new SimpleEmail();
a.setSmtpPort(587);
System.out.println("setSmtpPort(587) 后 getSmtpPort(): " + a.getSmtpPort());

SimpleEmail b = new SimpleEmail();
b.setSSLOnConnect(true);
System.out.println("setSSLOnConnect(true) 后 isSSLOnConnect(): " + b.isSSLOnConnect()
        + ", getSslSmtpPort(): " + b.getSslSmtpPort());

实测回显:

setSmtpPort(587) 后 getSmtpPort(): 587
setSSLOnConnect(true) 后 isSSLOnConnect(): true, getSslSmtpPort(): 465

setSmtpPort(int) 会覆盖明文端口;setSSLOnConnect(true) 只是打开开关,SSL 端口仍由 setSslSmtpPort 单独控制(默认 465)。要在建连后改端口须在 send() 之前调用,否则会抛 IllegalStateException(源码里每个 setter 都先 checkSessionAlreadyInitialized())。

三、加密参数:STARTTLS 与 SMTPS

加密参数是三个布尔开关的组合:startTLSEnabled、startTLSRequired、sslOnConnect。源码注释给出的语义是:

开关默认生效条件服务器不支持时
startTLSEnabledfalse服务器广告 STARTTLS 则升级明文继续发送(不报错)
startTLSRequiredfalse强制要求 STARTTLS抛 EmailException
sslOnConnectfalse连接即用 SSL(SMTPS)端口不通则建连失败

先验证 startTLSRequired(true) 遇到不支持 STARTTLS 的服务器会怎样。程序:

package net.quzuo.demo;

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

public class StartTlsProbe {
    public static void main(String[] args) throws Exception {
        SimpleEmail tls = new SimpleEmail();
        tls.setHostName("127.0.0.1");
        tls.setSmtpPort(25);
        tls.setStartTLSRequired(true);
        tls.setFrom("sender@quzuo.net");
        tls.setSubject("starttls required probe");
        tls.setMsg("this should fail");
        tls.addTo("receiver@quzuo.net");
        try {
            tls.send();
            System.out.println("结果: 发送成功(未预期)");
        } catch (EmailException e) {
            Throwable cur = e;
            int i = 1;
            while (cur != null) {
                System.out.println("[" + i + "] " + cur.getClass().getName() + ": " + cur.getMessage());
                cur = cur.getCause();
                i++;
            }
        }
    }
}

程序逻辑:开 startTLSRequired(true) 后向监听 25 的服务器发信,逐层打印异常链。实测回显:

[1] org.apache.commons.mail.EmailException: Sending the email to the following server failed : 127.0.0.1:25
[2] javax.mail.MessagingException: STARTTLS is required but host does not support STARTTLS

第二行是根因:服务器未广告 STARTTLS,而客户端强制要求,于是 JavaMail 拒绝明文继续。这印证了 startTLSRequired 的行为——用它可以「确保不降级为明文」,代价是遇到不支持 STARTTLS 的服务器直接发送失败。

再看 SSL 直连(SMTPS)的成功路径。服务器 465 端口挂了一张自签证书,Java 默认信任库不认它,需要把证书导入信任库并让 JVM 指向它。生成信任库:

keytool -importcert -alias quzuo-mail -file cert.pem -keystore truststore.jks -storepass changeit -noprompt

实测回显:

Certificate was added to keystore

发信程序(setSSLOnConnect(true) 走 465):

package net.quzuo.demo;

import org.apache.commons.mail.SimpleEmail;

public class SmtpsDemo {
    public static void main(String[] args) throws Exception {
        SimpleEmail email = new SimpleEmail();
        email.setHostName("127.0.0.1");
        email.setSSLOnConnect(true);
        email.setSslSmtpPort("465");
        email.setCharset("UTF-8");
        email.setFrom("sender@quzuo.net");
        email.setSubject("SMTPS over TLS");
        email.setMsg("这是一封通过 SMTPS(465) 加密通道发送的邮件。");
        email.addTo("receiver@quzuo.net");
        String id = email.send();
        System.out.println("SMTPS 发送成功, Message-ID: " + id);
        System.out.println("smtpPort=" + email.getSmtpPort() + ", sslSmtpPort=" + email.getSslSmtpPort());
    }
}

带信任库参数运行:

JAVA_TOOL_OPTIONS="-Djavax.net.ssl.trustStore=/opt/mail-lab/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit" \
  mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.SmtpsDemo

实测回显:

Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/opt/mail-lab/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit
SMTPS 发送成功, Message-ID: <2136179907.0.1791909896502@localhost>
smtpPort=465, sslSmtpPort=465

setSSLOnConnect(true) 后 getSmtpPort() 也返回 465——源码在 send() 里用 sslSmtpPort 覆盖了会话端口。注意 sslCheckServerIdentity 默认 false,意味着默认不做证书主机名校验;生产环境建议显式 setSSLCheckServerIdentity(true),并保证证书 CN/SAN 与 hostName 一致,否则会因校验失败而发不出去。

四、认证与超时参数

认证通过 setAuthentication(user, password) 配置,底层走 JavaMail 的 AUTH 机制。写程序向广告 AUTH LOGIN PLAIN 的服务器发信:

package net.quzuo.demo;

import org.apache.commons.mail.SimpleEmail;

public class AuthProbe {
    public static void main(String[] args) throws Exception {
        SimpleEmail email = new SimpleEmail();
        email.setHostName("127.0.0.1");
        email.setSmtpPort(25);
        email.setAuthentication("demo-user", "demo-pass");
        email.setCharset("UTF-8");
        email.setFrom("sender@quzuo.net");
        email.setSubject("auth login probe");
        email.setMsg("hello with AUTH LOGIN");
        email.addTo("receiver@quzuo.net");
        String id = email.send();
        System.out.println("认证发信成功, Message-ID: " + id);
    }
}

实测回显(程序侧):

认证发信成功, Message-ID: <1231178415.0.1791909606479@localhost>

服务器端抓到的完整 SMTP 会话(>>> 客户端、<<< 服务器):

>>> EHLO localhost
<<< 250-mail-lab.local | 250-SIZE 10485760 | 250-8BITMIME | 250 AUTH LOGIN PLAIN
>>> AUTH LOGIN
<<< 334 VXNlcm5hbWU6 (Username:)
>>> ZGVtby11c2Vy  <- base64(用户名)
<<< 334 UGFzc3dvcmQ6 (Password:)
>>> ZGVtby1wYXNz  <- base64(口令)
<<< 235 2.7.0 Authentication successful
>>> MAIL FROM:<sender@quzuo.net>
<<< 250 2.1.0 Ok sender
>>> RCPT TO:<receiver@quzuo.net>
<<< 250 2.1.5 Ok recipient
>>> DATA
<<< 354 End data with <CR><LF>.<CR><LF>
<<< 250 OK, message accepted
>>> QUIT
<<< 221 2.0.0 Bye

三个要点:ZGVtby11c2Vy 即 demo-user 的 base64,ZGVtby1wYXNz 即 demo-pass 的 base64——AUTH LOGIN 口令是 base64 而非加密,所以认证必须配合 STARTTLS 或 SMTPS 使用,否则口令可被旁路嗅探;服务器只在 EHLO 里广告了 AUTH LOGIN PLAIN,客户端就选了 AUTH LOGIN(没有 TLS 时 JavaMail 默认会优先明文可用的认证方式)。

超时参数实测:默认 60000 毫秒已见第二章。要缩短超时可显式调用(注意 1.6.0 的整型 setter 已 @Deprecated,推荐传入 java.time.Duration):

方法作用底层 JavaMail 属性
setSocketTimeout(Duration/int)socket 读写超时mail.smtp.timeout
setSocketConnectionTimeout(Duration/int)socket 建连超时mail.smtp.connectiontimeout
email.setSocketTimeout(java.time.Duration.ofSeconds(10));
email.setSocketConnectionTimeout(java.time.Duration.ofSeconds(5));

逐项说明:这两个值最终写进 JavaMail 会话属性(源码里只在值大于 0 时才写入,等于 0 表示用 JavaMail 自身默认即「不超时」);连接不可达的服务器时,建连超时先触发;连接已建立但服务器迟迟不响应时,读写超时触发。

五、内容与头部参数

报文侧的参数集中在收件人、抄送、密送、回复地址、自定义头与字符集。写一个综合程序:

package net.quzuo.demo;

import org.apache.commons.mail.HtmlEmail;

public class HeaderDemo {
    public static void main(String[] args) throws Exception {
        HtmlEmail email = new HtmlEmail();
        email.setHostName("127.0.0.1");
        email.setSmtpPort(25);
        email.setCharset("UTF-8");
        email.setFrom("sender@quzuo.net", "Quzuo 发信机器人");
        email.addTo("receiver@quzuo.net", "收件人");
        email.addCc("cc@quzuo.net");
        email.addBcc("bcc@quzuo.net");
        email.addReplyTo("support@quzuo.net", "支持团队");
        email.addHeader("X-Campaign", "commons-email-lab");
        email.setSubject("Commons Email 头部参数示例");
        email.setHtmlMsg("<p>包含抄送、密送、回复地址与自定义头。</p>");
        email.setTextMsg("纯文本回退内容。");
        String id = email.send();
        System.out.println("发送成功, Message-ID: " + id);
    }
}

实测回显:

发送成功, Message-ID: <357678218.3.1791909967619@localhost>

服务器落盘报文的首部(实测节选):

From: =?UTF-8?Q?Quzuo_=E5=8F=91=E4=BF=A1=E6=9C=BA=E5=99=A8=E4=BA=BA?= <sender@quzuo.net>
Reply-To: =?UTF-8?B?5pSv5oyB5Zui6Zif?= <support@quzuo.net>
To: =?UTF-8?B?5pS25Lu25Lq6?= <receiver@quzuo.net>
Cc: cc@quzuo.net
Subject: =?UTF-8?Q?Commons_Email_=E5=A4=B4=E9=83=A8=E5=8F=82=E6=95=B0=E7=A4=BA=E4=BE=8B?=
X-Campaign: commons-email-lab

逐项说明:setFrom(email, name) 第二个参数是显示名,中文显示名会被 MIME 编码(=?UTF-8?Q?...?= 或 =?UTF-8?B?...?=);addReplyTo 写入 Reply-To 头;addCc 写入 Cc;addHeader 写入自定义头 X-Campaign。注意报文里看不到 Bcc——密送地址只用于 SMTP 的 RCPT TO,不会写进报文头,这正是 BCC 的本意(收件人互相不可见)。

字符集参数:setCharset(String) 影响显示名与正文编码,默认 null(源码 getter getCharsetName() 返回 null,此时用平台默认编码)。实测建议显式设为 UTF-8,否则中文显示名在部分平台上会被编成非 UTF-8 字节导致客户端乱码。setDebug(true) 会打开 JavaMail 的会话调试日志(默认 false),排查连接问题时很有用:

DEBUG SMTP: trying to connect to host "127.0.0.1", port 25, isSSL false
DEBUG SMTP: connected to host "127.0.0.1", port: 25

六、常见问题

Q1:setSSLOnConnect(true) 后为什么 getSmtpPort() 变成 465? 因为源码在建立会话时用 sslSmtpPort 覆盖了会话端口(实测 smtpPort=465, sslSmtpPort=465)。明文端口与 SSL 端口是两个独立 setter,setSmtpPort 管 25/587,setSslSmtpPort 管 465。

Q2:startTLSEnabled 和 startTLSRequired 有什么区别? enabled 是「服务器支持就升级,不支持就明文继续」;required 是「必须升级,不支持就报错」。实测对不支持 STARTTLS 的服务器开 required,会拿到 javax.mail.MessagingException: STARTTLS is required but host does not support STARTTLS(见第三章)。

Q3:连的是自签证书服务器,报证书校验失败怎么办? Java 默认信任库不含该证书。可把证书导入信任库(keytool -importcert ... -keystore truststore.jks),再用 -Djavax.net.ssl.trustStore=truststore.jks 让 JVM 指向它(见第三章实测)。生产环境更推荐换成受信任 CA 证书,而不是绕过校验。

Q4:认证口令会明文上网吗? AUTH LOGIN/PLAIN 的口令本身是 base64 编码(实测 ZGVtby1wYXNz),不是加密,可被 base64 解码还原。因此认证必须配合 STARTTLS 或 SMTPS 通道使用,切勿在明文连接上做认证。

Q5:超时默认为多少?设成 0 会怎样? 默认 60000 毫秒(源码 EmailConstants.SOCKET_TIMEOUT = Duration.ofMinutes(1))。源码只在值大于 0 时写入会话属性,因此设成 0 等于交回 JavaMail 默认(通常是不超时,不推荐)。

七、参考链接

  1. Apache Commons Email 官方主页:https://commons.apache.org/proper/commons-email/
  2. 1.6.0 官方 javadoc(Email 各 setter 的权威说明):https://commons.apache.org/proper/commons-email/apidocs/org/apache/commons/mail/Email.html
  3. 官方用户指南:https://commons.apache.org/proper/commons-email/userguide.html
  4. Maven 中央仓库 1.6.0(pom 与 jar):https://repo1.maven.org/maven2/org/apache/commons/commons-email/1.6.0/
  5. JavaMail 项目(jakarta.mail):https://eclipse-ee4j.github.io/mail/
  6. JavaMail SMTP 属性总表(timeout 等底层键):https://eclipse-ee4j.github.io/mail/docs/api/jakarta.mail/com/sun/mail/smtp/package-summary.html