smtp-connection-pool keeps Jakarta Mail Transport connections open and leases each physical connection exclusively to one caller at a time. It supports lazy or eager allocation, bounded waiting, expiration, and clusters of SMTP servers.
It does not build messages or decide how SMTP works. The selected Jakarta Mail provider still owns authentication, TLS, EHLO, PIPELINING, CHUNKING, response parsing, and the actual send.
Version 4.0.0 expands the project beyond its direct API with optional Jakarta Mail and Camel integrations. Existing users can keep the same org.simplejavamail:smtp-connection-pool dependency and direct API.
The best introduction is the non-published demo project. Every example runs against a real dummy SMTP server on a random port and asserts message delivery, physical connection reuse, and clean shutdown.
| Runnable example | Integration shown | Verified result |
|---|---|---|
| DirectPoolDemo | Direct leases, release, forced failure, invalidation, and recovery | 3 messages over 1 connection; then replacement after a dropped connection |
| SimpleJavaMailDemo | Simple Java Mail as a higher-level library built directly on the pool | 3 messages over 1 connection |
| BatchModuleDemo | Standalone Simple Java Mail batch callbacks over caller-created Jakarta Mail messages | 3 messages over 1 connection |
| JakartaMailDemo | Plain Jakarta Mail with smtppool |
3 messages over 1 connection |
| SpringDemo | Spring JavaMailSenderImpl with smtppool |
3 messages over 1 connection |
| CamelDemo | Camel with the separate smtppool: adapter |
3 messages over 1 connection |
Run the complete executable suite with JDK 21:
mvn -pl smtp-connection-pool-demo -am testOr run DemoLauncher or any individual demo directly from IntelliJ. The demo is built and tested with the rest of the project, but is deliberately excluded from Maven Central. BatchModuleDemo uses the supported standalone path-2 API published in Simple Java Mail 9.3.0 through #698.
Start by deciding whether you need a pool at all. Then choose the one layer that should own it:
| Option | Best when | Orchestration owner | Lease handling | Pooling/clustering |
|---|---|---|---|---|
No pool: withOpenConnection / simple batch |
One sequential unit of work | Application / Simple Java Mail | Not applicable | No |
Simple Java Mail Mailer + batch-module |
Using EmailBuilder and Mailer |
Simple Java Mail | Automatic | Yes |
Standalone batch-module facade |
Creating MimeMessage objects while wanting managed callbacks and futures |
Batch facade | Automatic | Yes |
Direct smtp-connection-pool |
Needing exact claim, failure, and shutdown control | Application | Explicit lease | Yes |
Jakarta smtppool provider |
Plain Jakarta Mail or Spring owns Transport calls |
Provider | Mapped to connect / close |
Yes |
Camel smtppool: adapter |
Camel owns endpoints and component lifecycle | Camel adapter / provider | Automatic | Yes |
Exactly one component owns the physical connection pool. Never place
batch-moduleor a direct pool around ansmtppooltransport.
The complete chooser, ownership model, and examples live in Simple Java Mail's SMTP connection pooling and batch orchestration guide.
The pooled choices reduce to three integration paths, not three abstraction levels inside this repository.
| Path | Choose it when | Who manages the pool | Status |
|---|---|---|---|
| 1. Use the pool directly | Your application or a higher-level library needs clustering, explicit leases, and complete failure/shutdown control. Simple Java Mail itself belongs here. | Your application or library | Available; explicit SmtpTransportLease is available since 4.0.0 |
2. Use Simple Java Mail's batch-module directly |
You create Jakarta Mail messages yourself but want Simple Java Mail's asynchronous batch engine and a safe callback API without adopting EmailBuilder and Mailer. |
Simple Java Mail's batch API | Available since Simple Java Mail 9.3.0 through #698 |
3. Use it as a Jakarta Mail Transport |
Plain Jakarta Mail, Spring, or Camel already obtains and closes Transport instances. |
PooledTransport |
Available since 4.0.0 through #10 |
Simple Java Mail stays on path 1 internally. Path 2 is a narrower public API over part of its batch engine. Path 3 presents the pool as a normal Jakarta Mail transport protocol.
The architecture, ownership rules, and five flow diagrams are in PRODUCT-VISION.md.
All three published modules are released together at one version. Maven Central also receives the shared smtp-connection-pool-parent POM required by Maven; it is build metadata, not a fourth application dependency.
| Artifact | Purpose | Java |
|---|---|---|
org.simplejavamail:smtp-connection-pool |
Direct and clustered pool APIs plus SmtpTransportLease |
8+ |
org.simplejavamail:smtp-connection-pool-jakarta-provider |
Discoverable smtppool Jakarta Mail provider and Session-scoped lifecycle registry |
8+ |
org.simplejavamail:smtp-connection-pool-camel |
Optional Camel Mail selection adapter; pooling remains in the provider module | 17+ (Camel 4.21) |
Starting with 4.0.1, every published JAR declares a stable Automatic-Module-Name:
| Artifact | Module name |
|---|---|
smtp-connection-pool |
org.simplejavamail.smtpconnectionpool |
smtp-connection-pool-jakarta-provider |
org.simplejavamail.smtpconnectionpool.jakarta |
smtp-connection-pool-camel |
org.simplejavamail.smtpconnectionpool.camel |
The transitive object-pool chain is stable as well: generic-object-pool 2.4.2 declares
org.bbottema.genericobjectpool, and clustered-object-pool 4.0.3 declares
org.bbottema.clusteredobjectpool. The build inspects every packaged manifest and compiles a real
module-path consumer requiring all five names.
The repository also contains smtp-connection-pool-demo. It is an example project tested with every build, not a fourth published module, and is explicitly excluded from Maven Central.
The provider module lets Jakarta Mail, Spring, and Camel obtain pooled Transport instances; it does not speak SMTP itself. Applications still supply Angus Mail or another compatible SMTP Transport provider. Each underlying Transport must represent one reusable physical connection—do not hide a second connection pool beneath this one.
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>smtp-connection-pool</artifactId>
<version>4.0.1</version>
</dependency>Create a Session normally, then claim one exclusive lease. Closing an active lease releases it; invalidate it first when a failure makes the connection uncertain.
SmtpConnectionPool pool = new SmtpConnectionPool(new SmtpClusterConfig<Session>());
try (SmtpTransportLease lease = pool.claimTransport(session)) {
try {
Session selectedSession = lease.getSession();
Transport transport = lease.getTransport();
transport.sendMessage(message, message.getAllRecipients());
} catch (MessagingException | RuntimeException failure) {
lease.invalidate();
throw failure;
}
}
// Application shutdown: wait until active leases return and connections close.
pool.shutDown().get();claimTransport can block and throws InterruptedException. Preserve the thread's interruption policy. The default pool is lazy, has a maximum of four physical connections per Session, and makes an available transport eligible for expiration ten seconds after its last claim.
For partial-recipient failures, an advanced integration may release rather than invalidate only when the delegate is demonstrably still connected. Unknown failures should be treated conservatively.
SmtpClusterConfig<UUID> config = new SmtpClusterConfig<>();
config.getConfigBuilder()
.defaultCorePoolSize(0)
.defaultMaxPoolSize(10)
.loadBalancingStrategy(new RandomAccessLoadBalancing<>())
.claimTimeout(new Timeout(30, SECONDS));
SmtpConnectionPoolClustered<UUID> pool = new SmtpConnectionPoolClustered<>(config);
ResourceClusterAndPoolKey<UUID, Session> server =
new ResourceClusterAndPoolKey<>(clusterId, session);
try (SmtpTransportLease lease = pool.claimTransport(server)) {
lease.getTransport().sendMessage(message, message.getAllRecipients());
}Clusters and pools are created on demand. Use registerResourceCluster or registerResourcePool when a cluster or server needs different sizing, expiration, or load-balancing behavior.
The direct allocator resolves a thread-safe token supplier only when it opens or reconnects a physical transport:
session.getProperties().put(
SmtpConnectionPool.OAUTH2_TOKEN_PROVIDER_PROPERTY,
(Supplier<String>) tokenProvider::getAccessToken);The supplier owns caching and refresh. A fixed token remains available through OAUTH2_TOKEN_PROPERTY for short-lived use.
This path is deliberately delivered in Simple Java Mail, not in this repository. Simple Java Mail #698 added a public callback API in Simple Java Mail 9.3.0 for applications that already create Jakarta Mail messages but want asynchronous execution, clustering, and safe release/invalidate handling without adopting the full EmailBuilder/Mailer API.
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>batch-module</artifactId>
<version>9.3.0</version>
</dependency>try (BatchTransportExecutor<String> batch =
BatchTransportExecutor.<String>builder().build()) {
batch.registerSession("outbound", session);
batch.execute("outbound", (selectedSession, transport) -> {
MimeMessage message = new MimeMessage(selectedSession);
// populate recipients, subject, and content
transport.sendMessage(message, message.getAllRecipients());
return null;
});
}BatchTransportExecutor uses SmtpTransportLease internally and remains separate from smtppool. It releases after a successful callback, invalidates after an escaping failure, and owns only the default executor it creates. BatchModuleDemo runs this exact path against a real dummy SMTP server.
Add the provider plus a physical Jakarta Mail implementation:
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>smtp-connection-pool-jakarta-provider</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>2.0.5</version>
</dependency>The provider registers only smtppool; it never replaces or pretends to be smtp or smtps.
Properties properties = new Properties();
properties.setProperty(SmtpPoolProperties.DELEGATE_PROTOCOL, "smtp"); // default
Session session = Session.getInstance(properties);
Transport transport = session.getTransport("smtppool");
transport.connect(host, port, username, password); // claims and connects if needed
try {
transport.sendMessage(message, message.getAllRecipients());
} finally {
transport.close(); // releases a healthy lease; invalidates an unhealthy one
}
Future<?> shutdown = SmtpPoolRegistry.shutdown(session); // graceful
shutdown.get();Graceful shutdown stops new claims and waits for active leases. If a bounded wait expires, shutdownNow(session) invalidates active leases and returns the same completion handle, which completes only after physical Transport.close() calls finish. A Session can be reused only after that shutdown completes and SmtpPoolRegistry.restart(session) is called explicitly.
When credentials or OAuth tokens rotate for the same endpoint, a new credential-isolated pool becomes current and the superseded pool drains. Its retained credential material is cleared as soon as its last active lease finishes; inactive credential generations do not accumulate.
Spring uses the same provider by configuring JavaMailSenderImpl with protocol smtppool. Camel uses the separate adapter and smtppool: or smtppools: endpoint schemes:
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>smtp-connection-pool-camel</artifactId>
<version>4.0.1</version>
</dependency>to("smtppool://smtp.example.com:587"
+ "?username=user&password=secret&to=recipient@example.com");See the provider reference and Camel reference for configuration, programmatic delegate selection, credential rotation, and shutdown semantics.
Build the complete project with JDK 21 and Maven. The original pool and Jakarta provider still run on Java 8; the Camel and demo modules require Java 17.
mvn clean verifyVerification runs all module tests, the real-server demo smoke tests, SpotBugs, Javadocs, and a checksum-pinned japicmp comparison with the preceding published 4.0.0 library. CircleCI also compiles and tests the original pool plus Jakarta provider on an actual JDK 8. Its JDK 21 release job versions and publishes all public modules together while leaving out the demo.
Simple Java Mail #699 may produce a faster physical transport. If it implements Jakarta Mail's synchronous Transport contract with one reusable physical session per instance, it can be selected beneath these paths just like Angus. If it instead uses Simple Java Mail's CustomMailer, it owns its own lifecycle and must not be stacked on this pool.
4.0.1 (11 August 2026)
- #11: declare stable JPMS automatic module names for all three published JARs, consume the fixed object-pool module chain, inspect the packaged manifests, and compile an end-to-end module-path consumer.
4.0.0 (10 August 2026)
- #10: add the explicit lease API, optional
smtppoolJakarta Mail provider, and separate Camel adapter without changing the existingorg.simplejavamail:smtp-connection-pooldependency or direct API. Harden credential rotation and graceful/forced shutdown usinggeneric-object-pool 2.4.1andclustered-object-pool 4.0.2.
3.1.0 (7 August 2026)
- #9: resolve a current OAuth2 token whenever a physical SMTP transport is opened or reconnected.
3.0.1 (6 July 2026)
- #8: update
clustered-object-poolto 4.0.1 so clustered SMTP pools can use cluster-specific defaults.