Migrations - phax/phase4 GitHub Wiki

This page contains everything necessary related to migrating phase4 versions

Upgrade from 4.x to 5.x (in development)

  • The usage of the configuration file phase4.properties and private-phase4.properties was removed. Use application.properties or private-application.properties, environment variables or system properties instead.

Upgrade from 4.7.x to 4.8.x (current)

v4.8.0 changed the way the details of the TLS connection of a single AS4 message are surfaced, and it updated to WSS4J 4.0.2. The first two items below require source changes, and the WSS4J update affects interoperability with partners using AES-256, so v4.8.0 is not a drop-in replacement for v4.7.x.

  • The TLS related Consumer parameters now take an AS4ConnectionDetails instead of an ICommonsList<X509Certificate>. This affects the last parameter of sendGenericMessage(...) and sendGenericMessageWithRetries(...) in BasicHttpPoster and IHttpPoster, as well as the second parameter of the AS4ClientSentMessage constructors. The new class carries the HTTP version and the IP addresses of both ends plus a nested AS4TlsConnectionDetails with the certificates, the negotiated TLS protocol version, the cipher suite, the key size, the TLS session ID, the SNI and the principals. AS4ClientSentMessage.getRemoteTlsPeerCerts() is unchanged and now returns getConnectionDetails().getTlsDetails().peerCerts()
  • Custom implementations of IAS4IncomingMessageMetadata need to implement the new methods getTlsConnectionDetails(), getLocalAddr(), getLocalPort(), getServerName(), getHttpVersion() and getSecure(). AS4IncomingMessageMetadata has the matching setters; getSecure() returns an ETriState and must never return null
  • Incoming messages encrypted with AES-256 are rejected if the session key is only 128 bits long. WSS4J 4.0.2 rejects a symmetric key whose length does not exactly match the declared encryption algorithm - previously a 128-bit key was silently accepted for aes256-gcm and effectively used as AES-128. Up to and including v4.7.x, phase4 itself sent exactly such messages: the session key was 128 bits long by default, even if aes256-cbc or aes256-gcm was configured, unless a session key provider was set explicitly (see #401). Of the shipped profiles only DBNAlliance uses AES-256 (aes256-gcm), but Phase4DBNAllianceSender has always explicitly used a 256-bit session key (ICryptoSessionKeyProvider.INSTANCE_RANDOM_AES_256), so DBNAlliance messages sent with it by older phase4 versions are not affected. Affected are only messages sent by phase4 v4.7.x or older with an AES-256 algorithm but without an explicit session key provider - e.g. a custom PMode or a sender builder other than Phase4DBNAllianceSender - they fail with EBMS:0102 (Failed Decryption) at a receiver on v4.8.0. Such senders must either set a 256-bit session key provider explicitly or update to phase4 v4.8.0 or later. The opposite direction is not affected: the correct 256-bit session keys sent by v4.8.0 are accepted by older versions as well. All other shipped profiles use aes128-gcm and are not affected
  • WSS4J 4.0.2 brings the new transitive dependency org.apache.commons:commons-secure-xml. Nothing needs to be done, unless you manage dependencies explicitly (e.g. an allowlist, a shaded JAR or an OSGi setup)

Upgrade from 4.6.x to 4.7.x

All items of this section are consequences of the update to peppol-commons 13.0.0, which moved the network neutral types out of the Peppol specific packages and modules, so that Peppol, DBNAlliance, HR eDelivery and future networks can share them. Except for the Country C1 check mentioned last, none of them changes the AS4 behaviour of phase4 - but they do require source changes, so v4.7.0 is not a drop-in replacement for v4.6.x.

  • ISMPTransportProfile moved from com.helger.peppol.smp to com.helger.edelivery.smp. Change the import of that type. It is used as the type of the constant DEFAULT_TRANSPORT_PROFILE and in the methods getTransportProfile() and setTransportProfile(ISMPTransportProfile) of AS4EndpointDetailProviderBDXR, AS4EndpointDetailProviderBDXR2, AS4EndpointDetailProviderPeppol and Phase4PeppolServletMessageProcessorSPI. The Peppol enum ESMPTransportProfile itself stays in com.helger.peppol.smp
  • The transport profiles of the non-Peppol networks are no longer constants of the Peppol enum ESMPTransportProfile. Replace ESMPTransportProfile.TRANSPORT_PROFILE_DBNA_AS4_V1 with EDBNAllianceTransportProfile.AS4_V1 (package com.helger.dbnalliance.commons.smp) and ESMPTransportProfile.TRANSPORT_PROFILE_ERACUN_AS4_V1 with EHREDeliveryTransportProfile.AS4_V1 (package com.helger.hredelivery.commons.smp). The type of Phase4DBNAllianceServletMessageProcessorSPI.DEFAULT_TRANSPORT_PROFILE and of Phase4HREDeliveryServletMessageProcessorSPI.DEFAULT_TRANSPORT_PROFILE changed accordingly. The transport profile IDs are unchanged, so nothing changes on the wire or in an SMP
  • The DNS based SMP URL providers moved from com.helger.smpclient.url to com.helger.smpclient.url.dns. This affects BDXLURLProvider, IBDXLURLProvider, PeppolNaptrURLProvider and DBNAURLProviderSMP, and therefore the imports needed around Phase4PeppolSender.URL_PROVIDER, Phase4CEFSender.URL_PROVIDER, Phase4EDelivery2Sender.URL_PROVIDER and Phase4EudamedSender.URL_PROVIDER. ISMPURLProvider and SMPDNSResolutionException stay in com.helger.smpclient.url
  • IPeppolURLProvider was deprecated in favour of IBDXLURLProvider, so Phase4PeppolSender.URL_PROVIDER is now declared as IBDXLURLProvider. If you assigned that constant to a variable, a field or a parameter of type IPeppolURLProvider, change that type to IBDXLURLProvider
  • Implementations of IPhase4DBNAllianceIncomingXHEHandlerSPI need to change one import: DBNAllianceXHEData moved from com.helger.peppol.xhe to com.helger.dbnalliance.xhe. The class name, its API and the rest of the method signature are unchanged. The same move applies to DBNAlliancePayload, DBNAllianceXHEDataReader, DBNAllianceXHEDataWriter and DBNAllianceXHEDataReadException
  • If you reference MozillaNSSTrustStore directly - e.g. next to Phase4PeppolHttpClientSettings.setSSLContextPeppolMozillaNSS() - change its import from com.helger.peppol.security to com.helger.nss. The class moved into the new standalone project ph-nss, because the Mozilla NSS root certificates are not Peppol specific and are updated every couple of weeks. phase4-profile-peppol declares the dependency com.helger:ph-nss itself, so nothing needs to be added to your POM, and the API of the class is unchanged
  • The Country C1 element of an incoming Peppol SBDH is now always checked, as soon as the SBDH value checks are enabled. PeppolSBDHDataReader.setCheckForCountryC1(boolean) was removed in peppol-commons 13.0.0, so the deprecated methods Phase4PeppolDefaultReceiverConfiguration.setCheckSBDHForMandatoryCountryC1(boolean) and Phase4PeppolReceiverConfigurationBuilder.checkSBDHForMandatoryCountryC1(boolean) still compile, but no longer have any effect. A receiver that relied on disabling that check now rejects incoming messages without a Country C1 element. The only remaining way to accept them is to disable performSBDHValueChecks altogether, which also disables all other SBDH value checks
  • Applications that use further parts of peppol-commons directly are affected by more moves and removals than the ones listed here - see the peppol-commons v13.0.0 release notes for the complete list

Upgrade from 4.5.x to 4.6.x

  • If you have a custom subclass of AS4DefaultPModeResolver that overrides the protected method createDefaultPMode(String,String,String), move your code to the new 5 argument overload createDefaultPMode(String,String,String,String,String) (since v4.6.1). The 3 argument version still exists and still compiles, but it is deprecated and is no longer called by findPMode, so an override of it would be silently ignored. Implementations of IAS4Profile are unaffected - the new createPModeTemplate overload has a default implementation. See #213
  • The method IAS4IncomingAttachmentFactory.createAttachment now takes an AS4IncomingMimePart instead of a jakarta.mail.internet.MimeBodyPart parameter - custom implementations of this interface must be adapted. AS4IncomingMimePart only parses the header section of a MIME part eagerly, whereas the content stays on the stream and can be consumed exactly once. That is what avoids loading a whole attachment into memory. For the same reason, the method WSS4JAttachment.createIncomingFileAttachment(MimeBodyPart, AS4ResourceHelper) was deprecated in favour of the new overload taking an AS4IncomingMimePart. See #382
  • The signature of an incoming message must now cover the ebMS Messaging header element and all attachments. For messages without attachments, the SOAP Body element must be covered as well, as required by the AS4 profile chapter 5.1.4. For messages with attachments it is not required, because chapter 5.1.5 only demands the eb:Messaging header element and all MIME body parts (since v4.6.1). Since v4.6.1 the required parts are AS4 profile specific: custom profiles can override IAS4ProfileValidator.getRequiredSignedParts(boolean) to demand more or less than the generic AS4 rules. Up to v4.5.x it was sufficient that any element was correctly signed for a message to count as "signed" - it was never checked what the signature really covers, which is the base of XML signature wrapping attacks. Messages with an incomplete signature coverage are now rejected with EBMS:0103 (Policy Noncompliance). If a partner does not sign all of these parts, the previous behaviour - only logging a warning - can be restored by setting the configuration property phase4.incoming.signature.requirefullcoverage to false. See #318 and #392
  • Incoming messages are now limited in size. All limits can be disabled individually by using a value < 0. See #318
    • phase4.incoming.maxmessagesize.bytes (default 1 GB), phase4.incoming.maxattachmentcount (default 100) and phase4.incoming.attachment.maxsize.bytes (default 1 GB) are answered with HTTP status code 400 (Bad Request, not retryable)
    • phase4.incoming.attachment.maxdecompressedsize.bytes (default 1 GB) and phase4.incoming.attachment.maxcompressionratio (default 1000) lead to EBMS:0303 (Decompression Failure) in the message processors of the Peppol, DBNAlliance and HR eDelivery profiles
    • Note the interaction with #382: the previous implicit limit of ~2 GB per MIME part was removed in the same version, so a message larger than 2 GB became receivable and is now limited by configuration instead. Deployments that exchange very large files need to raise or disable these values explicitly
    • Custom implementations of IAS4IncomingMessageProcessorSPI should translate an IOException of the new type AS4SizeLimitException, thrown while reading an attachment, into EBMS:0303 - like the shipped profile implementations do
  • If the configuration property phase4.http.response.accept.allstatuscodes is disabled, a received SOAP Fault with a PERMANENT disposition now stops all remaining HTTP retries immediately. Previously all configured retries were exhausted. See #384
  • Malformed MIME parts of an incoming message - a header section exceeding the configured maximum size or an unknown Content-Transfer-Encoding - are now answered with HTTP status code 400 (Bad Request, not retryable) instead of HTTP status code 500. Client side retry logic or monitoring that keys on 5xx status codes is affected. See #382
  • Non-parseable bodies of non-success HTTP responses (status code ≥ 300) are now logged on WARN level instead of on ERROR level, because they typically represent infrastructure error pages (e.g. HTML from a load balancer or reverse proxy) rather than AS4 protocol violations. Log based alerting that triggers on ERROR will no longer fire for these. See #378
  • BasicHttpPoster can optionally reuse HTTP connections across requests via a shared HttpClientManager (see setSharedHttpClientManager). This is opt-in, but if it is used, the caller owns the manager and must close it, and the configured HttpClientFactory is ignored. See #379

Upgrade from 4.4.x to 4.5.x

  • Peppol SMP certificate revocation checks are enabled by default
  • Peppol SMP TLS certificate revocation checks are enabled by default
  • Peppol AP TLS certificate revocation checks are enabled by default
  • Peppol TLS connections now rely on the default Java runtime truststore instead of trusting all server certificates. Three explicit truststore options are available on Phase4PeppolHttpClientSettings:
    • Leave the default - JVM cacerts is used and the configured revocation check mode is applied.
    • Call setSSLContextTrustAll() (inherited from HttpClientSettings) to restore the pre-4.5.0 "trust all server certificates" behaviour. Test environments only.
    • Call setSSLContextPeppolMozillaNSS() (since 4.5.1) to use the Mozilla NSS root certificate trust store shipped with peppol-commons - the strictest production option.
  • TLS handshake / PKIX failures during message sending are now wrapped with a hint pointing at this page and at the helpers above (since 4.5.1).
  • An unfortunate regression requires the http.proxy.enabled=true configuration property to be enabled. This was relevant only in v4.5.0 and fixed in v4.5.1.

Upgrade from 4.3.x to 4.4.x

  • OSGi bundle support has been removed. All submodule artifacts are now packaged as plain JAR files instead of OSGi bundles. If you relied on OSGi metadata (Import-Package, Export-Package, Require-Capability, etc.), you will need to handle this in your OSGi container configuration. The Automatic-Module-Name manifest entry is still present for Java Platform Module System (JPMS) compatibility.

Upgrade from 4.2.x to 4.3.x

  • The SPI interface method IPhase4PeppolIncomingSBDHandlerSPI.processAS4ResponseMessage is no longer a default method

Upgrade from 4.1.x to 4.2.x

  • The SPI interface for receiving AS4 messages was slightly changed on error handling:
    • Instead of ICommonsList <Ebms3Error> the error list is now of type AS4ErrorList
    • Each individual error is now of type AS4Error instead of Ebms3Error
    • Both API changes should be transparent, so no additional coding is expected besides the type change

Upgrade from 4.0.x to 4.1.x

  • The @NonNull and @Nullable annotations are now taken from JSpecify and no longer from Jakarta-Annotations

Upgrade from 3.x to 4.0.x

  • It requires at least Java 17 for building and execution
  • It was updated to the JakartaEE 10 specifications
    • Uses Servlet Specification 6.0.x
  • The minimum requirements for application servers are
    • Tomcat 10.1.x
    • Jetty 12.x

Upgrade from 3.1.x to 3.2.x

  • Make sure that all dependent libraries are updated to the required versions (see the release for details)
  • All interfaces that declared their methods are throwing Phase4PeppolException are now throwing Phase4Exception instead
  • [Peppol] The support for Peppol Policy for use of Identifiers v4.2.0 was dropped

Upgrade from 3.0.x to 3.1.x

  • Make sure that all dependent libraries are updated to the required versions (see the release for details)
  • Class PeppolCAChecker was replaced with class TrustedCAChecker
  • The predefined trusted CAs of Peppol were moved from PeppolCertificateChecker to PeppolTrustedCA
  • Enum EPeppolCertificateCheckResult was replaced with ECertificateCheckResult
  • Some methods were moved from class PeppolCertificateHelper to class CertificateHelper
  • [DBNAlliance] Moved class Phase4DBNAllianceHttpClientSettings to module phase4-profile-dbnalliance
  • The method AbstractAS4MessageBuilder.finishFields (and in all derived classes) now have an AS4ResourceHelper parameter

Upgrade from 3.0.x to 3.0.4 or later

Upgrade from 2.x to 3.0.x

  • The phase4-profile-bpc submodule was removed in favour of phase4-profile-dbnalliance

  • The phase4-spring-boot-demo submodule was removed in favour of https://github.com/phax/phase4-peppol-standalone

  • All deprecated methods marked for removal have been removed

  • Configuration related changes

    • The configuration files private-crypto.properties and crypto.properties are deprecated. Please move the properties to application.properties, environment variables or Java system properties instead.
    • The configuration property phase4.profile was renamed to phase4.default.profile - the old property is still evaluated as a secondary option
    • In case you are using AS4CryptoFactoryProperties and
      1. read the configuration from a file: use AS4CryptoFactoryConfiguration instead (as a drop-in replacement)
      2. provide the properties manually: use AS4CryptoFactoryInMemoryKeyStore instead (and see below)
  • Here are the main coding related changes

    • Renamed packages
      • Renamed package com.helger.phase4.error to com.helger.phase4.model.error
      • Renamed package com.helger.phase4.http to com.helger.phase4.messaging.http
      • Renamed package com.helger.phase4.messaging.domain to com.helger.phase4.model.message
    • Moved classes
      • Moved classes from com.helger.phase4.servlet to com.helger.phase4.incoming
      • Moved classes from com.helger.phase4.servlet.mgr to com.helger.phase4.incoming.mgr
      • Moved classes from com.helger.phase4.servlet.soap to com.helger.phase4.incoming.soap
      • Moved classes from com.helger.phase4.servlet.spi to com.helger.phase4.incoming.spi
      • Moved class AbstractAS4RawResponseConsumer to com.helger.phase4.sender
      • Moved class AS4IncomingDumperFileBased.IFileProvider to IAS4IncomingDumperFileProvider
      • Moved class AS4OutgoingDumperFileBased.IFileProvider to IAS4OutgoingDumperFileProvider
      • Moved class AS4RequestHandler.ISoapProcessingFinalizedCallback to IAS4SoapProcessingFinalizedCallback
      • Moved class ESoapVersion to com.helger.phase4.model`
      • Moved classes IAS4IncomingSecurityConfiguration and AS4IncomingSecurityConfiguration to com.helger.phase4.incoming.crypto
      • Moved class IAS4IncomingMessageMetadata to com.helger.phase4.incoming
      • Moved class IAS4RawResponseConsumer to com.helger.phase4.sender
      • Moved class IAS4SignalMessageConsumer to com.helger.phase4.incoming
      • Moved class IAS4UserMessageConsumer to com.helger.phase4.incoming
    • Renamed classes
      • Renamed class AbstractAS4UserMessageBuilder.ESimpleUserMessageSendResult to EAS4UserMessageSendResult
      • Renamed class AS4DuplicateManager to AS4DuplicateManagerXML
      • Renamed class AS4MessageState to AS4IncomingMessageState
      • Renamed class AS4MessagingHelper to AS4IncomingHelper
        • And moved to com.helger.phase4.incoming
      • Renamed class AS4ServletMessageProcessorManager to AS4IncomingMessageProcessorManager
      • Renamed class AS4ServletPullRequestProcessorManager to AS4IncomingPullRequestProcessorManager
      • Renamed class AS4SingleSOAPHeader to AS4SingleSoapHeader (casing only)
      • Renamed class AS4XServletHandler.IHandlerCustomizer to IAS4ServletRequestHandlerCustomizer
        • Made method customizeAfterHandling non-default
      • Renamed class DefaultPModeResolver to AS4DefaultPModeResolver
      • Renamed SPI class IAS4ServletMessageProcessorSPI to IAS4IncomingMessageProcessorSPI
        • Was also moved to a different package - see above
        • Made method IAS4IncomingMessageProcessorSPI non-default
      • Renamed SPI class IAS4ServletPullRequestProcessorSPI to IAS4IncomingPullRequestProcessorSPI
        • Was also moved to a different package - see above
      • Renamed class IAS4MessageState to IAS4IncomingMessageState
      • Renamed class IPModeResolver to IAS4PModeResolver
      • Renamed class ISOAPHeaderElementProcessor to ISoapHeaderElementProcessor (casing only)
      • Renamed class MimeMessageCreator to AS4MimeMessageHelper
      • Renamed class MPCManager to MPCManagerXML
      • Renamed class Phase4PeppolServletConfiguration to Phase4PeppolDefaultReceiverConfiguration
      • Renamed class Phase4PeppolReceiverCheckData to Phase4PeppolReceiverConfiguration
      • Renamed class Phase4PeppolSender.Builder to Phase4PeppolSender.PeppolUserMessageBuilder
      • Renamed class Phase4PeppolSender.SBDHBuilder to Phase4PeppolSender.PeppolUserMessageSBDHBuilder
      • Renamed class PModeManager to PModeManagerXML
      • Renamed class SoapMimeMultipart to AS4SoapMimeMultipart
      • Renamed class SOAPHeaderElementProcessorExtractEbms3Messaging to SoapHeaderElementProcessorExtractEbms3Messaging (casing only)
      • Renamed class SOAPHeaderElementProcessorRegistry to SoapHeaderElementProcessorRegistry (casing only)
      • Renamed class SOAPHeaderElementProcessorWSS4J to SoapHeaderElementProcessorWSS4J (casing only)
    • Removed classes
      • Removed class Phase4PeppolClientException
    • Altered classes
      • Added template parameter to class AbstractAS4IncomingDumperWithHeaders
      • Added template parameter to class AbstractAS4OutgoingDumperWithHeaders
      • Removed constant DefaultPModeResolver.DEFAULT_PMODE_RESOLVER in favour of new instances with an AS4 profile ID
    • Moved methods
      • Moved methods AS4ReceiptMessage.getAllDSigReferenceNodes and AS4ReceiptMessage.getAllDSigReferences to class MessageHelperMethods
      • Moved all setters from AS4XServletHandler to AS4RequestHandler
      • Moved method IAS4ResponseAbstraction.wrap to AS4XServletHandler.createResponseAbstraction
      • Moved methods MessageHelperMethods.forEachHeaderAndRemoveAfterwards and MessageHelperMethods.getAndRemoveAllHeaders to class AS4MimeMessageHelper
    • Renamed methods
      • Renamed methods AbstractAS4Client.(get|set)AS4CryptoFactory* to (get|set)CryptoFactory...
      • Renamed methods AS4ClientBuiltMessage.getCustomHeaders to getAllCustomHttpHeaders
      • Renamed method AS4ClientErrorMessage.isReceiptShouldBeSigned to isErrorShouldBeSigned
      • Renamed method AS4ClientSentMessage.getResponse to getResponseContent
      • Renamed method AS4ClientSentMessage.hasResponse to hasResponseContent
      • Renamed method AS4Configuration.getAS4ProfileIDto getDefaultAS4ProfileID
      • Renamed method AS4ProfileSelector.getAS4ProfileID to getDefaultAS4ProfileID
      • Renamed methods AS4ProfileSelector.(get|set)CustomAS4ProfileID to (get|set)CustomDefaultAS4ProfileID
      • Renamed methods AS4XServletHandler.(get|set)HandlerCustomizer to (get|set)RequestHandlerCustomizer
      • Renamed method IAS4IncomingDumperFileProvider.getFilename to getDefaultDirectoryAndFilename
      • Renamed method IAS4OutgoingDumperFileProvider.getFilename to getDefaultDirectoryAndFilename
      • Renamed method IPModeResolver.getPModeOfID to findPMode
    • Altered methods
      • Extended method AS4DumpReader.decryptAS4In to include an AS4 profile ID parameter
      • Made method IAS4CryptoFactory.getKeyPasswordPerAlias(String) non-default
      • Made method IAS4IncomingDumper.onEndRequest non-default and added optional "caughtException" parameter
      • Made method IAS4OutgoingDumper.onEndRequest non-default and added optional "caughtException" parameter
      • Extended method IAS4ProfileValidator.validatePMode with an additional parameter to differentiate UserMessage and SignalMessage
    • Removed methods
      • Removed method IAS4ProfileManager.hasDefaultProfile
      • Removed method IAS4ProfileManager.getDefaultProfileOrNull
      • Removed method IAS4ProfileManager.getDefaultProfile
      • Removed method IAS4ProfileManager.setDefaultProfileID
      • Removed method IAS4ProfileRegistrar.setDefaultProfile
  • The most common form of CryptoFactory creation got reworked.

Instead of

final AS4CryptoProperties aCP = new AS4CryptoProperties ().setKeyStoreType (EKeyStoreType.PKCS12)
                                                          .setKeyStorePath ("path.p12")
                                                          .setKeyStorePassword ("pw")
                                                          .setKeyAlias ("cert")
                                                          .setKeyPassword ("pw")
                                                          .setTrustStoreType (EKeyStoreType.JKS)
                                                          .setTrustStorePath ("truststore/complete-truststore.jks")
                                                          .setTrustStorePassword ("peppol");
new AS4CryptoFactoryProperties (aCP);

use this code now:

new AS4CryptoFactoryInMemoryKeyStore (KeyStoreAndKeyDescriptor.builder ()
                                                              .type (EKeyStoreType.PKCS12)
                                                              .path ("path.p12")
                                                              .password ("pw")
                                                              .keyAlias ("cert")
                                                              .keyPassword ("pw")
                                                              .build (),
                                      TrustStoreDescriptor.builder ()
                                                          .type (EKeyStoreType.JKS)
                                                          .path ("truststore/complete-truststore.jks")
                                                          .password ("peppol")
                                                          .build ());

In case of problems of PMode resolution, most likely you are missing a default AS4 profiles. Use the following code snippet in your global initialization to pick the correct AS4 profile.

// Example on how to globally use the AS4 Peppol profile
AS4ProfileSelector.setCustomDefaultAS4ProfileID (AS4PeppolProfileRegistarSPI.AS4_PROFILE_ID);

Upgrade from 1.x to 2.x

The major change in v2.x is the new software baseline.

  • It requires at least Java 11 for building and execution
  • It was updated to the JakartaEE 9 specifications
    • Uses Servlet Specification 5.0.x
    • All the namespaces javax.servlet where changed to jakarta.servlet
    • Uses Eclipse Angus instead of Jakarta Mail
  • The minimum requirements for application servers are
    • Tomcat 10.0.x or Tomcat 10.1.x
    • Jetty 11.x

All other potentially breaking changes are listed in News and noteworthy.

⚠️ **GitHub.com Fallback** ⚠️