From 06c7ca09a46a7c47f54a4a5a3906af977e7cd679 Mon Sep 17 00:00:00 2001 From: Jorge Bescos Gascon Date: Wed, 1 Oct 2025 17:51:46 +0200 Subject: [PATCH] Custom mail providers distributed under META-INF/javamail.properties are no longer loaded with Jakarta Mail 2.1.5 #812 Signed-off-by: Jorge Bescos Gascon --- api/src/main/java/jakarta/mail/Provider.java | 3 +- api/src/main/java/jakarta/mail/Session.java | 163 ++++++++++-------- .../jakarta/mail/internet/MimeUtility.java | 42 ++--- .../test/java/jakarta/mail/SessionTest.java | 25 ++- .../mail/internet/MimeUtilityTest.java | 37 ++++ .../resources/META-INF/jakarta.charset.map | 19 ++ .../test/resources/META-INF/jakarta.providers | 4 +- .../resources/META-INF/javamail.charset.map | 19 ++ .../resources/META-INF/javamail.providers | 4 +- doc/release/CHANGES.txt | 1 + www/docs/COMPAT.txt | 16 +- 11 files changed, 233 insertions(+), 100 deletions(-) create mode 100644 api/src/test/java/jakarta/mail/internet/MimeUtilityTest.java create mode 100644 api/src/test/resources/META-INF/jakarta.charset.map create mode 100644 api/src/test/resources/META-INF/javamail.charset.map diff --git a/api/src/main/java/jakarta/mail/Provider.java b/api/src/main/java/jakarta/mail/Provider.java index 5d3caaf2..e6140fd8 100644 --- a/api/src/main/java/jakarta/mail/Provider.java +++ b/api/src/main/java/jakarta/mail/Provider.java @@ -22,7 +22,8 @@ * configuring providers using the Java SE * {@link java.util.ServiceLoader ServiceLoader} mechanism. * As an alternative the values could come from the - * javamail.providers and javamail.default.providers + * javamail.providers, javamail.default.providers, + * jakarta.providers and jakarta.default.providers * resource files. An application may also create and * register a Provider object to dynamically add support * for a new provider. diff --git a/api/src/main/java/jakarta/mail/Session.java b/api/src/main/java/jakarta/mail/Session.java index 5340b1eb..0e446d23 100644 --- a/api/src/main/java/jakarta/mail/Session.java +++ b/api/src/main/java/jakarta/mail/Session.java @@ -55,15 +55,33 @@ * implement the Store, Transport, and related * classes. The protocol providers are configured using the following files: * - *

- * Each jakarta.X resource file is searched for using - * three methods in the following order: + * + *

Compatibility note: Both javamail.* and + * jakarta.* resource files are currently supported. For + * backwards compatibility, javamail.* entries take precedence + * over equivalent jakarta.* entries when conflicts occur. + * This ensures existing applications depending on the legacy JavaMail + * configuration continue to work.
+ * Future note: Support for javamail.* resources is + * deprecated and will be removed in a future release. Applications should + * migrate to the jakarta.* resource files. + *

+ * + * Each javamail.X or jakarta.X + * resource file is searched for using three methods in the following order: *
    + *
  1. java.home/conf/javamail.X (deprecated)
  2. + *
  3. META-INF/javamail.X (deprecated)
  4. + *
  5. META-INF/javamail.default.X (deprecated)
  6. *
  7. java.home/conf/jakarta.X
  8. *
  9. META-INF/jakarta.X
  10. *
  11. META-INF/jakarta.default.X
  12. @@ -79,7 +97,8 @@ * java.home property points. The second method allows an * application that uses the Jakarta Mail APIs to include their own resource * files in their application's or jar file's META-INF - * directory. The jakarta.default.X default files + * directory. The javamail.default.X (deprecated) and + * jakarta.default.X default files * are part of the Jakarta Mail mail.jar file and should not be * supplied by users.

    * @@ -96,6 +115,8 @@ * do not override, the default files included with the Jakarta Mail APIs. * This means that all entries in all files loaded will be available.

    * + * javamail.providers (deprecated), + * javamail.default.providers (deprecated), * jakarta.providers and * jakarta.default.providers

    * @@ -168,18 +189,20 @@ * *

    * + * javamail.address.map (deprecated), + * javamail.default.address.map (deprecated), * jakarta.address.map and * jakarta.default.address.map

    * * These resource files map transport address types to the transport * protocol. The getType method of * jakarta.mail.Address returns the address type. The - * jakarta.address.map file maps the transport type to the - * protocol. The file format is a series of name-value pairs. Each key - * name should correspond to an address type that is currently installed - * on the system; there should also be an entry for each - * jakarta.mail.Address implementation that is present if it is - * to be used. For example, the + * javamail.address.map (deprecated) and jakarta.address.map + * files map the transport type to the protocol. The file format is a series + * of name-value pairs. Each key name should correspond to an address type + * that is currently installed on the system; there should also be an entry + * for each jakarta.mail.Address implementation that is present + * if it is to be used. For example, the * jakarta.mail.internet.InternetAddress method * getType returns "rfc822". Each referenced protocol should * be installed on the system. For the case of news, below, @@ -202,11 +225,12 @@ public final class Session { // Support legacy @DefaultProvider private static final String DEFAULT_PROVIDER = "org.eclipse.angus.mail.util.DefaultProvider"; - // This is for backwards compatibility. Eventually we have to remove the second value of the next arrays. - private static final String[] DEFAULT_ADDRESS_MAP_RESOURCES = new String[] {"/META-INF/jakarta.default.address.map", "/META-INF/javamail.default.address.map"}; - private static final String[] ADDRESS_MAP_RESOURCES = new String[] {"META-INF/jakarta.address.map", "META-INF/javamail.address.map"}; - private static final String[] DEFAULT_PROVIDER_RESOURCES = new String[] {"/META-INF/jakarta.default.providers", "/META-INF/javamail.default.providers"}; - private static final String[] PROVIDER_RESOURCES = new String[] {"META-INF/jakarta.providers", "META-INF/javamail.providers"}; + // This is for backwards compatibility. javamail has more precedence than jakarta to not break compatibility + // Eventually we have to remove the first value of the next arrays. + private static final String[] DEFAULT_ADDRESS_MAP_RESOURCES = new String[] {"/META-INF/javamail.default.address.map", "/META-INF/jakarta.default.address.map"}; + private static final String[] ADDRESS_MAP_RESOURCES = new String[] {"META-INF/javamail.address.map", "META-INF/jakarta.address.map"}; + private static final String[] DEFAULT_PROVIDER_RESOURCES = new String[] {"/META-INF/javamail.default.providers", "/META-INF/jakarta.default.providers"}; + private static final String[] PROVIDER_RESOURCES = new String[] {"META-INF/javamail.providers", "META-INF/jakarta.providers"}; private final StreamProvider streamProvider; private final Properties props; @@ -466,9 +490,16 @@ public synchronized PrintStream getDebugOut() { } /** - * This method returns an array of all the implementations installed - * via the jakarta.[default.]providers files that can - * be loaded using the ClassLoader available to this application. + * This method returns an array of all the provider implementations installed + * via the javamail.[default.]providers (deprecated) and + * jakarta.[default.]providers files that can + * be loaded using the ClassLoader available to this application. + *

    + * For backwards compatibility, providers defined in + * javamail.* files take precedence over equivalent + * jakarta.* entries when conflicts occur. Applications are + * encouraged to migrate to the jakarta.* resource files, as + * support for javamail.* will be removed in a future release. * * @return Array of configured providers */ @@ -949,10 +980,10 @@ public void load(InputStream is) throws IOException { } }; - // load system-wide jakarta.providers from the + // load system-wide javamail.providers and jakarta.providers from the // /{conf,lib} directory if (confDir != null) - loadFile(loader, confDir + "jakarta.providers", confDir + "javamail.providers"); + loadFile(loader, confDir + "javamail.providers", confDir + "jakarta.providers"); //Fetch classloader of given class, falling back to others if needed. ClassLoader gcl; @@ -978,10 +1009,10 @@ public void load(InputStream is) throws IOException { addProvider(p); } - // load the META-INF/jakarta.providers file supplied by an application + // load the META-INF/javamail.providers and META-INF/jakarta.providers file supplied by an application loadAllResources(cl, loader, PROVIDER_RESOURCES); - // load default META-INF/jakarta.default.providers from mail.jar file + // load default META-INF/javamail.providers and META-INF/jakarta.default.providers from mail.jar file loadResource(cl, loader, false, DEFAULT_PROVIDER_RESOURCES); // finally, add all the default services @@ -1102,7 +1133,8 @@ private void loadProvidersFromStream(InputStream is) throws IOException { */ public synchronized void addProvider(Provider provider) { providers.add(provider); - providersByClassName.put(provider.getClassName(), provider); + if (!providersByClassName.containsKey(provider.getClassName())) + providersByClassName.put(provider.getClassName(), provider); if (!providersByProtocol.containsKey(provider.getProtocol())) providersByProtocol.put(provider.getProtocol(), provider); } @@ -1117,16 +1149,16 @@ public void load(InputStream is) throws IOException { } }; - // load default META-INF/jakarta.default.address.map from mail.jar + // load default META-INF/javamail.default.address.map and META-INF/jakarta.default.address.map from mail.jar loadResource(cl, loader, true, DEFAULT_ADDRESS_MAP_RESOURCES); - // load the META-INF/jakarta.address.map file supplied by an app + // load the META-INF/javamail.address.map and META-INF/jakarta.address.map files supplied by an app loadAllResources(cl, loader, ADDRESS_MAP_RESOURCES); - // load system-wide jakarta.address.map from the + // load system-wide javamail.address.map and jakarta.address.map from the // /{conf,lib} directory if (confDir != null) - loadFile(loader, confDir + "jakarta.address.map", confDir + "javamail.address.map"); + loadFile(loader, confDir + "javamail.address.map", confDir + "jakarta.address.map"); if (addressMap.isEmpty()) { logger.config("failed to load address map, using defaults"); @@ -1156,24 +1188,20 @@ public synchronized void setProtocolForAddress(String addresstype, String protoc * Load from the named file. */ private void loadFile(StreamLoader loader, String ... names) { - InputStream clis = null; - for (String name : names) { - try { - clis = new BufferedInputStream(new FileInputStream(name)); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + try (InputStream clis = new BufferedInputStream(new FileInputStream(name))) { loader.load(clis); logger.log(Level.CONFIG, "successfully loaded file: {0}", name); - break; + // We already know size is two, but just in case, we don't want to fail here for a logger reason + if (i == 0 && names.length == 2) { + logger.log(Level.WARNING, "[DEPRECATED] {0} is deprecated and will be removed. Future versions will require {1}.", names[0], names[1]); + } } catch (FileNotFoundException fex) { // ignore it } catch (IOException e) { if (logger.isLoggable(Level.CONFIG)) logger.log(Level.CONFIG, "not loading file: " + name, e); - } finally { - try { - if (clis != null) - clis.close(); - } catch (IOException ex) { - } // ignore it } } } @@ -1182,28 +1210,27 @@ private void loadFile(StreamLoader loader, String ... names) { * Load from the named resource. */ private void loadResource(Class cl, StreamLoader loader, boolean expected, String ... names) { - InputStream clis = null; - try { - for (String name : names) { - clis = getResourceAsStream(cl, name); + boolean loaded = false; + for (int i = 0; i < names.length; i++) { + String name = names[i]; + try (InputStream clis = getResourceAsStream(cl, name)) { if (clis != null) { + loaded = true; loader.load(clis); logger.log(Level.CONFIG, "successfully loaded resource: {0}", name); - break; + // We already know size is two, but just in case, we don't want to fail here for a logger reason + if (i == 0 && names.length == 2) { + logger.log(Level.WARNING, "[DEPRECATED] {0} is deprecated and will be removed. Future versions will require {1}.", names[0], names[1]); + } } + } catch (IOException e) { + logger.log(Level.CONFIG, "Exception loading resource", e); } - if (clis == null) { - if (expected) - logger.log(Level.WARNING, "expected resource not found: {0}", Arrays.asList(names)); - } - } catch (IOException e) { - logger.log(Level.CONFIG, "Exception loading resource", e); - } finally { - try { - if (clis != null) - clis.close(); - } catch (IOException ex) { - } // ignore it + + } + if (!loaded) { + if (expected) + logger.log(Level.WARNING, "expected resource not found: {0}", Arrays.asList(names)); } } @@ -1213,7 +1240,8 @@ private void loadResource(Class cl, StreamLoader loader, boolean expected, St private void loadAllResources(Class cl, StreamLoader loader, String ... names) { boolean anyLoaded = false; try { - for (String name : names) { + for (int j = 0; j < names.length; j++) { + String name = names[j]; URL[] urls; ClassLoader cld = null; // First try the "application's" class loader. @@ -1227,10 +1255,8 @@ private void loadAllResources(Class cl, StreamLoader loader, String ... names if (urls != null) { for (int i = 0; i < urls.length; i++) { URL url = urls[i]; - InputStream clis = null; logger.log(Level.CONFIG, "URL {0}", url); - try { - clis = url.openStream(); + try (InputStream clis = url.openStream()) { if (clis != null) { loader.load(clis); anyLoaded = true; @@ -1245,16 +1271,11 @@ private void loadAllResources(Class cl, StreamLoader loader, String ... names } catch (IOException ioex) { logger.log(Level.CONFIG, "Exception loading resource", ioex); - } finally { - try { - if (clis != null) - clis.close(); - } catch (IOException cex) { - } } } - if (anyLoaded) { - break; + // We already know size is two, but just in case, we don't want to fail here for a logger reason + if (j == 0 && names.length == 2) { + logger.log(Level.WARNING, "[DEPRECATED] {0} is deprecated and will be removed. Future versions will require {1}.", names[0], names[1]); } } } diff --git a/api/src/main/java/jakarta/mail/internet/MimeUtility.java b/api/src/main/java/jakarta/mail/internet/MimeUtility.java index f30ad58d..de0ffa89 100644 --- a/api/src/main/java/jakarta/mail/internet/MimeUtility.java +++ b/api/src/main/java/jakarta/mail/internet/MimeUtility.java @@ -40,6 +40,8 @@ import java.util.NoSuchElementException; import java.util.Properties; import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; /** * This is a utility class that provides various MIME related @@ -139,9 +141,13 @@ public class MimeUtility { private MimeUtility() { } + private static final Logger LOGGER = Logger.getLogger(MimeUtility.class.getName()); + public static final int ALL = -1; // This is for backwards compatibility. Eventually we have to remove the second value of the next array. + // Note this order is different than in Session arrays. This is because here the key-value pair is overwritten meanwhile in Session + // it checks whether the value was inserted or not already, to avoid @DefaultProvider overrides it. private static final String[] CHARSET_MAP_RESOURCES = new String[] {"/META-INF/jakarta.charset.map", "/META-INF/javamail.charset.map"}; // cached map of whether a charset is compatible with ASCII @@ -1353,13 +1359,12 @@ static String getDefaultMIMECharset() { java2mime = new HashMap<>(40); mime2java = new HashMap<>(14); - try { - // Use this class's classloader to load the mapping file - // XXX - we should use SecuritySupport, but it's in another package - InputStream is = resource(CHARSET_MAP_RESOURCES); - - if (is != null) { - try { + // Use this class's classloader to load the mapping file + // XXX - we should use SecuritySupport, but it's in another package + for (int i = 0; i < CHARSET_MAP_RESOURCES.length; i++) { + String charsetResource = CHARSET_MAP_RESOURCES[i]; + try (InputStream is = MimeUtility.class.getResourceAsStream(charsetResource)) { + if (is != null) { LineInputStream lineInput = StreamProvider.provider().inputLineStream(is, false); // Load the JDK-to-MIME charset mapping table @@ -1367,15 +1372,14 @@ static String getDefaultMIMECharset() { // Load the MIME-to-JDK charset mapping table loadMappings(lineInput, mime2java); - } finally { - try { - is.close(); - } catch (Exception cex) { - // ignore - } } + // We already know size is two, but just in case, we don't want to fail here for a logger reason + if (i == 0 && CHARSET_MAP_RESOURCES.length == 2) { + LOGGER.log(Level.WARNING, "[DEPRECATED] {0} is deprecated and will be removed. Future versions will require {1}.", + new Object[]{CHARSET_MAP_RESOURCES[0], CHARSET_MAP_RESOURCES[1]}); + } + } catch (Exception ex) { } - } catch (Exception ex) { } // If we didn't load the tables, e.g., because we didn't have @@ -1444,16 +1448,6 @@ static String getDefaultMIMECharset() { mime2java.put("gbk", "GB18030"); } } - - private static InputStream resource(String[] resources) { - for (String resource : resources) { - InputStream is = MimeUtility.class.getResourceAsStream(resource); - if (is != null) { - return is; - } - } - return null; - } private static void loadMappings(LineInputStream is, Map table) { diff --git a/api/src/test/java/jakarta/mail/SessionTest.java b/api/src/test/java/jakarta/mail/SessionTest.java index 72caa987..6f6e4b61 100644 --- a/api/src/test/java/jakarta/mail/SessionTest.java +++ b/api/src/test/java/jakarta/mail/SessionTest.java @@ -16,7 +16,11 @@ package jakarta.mail; +import java.util.Arrays; import java.util.Properties; +import java.util.stream.Stream; + +import jakarta.mail.Provider.Type; import org.junit.Test; @@ -25,12 +29,31 @@ public class SessionTest { @Test - public void issue527() throws NoSuchProviderException { + public void issue527And812() throws NoSuchProviderException { Session session = Session.getInstance(new Properties()); + Stream providerStream = Arrays.stream(session.getProviders()); + // test exists in both jakarta.providers and javamail.providers + long count = providerStream.filter(p -> "test".equals(p.getProtocol())).count(); + assertEquals(2, count); Provider provider = session.getProvider("test"); + // javamail.providers one has precedence in case of conflict + assertEquals(JavaMail.class.getName(), provider.getClassName()); + provider = session.getProvider("test2"); assertEquals(Jakarta.class.getName(), provider.getClassName()); + provider = session.getProvider("test3"); + assertEquals(JavaMail.class.getName(), provider.getClassName()); } + @Test + public void byProperty() throws NoSuchProviderException { + Properties prop = new Properties(); + prop.put("mail.test4.class", Jakarta.class.getName()); + Session session = Session.getInstance(prop); + // protocol test4 exists in both with same class, but javamail.providers has precedence + Provider provider = session.getProvider("test4"); + assertEquals("OracleMail", provider.getVendor()); + } + public static class Jakarta extends Store { protected Jakarta(Session session, URLName urlname) { diff --git a/api/src/test/java/jakarta/mail/internet/MimeUtilityTest.java b/api/src/test/java/jakarta/mail/internet/MimeUtilityTest.java new file mode 100644 index 00000000..2317596d --- /dev/null +++ b/api/src/test/java/jakarta/mail/internet/MimeUtilityTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package jakarta.mail.internet; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MimeUtilityTest { + + @Test + public void issue812() { + // Same value in both files + assertEquals("ISO-8859-1", MimeUtility.mimeCharset("8859_1")); + // Conflict value, javamail.charset.map wins + assertEquals("ISO-8859-1", MimeUtility.mimeCharset("iso8859_1")); + // Exists only in javamail.charset.map + assertEquals("ISO-8859-3", MimeUtility.mimeCharset("8859_3")); + // Exists only in jakarta.charset.map + assertEquals("ISO-8859-4", MimeUtility.mimeCharset("8859_4")); + } + +} diff --git a/api/src/test/resources/META-INF/jakarta.charset.map b/api/src/test/resources/META-INF/jakarta.charset.map new file mode 100644 index 00000000..664c0eea --- /dev/null +++ b/api/src/test/resources/META-INF/jakarta.charset.map @@ -0,0 +1,19 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +8859_1 ISO-8859-1 +iso8859_1 ISO-8859-2 +8859_4 ISO-8859-4 \ No newline at end of file diff --git a/api/src/test/resources/META-INF/jakarta.providers b/api/src/test/resources/META-INF/jakarta.providers index b631ea61..ee1e2b44 100644 --- a/api/src/test/resources/META-INF/jakarta.providers +++ b/api/src/test/resources/META-INF/jakarta.providers @@ -14,4 +14,6 @@ # limitations under the License. # -protocol=test; type=store; class=jakarta.mail.SessionTest$Jakarta; vendor=Oracle; \ No newline at end of file +protocol=test; type=store; class=jakarta.mail.SessionTest$Jakarta; vendor=Oracle; +protocol=test2; type=store; class=jakarta.mail.SessionTest$Jakarta; vendor=Oracle; +protocol=test4; type=store; class=jakarta.mail.SessionTest$Jakarta; vendor=OracleJakarta; \ No newline at end of file diff --git a/api/src/test/resources/META-INF/javamail.charset.map b/api/src/test/resources/META-INF/javamail.charset.map new file mode 100644 index 00000000..75e7487d --- /dev/null +++ b/api/src/test/resources/META-INF/javamail.charset.map @@ -0,0 +1,19 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +8859_1 ISO-8859-1 +iso8859_1 ISO-8859-1 +8859_3 ISO-8859-3 \ No newline at end of file diff --git a/api/src/test/resources/META-INF/javamail.providers b/api/src/test/resources/META-INF/javamail.providers index bd14849b..0d782852 100644 --- a/api/src/test/resources/META-INF/javamail.providers +++ b/api/src/test/resources/META-INF/javamail.providers @@ -14,4 +14,6 @@ # limitations under the License. # -protocol=test; type=store; class=jakarta.mail.SessionTest$JavaMail; vendor=Oracle; \ No newline at end of file +protocol=test; type=store; class=jakarta.mail.SessionTest$JavaMail; vendor=Oracle; +protocol=test3; type=store; class=jakarta.mail.SessionTest$JavaMail; vendor=Oracle; +protocol=test4; type=store; class=jakarta.mail.SessionTest$Jakarta; vendor=OracleMail; \ No newline at end of file diff --git a/doc/release/CHANGES.txt b/doc/release/CHANGES.txt index ea2b4439..61ae6dfd 100644 --- a/doc/release/CHANGES.txt +++ b/doc/release/CHANGES.txt @@ -27,6 +27,7 @@ E 744 Remove SecurityManager reference from API E 758 Improve MimeMessage UTF8 handling E 804 Restore streamProvider fields for backwards compatibility E 810 InternetHeaders.InternetHeader toString() +E 812 Custom mail providers distributed under META-INF/javamail.properties are no longer loaded with Jakarta Mail 2.1.5 CHANGES IN THE 2.1.5 RELEASE ---------------------------- diff --git a/www/docs/COMPAT.txt b/www/docs/COMPAT.txt index 596701c2..770b01a6 100644 --- a/www/docs/COMPAT.txt +++ b/www/docs/COMPAT.txt @@ -3,7 +3,7 @@ -- Jakarta Mail 2.2.0 -- - Removal of finalize() +- Removal of finalize() The finalize() methods have been removed throughout the Mail API. @@ -18,6 +18,20 @@ 3. Resource / memory management Because finalizers are removed, users must more explicitly manage resources: ensure streams, folders, other resources are closed properly. Do not rely on garbage collection / finalization to clean up. +- Resource Loading Order Update + + In 2.1.4 we introduced a change to prioritize Jakarta-prefixed resources over the legacy ones. However, this unintentionally caused some legacy resources to be skipped entirely. + + As of 2.2.0, the loading logic has been corrected: + + Both legacy and Jakarta-prefixed resources are now loaded. + + Legacy resources take precedence to preserve compatibility. + + Jakarta-prefixed resources remain supported and will take over fully once migration is complete. + + This ensures backward compatibility while still allowing a smooth transition to the Jakarta-based resources. + -- Jakarta Mail 2.1.4 -- - Resource Loading Order