From 3608e9e2209a467d9384dd152844c1b55f83373c Mon Sep 17 00:00:00 2001
From: maximthomas <maxim.thomas@gmail.com>
Date: Fri, 18 Jul 2025 08:34:36 +0000
Subject: [PATCH] Merge branch 'master' into update-jdk-11

---
 opendj-packages/opendj-docker/pom.xml                                                                                                          |   10 
 opendj-embedded/pom.xml                                                                                                                        |    6 
 opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java                                                                            |   70 
 opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java                                                       |    5 
 opendj-packages/opendj-svr4/pom.xml                                                                                                            |    2 
 opendj-server-legacy/resource/schema/02-config.ldif                                                                                            |   11 
 opendj-rest2ldap/pom.xml                                                                                                                       |    2 
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java                                |   25 
 opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml                                                                                         |    2 
 opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java                                                              |   14 
 opendj-ldap-sdk-examples/pom.xml                                                                                                               |    2 
 opendj-server-legacy/pom.xml                                                                                                                   |    4 
 opendj-ldap-toolkit/pom.xml                                                                                                                    |    2 
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml |   55 +
 opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java                                             |   96 ++
 opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java                                              |  182 +++++
 opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java                                                    |    8 
 opendj-cli/pom.xml                                                                                                                             |    2 
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java                             |   24 
 opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template                                                                      |    2 
 opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java                                         |   66 +
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java                             |   24 
 opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java                                       |   82 ++
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java                           |   24 
 opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java                                                                |   10 
 opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java                                                   |   29 
 opendj-doc-maven-plugin/pom.xml                                                                                                                |   14 
 opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java                                                    |   26 
 opendj-dsml-servlet/pom.xml                                                                                                                    |   11 
 opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template                                                            |    4 
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml   |   75 ++
 opendj-embedded-server-examples/pom.xml                                                                                                        |    2 
 opendj-rest2ldap-servlet/pom.xml                                                                                                               |    2 
 opendj-legacy/pom.xml                                                                                                                          |    2 
 opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java                                                           |    8 
 opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java                                                                 |   52 +
 opendj-packages/opendj-msi/opendj-msi-standard/pom.xml                                                                                         |    2 
 opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java                                            |  101 ++
 opendj-doc-generated-ref/pom.xml                                                                                                               |    6 
 opendj-packages/opendj-msi/pom.xml                                                                                                             |    2 
 opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java                                            |  153 ++++
 pom.xml                                                                                                                                        |  142 +--
 opendj-server-legacy/resource/config/config.ldif                                                                                               |   17 
 opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc                                                                   |    9 
 opendj-grizzly/pom.xml                                                                                                                         |   11 
 opendj-maven-plugin/pom.xml                                                                                                                    |   12 
 opendj-server-example-plugin/pom.xml                                                                                                           |    7 
 opendj-config/pom.xml                                                                                                                          |    2 
 opendj-packages/pom.xml                                                                                                                        |    2 
 opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java                                                        |  260 +++++++
 opendj-server-msad-plugin/pom.xml                                                                                                              |    2 
 opendj-openidm-account-change-notification-handler/pom.xml                                                                                     |    2 
 opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java                                               |  168 ++++
 opendj-server/pom.xml                                                                                                                          |    6 
 opendj-packages/opendj-deb/opendj-deb-standard/pom.xml                                                                                         |    2 
 opendj-packages/opendj-deb/pom.xml                                                                                                             |    2 
 opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java                                           |   94 ++
 opendj-core/pom.xml                                                                                                                            |   20 
 opendj-packages/opendj-rpm/pom.xml                                                                                                             |    2 
 opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java                                           |  131 +++
 60 files changed, 1,866 insertions(+), 244 deletions(-)

diff --git a/opendj-cli/pom.xml b/opendj-cli/pom.xml
index f22b1df..1a99887 100644
--- a/opendj-cli/pom.xml
+++ b/opendj-cli/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-cli</artifactId>
diff --git a/opendj-config/pom.xml b/opendj-config/pom.xml
index 7c19aec..387b639 100644
--- a/opendj-config/pom.xml
+++ b/opendj-config/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-config</artifactId>
   <name>OpenDJ Configuration API</name>
diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml
index bff3bcf..1a80a9d 100644
--- a/opendj-core/pom.xml
+++ b/opendj-core/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-core</artifactId>
@@ -57,9 +57,9 @@
         </dependency>
 
         <dependency>
-            <groupId>io.reactivex.rxjava2</groupId>
+            <groupId>io.reactivex.rxjava3</groupId>
             <artifactId>rxjava</artifactId>
-            <version>2.2.21</version>
+            <version>3.1.10</version>
         </dependency>
 
         <dependency>
@@ -99,13 +99,13 @@
 	      <artifactId>bc-fips</artifactId>
 	      <version>${bc.fips.version}</version>
 	    </dependency>
-	
+
 	    <dependency>
 	      <groupId>org.bouncycastle</groupId>
 	      <artifactId>bctls-fips</artifactId>
 	      <version>${bctls.fips.version}</version>
 	    </dependency>
-	    
+
 	     <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
@@ -114,8 +114,8 @@
 
 
     <properties>
-        <bc.fips.version>1.0.2.5</bc.fips.version>
-        <bctls.fips.version>1.0.19</bctls.fips.version>
+        <bc.fips.version>2.1.0</bc.fips.version>
+        <bctls.fips.version>2.1.20</bctls.fips.version>
 
         <opendj.osgi.import.additional>
             com.sun.security.auth*;resolution:=optional
@@ -182,6 +182,7 @@
                             org.forgerock.opendj.ldif,
                             com.forgerock.reactive
                         </Export-Package>
+                        <Import-Package>!org.bouncycastle.jcajce.provider</Import-Package>
                         <Build-Maven>Apache Maven ${maven.version}</Build-Maven>
                         <SCM-Revision>${buildRevision}</SCM-Revision>
                         <SCM-Branch>${scmBranch}</SCM-Branch>
@@ -198,11 +199,6 @@
 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-source-plugin</artifactId>
-            </plugin>
-
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java
new file mode 100644
index 0000000..35c4981
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java
@@ -0,0 +1,94 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+
+/*
+    The Aborted Transaction Notice is an Unsolicited Notification message
+   where the responseName is 1.3.6.1.1.21.4 and responseValue is present
+   and contains a transaction identifier.
+ */
+public class AbortedTransactionExtendedResult extends AbstractExtendedResult<AbortedTransactionExtendedResult> {
+    @Override
+    public String getOID() {
+        return "1.3.6.1.1.21.4";
+    }
+
+    private AbortedTransactionExtendedResult(final ResultCode resultCode) {
+        super(resultCode);
+    }
+
+    public static AbortedTransactionExtendedResult newResult(final ResultCode resultCode) {
+        Reject.ifNull(resultCode);
+        return new AbortedTransactionExtendedResult(resultCode);
+    }
+
+    private String transactionID = null;
+
+    public AbortedTransactionExtendedResult setTransactionID(final String transactionID) {
+        this.transactionID = transactionID;
+        return this;
+    }
+
+    public String getTransactionID() {
+        return transactionID;
+    }
+
+    @Override
+    public ByteString getValue() {
+        final ByteStringBuilder buffer = new ByteStringBuilder();
+        final ASN1Writer writer = ASN1.getWriter(buffer);
+        try {
+            writer.writeOctetString(transactionID);
+        } catch (final IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return buffer.toByteString();
+    }
+
+    @Override
+    public boolean hasValue() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "AbortedTransactionExtendedResult(resultCode=" +
+                getResultCode() +
+                ", matchedDN=" +
+                getMatchedDN() +
+                ", diagnosticMessage=" +
+                getDiagnosticMessage() +
+                ", referrals=" +
+                getReferralURIs() +
+                ", responseName=" +
+                getOID() +
+                ", transactionID=" +
+                transactionID +
+                ", controls=" +
+                getControls() +
+                ")";
+    }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java
new file mode 100644
index 0000000..5b898a1
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java
@@ -0,0 +1,182 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.requests.*;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
+
+/*
+ An End Transaction Request is an LDAPMessage of CHOICE extendedReq
+   where the requestName is 1.3.6.1.1.21.3 and the requestValue is
+   present and contains a BER-encoded txnEndReq.
+
+      txnEndReq ::= SEQUENCE {
+           commit         BOOLEAN DEFAULT TRUE,
+           identifier     OCTET STRING }
+
+   A commit value of TRUE indicates a request to commit the transaction
+   identified by the identifier.  A commit value of FALSE indicates a
+   request to abort the identified transaction.
+ */
+public class EndTransactionExtendedRequest extends AbstractExtendedRequest<EndTransactionExtendedRequest, EndTransactionExtendedResult> {
+
+    public static final String END_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.3";
+
+    @Override
+    public String getOID() {
+        return END_TRANSACTION_REQUEST_OID;
+    }
+
+    @Override
+    public ExtendedResultDecoder<EndTransactionExtendedResult> getResultDecoder() {
+        return RESULT_DECODER;
+    }
+
+    Boolean commit=true;
+    public EndTransactionExtendedRequest setCommit(final Boolean commit) {
+        this.commit = commit;
+        return this;
+    }
+
+    String transactionID;
+    public EndTransactionExtendedRequest setTransactionID(final String transactionID) {
+        Reject.ifNull(transactionID);
+        this.transactionID = transactionID;
+        return this;
+    }
+    public boolean isCommit() {
+        return commit;
+    }
+
+    public String getTransactionID() {
+        return transactionID;
+    }
+
+    @Override
+    public ByteString getValue() {
+        Reject.ifNull(transactionID);
+        final ByteStringBuilder buffer = new ByteStringBuilder();
+        final ASN1Writer writer = ASN1.getWriter(buffer);
+        try {
+            writer.writeStartSequence();
+            if (commit!=null) {
+                writer.writeBoolean(commit);
+            }
+            writer.writeOctetString(transactionID);
+            writer.writeEndSequence();
+        } catch (final IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return buffer.toByteString();
+    }
+
+    @Override
+    public boolean hasValue() {
+        return true;
+    }
+
+    private static final EndTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new EndTransactionExtendedRequest.ResultDecoder();
+
+    private static final class ResultDecoder extends  AbstractExtendedResultDecoder<EndTransactionExtendedResult> {
+        @Override
+        public EndTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) {
+            if (!resultCode.isExceptional()) {
+                throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue());
+            }
+            return EndTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage);
+        }
+
+        /*
+        txnEndRes ::= SEQUENCE {
+           messageID MessageID OPTIONAL,
+                -- msgid associated with non-success resultCode
+           updatesControls SEQUENCE OF updateControls SEQUENCE {
+                messageID MessageID,
+                     -- msgid associated with controls
+                controls  Controls
+           } OPTIONAL
+      }
+         */
+        @Override
+        public EndTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException {
+            if (result instanceof EndTransactionExtendedResult) {
+                return (EndTransactionExtendedResult) result;
+            }
+
+            final ResultCode resultCode = result.getResultCode();
+            final EndTransactionExtendedResult newResult =
+                    EndTransactionExtendedResult.newResult(resultCode)
+                            .setMatchedDN(result.getMatchedDN())
+                            .setDiagnosticMessage(result.getDiagnosticMessage());
+
+            final ByteString responseValue = result.getValue();
+            if (!resultCode.isExceptional() && responseValue == null) {
+                throw DecodeException.error(LocalizableMessage.raw("Empty response value"));
+            }
+            if (responseValue != null) {
+                try {
+                    final ASN1Reader reader = ASN1.getReader(responseValue);
+                    if (reader.hasNextElement()) {
+                        reader.readStartSequence();
+                        if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_INTEGER_TYPE) {
+                            newResult.setFailedMessageID(Math.toIntExact(reader.readInteger()));
+                        } else if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) {
+                            reader.readStartSequence();
+                            while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) {
+                                reader.readStartSequence();
+                                final long messageId = reader.readInteger();
+                                final List<Control> controls = new ArrayList<>();
+                                reader.readStartSequence();
+                                while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_OCTET_STRING_TYPE) {
+                                    final ByteString controlEncoded = reader.readOctetString();
+                                    //TODO decode Control
+                                }
+                                reader.readEndSequence();
+                                //newResult.success(messageId, controls.toArray(new Control[]{}));
+                                reader.readEndSequence();
+                            }
+                            reader.readEndSequence();
+                        }
+                        reader.readEndSequence();
+                    }
+                } catch (final IOException e) {
+                    throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e);
+                }
+            }
+            for (final Control control : result.getControls()) {
+                newResult.addControl(control);
+            }
+            return newResult;
+        }
+    }
+}
+
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java
new file mode 100644
index 0000000..a4ba23e
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java
@@ -0,0 +1,168 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/*
+   An End Transaction Response is an LDAPMessage sent in response to a
+   End Transaction Request.  Its response name is absent.  The
+   responseValue when present contains a BER-encoded txnEndRes.
+
+      txnEndRes ::= SEQUENCE {
+           messageID MessageID OPTIONAL,
+                -- msgid associated with non-success resultCode
+           updatesControls SEQUENCE OF updateControls SEQUENCE {
+                messageID MessageID,
+                     -- msgid associated with controls
+                controls  Controls
+           } OPTIONAL
+      }
+      -- where MessageID and Controls are as specified in RFC 4511
+
+   The txnEndRes.messageID provides the message id of the update request
+   associated with a non-success response.  txnEndRes.messageID is
+   absent when resultCode of the End Transaction Response is success
+
+   The txnEndRes.updatesControls provides a facility for returning
+   response controls that normally (i.e., in the absence of
+   transactions) would be returned in an update response.  The
+   updateControls.messageID provides the message id of the update
+   request associated with the response controls provided in
+   updateControls.controls.
+
+   The txnEndRes.updatesControls is absent when there are no update
+   response controls to return.
+
+   If both txnEndRes.messageID and txnEndRes.updatesControl are absent,
+   the responseValue of the End Transaction Response is absent.
+ */
+public class EndTransactionExtendedResult extends AbstractExtendedResult<EndTransactionExtendedResult> {
+    @Override
+    public String getOID() {
+        return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID;
+    }
+
+    private EndTransactionExtendedResult(final ResultCode resultCode) {
+        super(resultCode);
+    }
+
+    public static EndTransactionExtendedResult newResult(final ResultCode resultCode) {
+        Reject.ifNull(resultCode);
+        return new EndTransactionExtendedResult(resultCode);
+    }
+
+    // The message ID for the operation that failed, if applicable.
+    Integer failedOpMessageID=null;
+
+    public EndTransactionExtendedResult setFailedMessageID(final Integer failedOpMessageID) {
+        Reject.ifNull(failedOpMessageID);
+        this.failedOpMessageID = failedOpMessageID;
+        return this;
+    }
+
+    // A mapping of the response controls for the operations performed as part of
+    // the transaction.
+    Map<Integer, List<Control>> opResponseControls= new TreeMap<>();
+
+    public EndTransactionExtendedResult success(Integer messageID, List<Control> responses) {
+        Reject.ifNull(messageID);
+        Reject.ifNull(responses);
+        opResponseControls.put(messageID,responses);
+        return this;
+    }
+
+    /*
+     txnEndRes ::= SEQUENCE {
+           messageID MessageID OPTIONAL,
+                -- msgid associated with non-success resultCode
+           updatesControls SEQUENCE OF updateControls SEQUENCE {
+                messageID MessageID,
+                     -- msgid associated with controls
+                controls  Controls
+           } OPTIONAL
+      }
+    */
+    @Override
+    public ByteString getValue() {
+        final ByteStringBuilder buffer = new ByteStringBuilder();
+        final ASN1Writer writer = ASN1.getWriter(buffer);
+        try {
+            if (failedOpMessageID!=null || (opResponseControls!=null && !opResponseControls.isEmpty()) ) {
+                writer.writeStartSequence();
+                if (failedOpMessageID != null) {
+                    writer.writeInteger(failedOpMessageID);
+                }
+                if (opResponseControls != null && !opResponseControls.isEmpty()) {
+                    writer.writeStartSequence();
+                    for (Map.Entry<Integer, List<Control>> entry : opResponseControls.entrySet()) {
+                        writer.writeStartSequence();
+                            writer.writeInteger(entry.getKey());
+                            writer.writeStartSequence();
+                                for (Control control : entry.getValue()) {
+                                   writer.writeOctetString(control.getValue());
+                                }
+                            writer.writeEndSequence();
+                        writer.writeEndSequence();
+                    }
+                    writer.writeEndSequence();
+                }
+                writer.writeEndSequence();
+            }
+        } catch (final IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return buffer.toByteString();
+    }
+
+    @Override
+    public boolean hasValue() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "EndTransactionExtendedResult(resultCode=" +
+                getResultCode() +
+                ", matchedDN=" +
+                getMatchedDN() +
+                ", diagnosticMessage=" +
+                getDiagnosticMessage() +
+                ", referrals=" +
+                getReferralURIs() +
+                ", responseName=" +
+                getOID() +
+                ", failedOpMessageID=" +
+                failedOpMessageID +
+                ", opResponseControls=" +
+                opResponseControls +
+                ", controls=" +
+                getControls() +
+                ")";
+    }
+}
\ No newline at end of file
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java
new file mode 100644
index 0000000..647063d
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java
@@ -0,0 +1,101 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.requests.AbstractExtendedRequest;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder;
+
+import java.io.IOException;
+
+/*
+ A Start Transaction Request is an LDAPMessage of CHOICE extendedReq
+   where the requestName is 1.3.6.1.1.21.1 and the requestValue is
+   absent.
+ */
+public class StartTransactionExtendedRequest extends AbstractExtendedRequest<StartTransactionExtendedRequest, StartTransactionExtendedResult> {
+    public static final String START_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.1";
+
+    @Override
+    public String getOID() {
+        return START_TRANSACTION_REQUEST_OID;
+    }
+    @Override
+    public ExtendedResultDecoder<StartTransactionExtendedResult> getResultDecoder() {
+        return RESULT_DECODER;
+    }
+
+    @Override
+    public ByteString getValue() {
+        return null;
+    }
+
+    @Override
+    public boolean hasValue() {
+        return false;
+    }
+
+    private static final StartTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new StartTransactionExtendedRequest.ResultDecoder();
+
+    private static final class ResultDecoder extends  AbstractExtendedResultDecoder<StartTransactionExtendedResult> {
+        @Override
+        public StartTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) {
+            if (!resultCode.isExceptional()) {
+                throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue());
+            }
+            return StartTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage);
+        }
+
+        @Override
+        public StartTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException {
+            if (result instanceof StartTransactionExtendedResult) {
+                return (StartTransactionExtendedResult) result;
+            }
+
+            final ResultCode resultCode = result.getResultCode();
+            final StartTransactionExtendedResult newResult =
+                    StartTransactionExtendedResult.newResult(resultCode)
+                            .setMatchedDN(result.getMatchedDN())
+                            .setDiagnosticMessage(result.getDiagnosticMessage());
+
+            final ByteString responseValue = result.getValue();
+            if (!resultCode.isExceptional() && responseValue == null) {
+                throw DecodeException.error(LocalizableMessage.raw("Empty response value"));
+            }
+            if (responseValue != null) {
+                try {
+                    final ASN1Reader reader = ASN1.getReader(responseValue);
+                    newResult.setTransactionID(reader.readOctetStringAsString());
+                } catch (final IOException e) {
+                    throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e);
+                }
+            }
+            for (final Control control : result.getControls()) {
+                newResult.addControl(control);
+            }
+            return newResult;
+        }
+    }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java
new file mode 100644
index 0000000..40dbf04
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java
@@ -0,0 +1,96 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+
+/*
+    A Start Transaction Response is an LDAPMessage of CHOICE extendedRes
+   sent in response to a Start Transaction Request.  Its responseName is
+   absent.  When the resultCode is success (0), responseValue is present
+   and contains a transaction identifier.  Otherwise, the responseValue
+   is absent.
+ */
+public class StartTransactionExtendedResult extends AbstractExtendedResult<StartTransactionExtendedResult> {
+    @Override
+    public String getOID() {
+        return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID;
+    }
+
+    private StartTransactionExtendedResult(final ResultCode resultCode) {
+        super(resultCode);
+    }
+
+    public static StartTransactionExtendedResult newResult(final ResultCode resultCode) {
+        Reject.ifNull(resultCode);
+        return new StartTransactionExtendedResult(resultCode);
+    }
+
+    private String transactionID = null;
+
+    public StartTransactionExtendedResult setTransactionID(final String transactionID) {
+        this.transactionID = transactionID;
+        return this;
+    }
+
+    public String getTransactionID() {
+        return transactionID;
+    }
+
+    @Override
+    public ByteString getValue() {
+        final ByteStringBuilder buffer = new ByteStringBuilder();
+        final ASN1Writer writer = ASN1.getWriter(buffer);
+        try {
+            writer.writeOctetString(transactionID);
+        } catch (final IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return buffer.toByteString();
+    }
+
+    @Override
+    public boolean hasValue() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "StartTransactionExtendedResult(resultCode=" +
+                getResultCode() +
+                ", matchedDN=" +
+                getMatchedDN() +
+                ", diagnosticMessage=" +
+                getDiagnosticMessage() +
+                ", referrals=" +
+                getReferralURIs() +
+                ", responseName=" +
+                getOID() +
+                ", transactionID=" +
+                transactionID +
+                ", controls=" +
+                getControls() +
+                ")";
+    }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
index 2ef0205..1bb350f 100644
--- a/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
+++ b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
@@ -18,13 +18,13 @@
 import org.forgerock.util.Function;
 import org.reactivestreams.Publisher;
 
-import io.reactivex.CompletableEmitter;
-import io.reactivex.CompletableOnSubscribe;
-import io.reactivex.CompletableSource;
-import io.reactivex.Flowable;
-import io.reactivex.SingleEmitter;
-import io.reactivex.SingleOnSubscribe;
-import io.reactivex.SingleSource;
+import io.reactivex.rxjava3.core.CompletableEmitter;
+import io.reactivex.rxjava3.core.CompletableOnSubscribe;
+import io.reactivex.rxjava3.core.CompletableSource;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.SingleEmitter;
+import io.reactivex.rxjava3.core.SingleOnSubscribe;
+import io.reactivex.rxjava3.core.SingleSource;
 
 /**
  * {@link Stream} and {@link Single} implementations based on RxJava.
@@ -96,7 +96,7 @@
      * @return A new {@link Stream}
      */
     public static <V> Single<V> singleFromPublisher(final Publisher<V> publisher) {
-        return new RxJavaSingle<>(io.reactivex.Single.fromPublisher(publisher));
+        return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.fromPublisher(publisher));
     }
 
     /**
@@ -109,7 +109,7 @@
      * @return A new {@link Single}
      */
     public static <V> Single<V> singleFrom(final V value) {
-        return new RxJavaSingle<>(io.reactivex.Single.just(value));
+        return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.just(value));
     }
 
     /**
@@ -122,7 +122,7 @@
      * @return A new {@link Single}
      */
     public static <V> Single<V> singleError(final Throwable error) {
-        return new RxJavaSingle<>(io.reactivex.Single.<V>error(error));
+        return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.<V>error(error));
     }
 
     /**
@@ -135,7 +135,7 @@
      * @return A new {@link Single}
      */
     public static <V> Single<V> newSingle(final Single.Emitter<V> emitter) {
-        return new RxJavaSingle<>(io.reactivex.Single.create(new SingleOnSubscribe<V>() {
+        return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.create(new SingleOnSubscribe<V>() {
             @Override
             public void subscribe(final SingleEmitter<V> e) throws Exception {
                 emitter.subscribe(new Single.Subscriber<V>() {
@@ -161,7 +161,7 @@
      * @return A new {@link Completable}
      */
     public static Completable newCompletable(final Completable.Emitter onSubscribe) {
-        return new RxJavaCompletable(io.reactivex.Completable.create(new CompletableOnSubscribe() {
+        return new RxJavaCompletable(io.reactivex.rxjava3.core.Completable.create(new CompletableOnSubscribe() {
             @Override
             public void subscribe(final CompletableEmitter e) throws Exception {
                 onSubscribe.subscribe(new Completable.Subscriber() {
@@ -186,7 +186,7 @@
      * @return A new {@link Completable}
      */
     public static Completable completableError(final Throwable error) {
-        return new RxJavaCompletable(io.reactivex.Completable.error(error));
+        return new RxJavaCompletable(io.reactivex.rxjava3.core.Completable.error(error));
     }
 
     private static final class RxJavaStream<V> implements Stream<V> {
@@ -204,7 +204,7 @@
 
         @Override
         public <O> Stream<O> map(final Function<V, O, Exception> function) {
-            return new RxJavaStream<>(impl.map(new io.reactivex.functions.Function<V, O>() {
+            return new RxJavaStream<>(impl.map(new io.reactivex.rxjava3.functions.Function<V, O>() {
                 @Override
                 public O apply(V t) throws Exception {
                     return function.apply(t);
@@ -215,7 +215,7 @@
         @Override
         public <O> Stream<O> flatMap(final Function<? super V, ? extends Publisher<? extends O>, Exception> function,
                 int maxConcurrency) {
-            return new RxJavaStream<>(impl.flatMap(new io.reactivex.functions.Function<V, Publisher<? extends O>>() {
+            return new RxJavaStream<>(impl.flatMap(new io.reactivex.rxjava3.functions.Function<V, Publisher<? extends O>>() {
                 @Override
                 public Publisher<? extends O> apply(V t) throws Exception {
                     return function.apply(t);
@@ -225,7 +225,7 @@
 
         @Override
         public Stream<V> onNext(final Consumer<V> onNext) {
-            return new RxJavaStream<>(impl.doOnNext(new io.reactivex.functions.Consumer<V>() {
+            return new RxJavaStream<>(impl.doOnNext(new io.reactivex.rxjava3.functions.Consumer<V>() {
                 @Override
                 public void accept(V value) throws Exception {
                     onNext.accept(value);
@@ -235,7 +235,7 @@
 
         @Override
         public Stream<V> onError(final Consumer<Throwable> onError) {
-            return new RxJavaStream<>(impl.doOnError(new io.reactivex.functions.Consumer<Throwable>() {
+            return new RxJavaStream<>(impl.doOnError(new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
                 @Override
                 public void accept(Throwable t) throws Exception {
                     onError.accept(t);
@@ -246,7 +246,7 @@
         @Override
         public Stream<V> onErrorResumeWith(final Function<Throwable, Publisher<V>, Exception> function) {
             return new RxJavaStream<>(
-                    impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, Publisher<? extends V>>() {
+                    impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, Publisher<? extends V>>() {
                         @Override
                         public Publisher<? extends V> apply(Throwable t) throws Exception {
                             return function.apply(t);
@@ -256,7 +256,7 @@
 
         @Override
         public Stream<V> onComplete(final Action action) {
-            return new RxJavaStream<>(impl.doOnComplete(new io.reactivex.functions.Action() {
+            return new RxJavaStream<>(impl.doOnComplete(new io.reactivex.rxjava3.functions.Action() {
                 @Override
                 public void run() throws Exception {
                     action.run();
@@ -272,9 +272,9 @@
 
     private static final class RxJavaSingle<V> implements Single<V> {
 
-        private final io.reactivex.Single<V> impl;
+        private final io.reactivex.rxjava3.core.Single<V> impl;
 
-        private RxJavaSingle(io.reactivex.Single<V> impl) {
+        private RxJavaSingle(io.reactivex.rxjava3.core.Single<V> impl) {
             this.impl = impl;
         }
 
@@ -285,7 +285,7 @@
 
         @Override
         public <O> Single<O> map(final Function<V, O, Exception> function) {
-            return new RxJavaSingle<>(impl.map(new io.reactivex.functions.Function<V, O>() {
+            return new RxJavaSingle<>(impl.map(new io.reactivex.rxjava3.functions.Function<V, O>() {
                 @Override
                 public O apply(V t) throws Exception {
                     return function.apply(t);
@@ -300,12 +300,12 @@
 
         @Override
         public void subscribe(final Consumer<? super V> resultConsumer, final Consumer<Throwable> errorConsumer) {
-            impl.subscribe(new io.reactivex.functions.Consumer<V>() {
+            impl.subscribe(new io.reactivex.rxjava3.functions.Consumer<V>() {
                 @Override
                 public void accept(V t) throws Exception {
                     resultConsumer.accept(t);
                 }
-            }, new io.reactivex.functions.Consumer<Throwable>() {
+            }, new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
                 @Override
                 public void accept(Throwable t) throws Exception {
                     errorConsumer.accept(t);
@@ -315,10 +315,10 @@
 
         @Override
         public <O> Single<O> flatMap(final Function<V, Single<O>, Exception> function) {
-            return new RxJavaSingle<>(impl.flatMap(new io.reactivex.functions.Function<V, SingleSource<O>>() {
+            return new RxJavaSingle<>(impl.flatMap(new io.reactivex.rxjava3.functions.Function<V, SingleSource<O>>() {
                 @Override
                 public SingleSource<O> apply(V t) throws Exception {
-                    return io.reactivex.Single.fromPublisher(function.apply(t));
+                    return io.reactivex.rxjava3.core.Single.fromPublisher(function.apply(t));
                 }
             }));
         }
@@ -326,10 +326,10 @@
         @Override
         public Single<V> onErrorResumeWith(final Function<Throwable, Single<V>, Exception> function) {
             return new RxJavaSingle<>(
-                    impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, SingleSource<V>>() {
+                    impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, SingleSource<V>>() {
                         @Override
                         public SingleSource<V> apply(Throwable error) throws Exception {
-                            return io.reactivex.Single.fromPublisher(function.apply(error));
+                            return io.reactivex.rxjava3.core.Single.fromPublisher(function.apply(error));
                         }
                     }));
         }
@@ -337,9 +337,9 @@
 
     private static final class RxJavaCompletable implements Completable {
 
-        private final io.reactivex.Completable impl;
+        private final io.reactivex.rxjava3.core.Completable impl;
 
-        RxJavaCompletable(io.reactivex.Completable impl) {
+        RxJavaCompletable(io.reactivex.rxjava3.core.Completable impl) {
             this.impl = impl;
         }
 
@@ -356,22 +356,22 @@
         @Override
         public Completable onErrorResumeWith(final Function<Throwable, Completable, Exception> function) {
             return new RxJavaCompletable(
-                    impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, CompletableSource>() {
+                    impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, CompletableSource>() {
                         @Override
                         public CompletableSource apply(Throwable error) throws Exception {
-                            return io.reactivex.Completable.fromPublisher(function.apply(error));
+                            return io.reactivex.rxjava3.core.Completable.fromPublisher(function.apply(error));
                         }
                     }));
         }
 
         @Override
         public void subscribe(final Action completeAction, final Consumer<Throwable> errorConsumer) {
-            impl.subscribe(new io.reactivex.functions.Action() {
+            impl.subscribe(new io.reactivex.rxjava3.functions.Action() {
                 @Override
                 public void run() throws Exception {
                     completeAction.run();
                 }
-            }, new io.reactivex.functions.Consumer<Throwable>() {
+            }, new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
                 @Override
                 public void accept(final Throwable error) throws Exception {
                     errorConsumer.accept(error);
@@ -381,7 +381,7 @@
 
         @Override
         public Completable doAfterTerminate(final Action onTerminate) {
-            return new RxJavaCompletable(impl.doAfterTerminate(new io.reactivex.functions.Action() {
+            return new RxJavaCompletable(impl.doAfterTerminate(new io.reactivex.rxjava3.functions.Action() {
                 @Override
                 public void run() throws Exception {
                     onTerminate.run();
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java b/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
index 0cb33bf..4edd2a2 100644
--- a/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
+++ b/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
@@ -59,10 +59,10 @@
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.RuntimeExceptionHandler;
 
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.FlowableEmitter;
+import io.reactivex.rxjava3.core.FlowableOnSubscribe;
 
 /**
  * Adapt a {@link ServerConnectionFactory} to a {@link Function} compatible with
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java
new file mode 100644
index 0000000..2d4f589
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java
@@ -0,0 +1,82 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems,LLC.
+ */
+package org.forgerock.opendj.ldap.controls;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.util.Reject;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_BAD_OID;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_DECODE_NULL;
+
+public class TransactionSpecificationRequestControl implements Control{
+
+    public final static String OID="1.3.6.1.1.21.2";
+
+    final String transactionId;
+    public TransactionSpecificationRequestControl(String transactionId) {
+        Reject.ifNull(transactionId);
+        this.transactionId = transactionId;
+    }
+
+    @Override
+    public String getOID() {
+        return OID;
+    }
+
+    @Override
+    public ByteString getValue() {
+        return ByteString.valueOfUtf8(transactionId);
+    }
+
+    @Override
+    public boolean hasValue() {
+        return true;
+    }
+
+    @Override
+    public boolean isCritical() {
+        return true;
+    }
+
+    public static final ControlDecoder<TransactionSpecificationRequestControl> DECODER = new ControlDecoder<TransactionSpecificationRequestControl>() {
+        @Override
+        public TransactionSpecificationRequestControl decodeControl(final Control control, final DecodeOptions options)
+                throws DecodeException {
+            Reject.ifNull(control);
+
+            if (control instanceof TransactionSpecificationRequestControl) {
+                return (TransactionSpecificationRequestControl) control;
+            }
+
+            if (!control.getOID().equals(OID)) {
+                throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_BAD_OID.get(control.getOID(), OID));
+            }
+
+            if (!control.hasValue()) {
+                throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_DECODE_NULL.get());
+            }
+
+            return new TransactionSpecificationRequestControl(control.getValue().toString());
+        }
+
+        @Override
+        public String getOID() {
+            return OID;
+        }
+    };
+}
diff --git a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
index 09085e4..c34d851 100644
--- a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
+++ b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
@@ -2,6 +2,8 @@
 define maildomain=example.com
 
 branch: [suffix]
+objectClass: top
+objectClass: domain
 
 branch: ou=People,[suffix]
 subordinateTemplate: person
diff --git a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
index 0f2c423..6d3074f 100644
--- a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
+++ b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
@@ -5,6 +5,8 @@
 define numgroup=5
 
 branch: [suffix]
+objectClass: top
+objectClass: domain
 subordinateTemplate: ous:[numous]
 
 template: ous
@@ -59,4 +61,4 @@
 rdnAttr: cn
 objectClass: top
 objectClass: groupOfNames
-cn: Group_<sequential:1>
\ No newline at end of file
+cn: Group_<sequential:1>
diff --git a/opendj-doc-generated-ref/pom.xml b/opendj-doc-generated-ref/pom.xml
index ccf7249..380302b 100644
--- a/opendj-doc-generated-ref/pom.xml
+++ b/opendj-doc-generated-ref/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-doc-generated-ref</artifactId>
@@ -656,7 +656,7 @@
                                     <goal>run</goal>
                                 </goals>
                                 <configuration>
-                                    <tasks>
+                                    <target>
                                         <copy todir="${project.build.directory}/asciidoc/source/man-pages">
                                             <fileset dir="${basedir}/src/main/asciidoc/man-pages" includes="**/*" />
                                         </copy>
@@ -667,7 +667,7 @@
                                         <delete>
                                             <fileset dir="${project.build.directory}/asciidoc/source/man-pages" includes="man-*-subcommands-ref*" />
                                         </delete>
-                                    </tasks>
+                                    </target>
                                 </configuration>
                             </execution>
                             <execution>
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc b/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
index 5d3707f..926f3b9 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
@@ -24,7 +24,7 @@
 [#appendix-standards]
 == Standards, RFCs, & Internet-Drafts
 
-OpenDJ 3.5 software implements the following RFCs, Internet-Drafts, and standards:
+OpenDJ software implements the following RFCs, Internet-Drafts, and standards:
 --
 
 [#rfc1274]
@@ -396,6 +396,13 @@
 +
 Describes the Lightweight Directory Access Protocol (LDAP) / X.500 'entryDN' operational attribute, that provides a copy of the entry's distinguished name for use in attribute value assertions.
 
+[#rfc5805]
+link:http://tools.ietf.org/html/rfc5805[RFC 5805: Lightweight Directory Access Protocol (LDAP) Transactions, window=\_top]::
++
+Lightweight Directory Access Protocol (LDAP) update operations, such as Add, Delete, and Modify operations, have atomic, consistency,
+isolation, durability (ACID) properties.  Each of these update operations act upon an entry.  It is often desirable to update two or
+more entries in a single unit of interaction, a transaction.
+
 [#fips180-1]
 link:http://www.itl.nist.gov/fipspubs/fip180-1.htm[FIPS 180-1: Secure Hash Standard (SHA-1), window=\_top]::
 +
diff --git a/opendj-doc-maven-plugin/pom.xml b/opendj-doc-maven-plugin/pom.xml
index 022ece3..2b013c7 100644
--- a/opendj-doc-maven-plugin/pom.xml
+++ b/opendj-doc-maven-plugin/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-doc-maven-plugin</artifactId>
@@ -54,19 +54,25 @@
         <dependency>
             <groupId>org.twdata.maven</groupId>
             <artifactId>mojo-executor</artifactId>
-            <version>2.2.0</version>
+            <version>2.4.1</version>
         </dependency>
 
         <dependency>
             <groupId>com.thoughtworks.qdox</groupId>
             <artifactId>qdox</artifactId>
-            <version>2.0-M3</version>
+            <version>2.2.0</version>
         </dependency>
 
         <dependency>
             <groupId>org.apache.maven.plugin-tools</groupId>
             <artifactId>maven-plugin-annotations</artifactId>
-            <version>3.6.0</version>
+            <version>3.15.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-core</artifactId>
+            <version>3.9.9</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
diff --git a/opendj-dsml-servlet/pom.xml b/opendj-dsml-servlet/pom.xml
index 4224925..423f222 100644
--- a/opendj-dsml-servlet/pom.xml
+++ b/opendj-dsml-servlet/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-parent</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-dsml-servlet</artifactId>
@@ -41,7 +41,6 @@
         <dependency>
       		<groupId>jakarta.servlet</groupId>
       		<artifactId>jakarta.servlet-api</artifactId>
-      		<version>6.0.0</version>
             <scope>provided</scope>
     	</dependency>
 
@@ -79,24 +78,24 @@
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
-        
+
         <dependency>
         	<groupId>com.sun.xml.ws</groupId>
 		    <artifactId>jaxws-ri</artifactId>
 		    <version>4.0.3</version>
 		    <type>pom</type>
         </dependency>
-        
+
         <dependency>
 		    <groupId>jakarta.xml.bind</groupId>
 		    <artifactId>jakarta.xml.bind-api</artifactId>
 		    <version>3.0.1</version>
 		</dependency>
-		
+
 		<dependency>
             <groupId>org.glassfish.jaxb</groupId>
             <artifactId>jaxb-runtime</artifactId>
-            <version>4.0.1</version>
+            <version>4.0.5</version>
             <scope>runtime</scope>
         </dependency>
     </dependencies>
diff --git a/opendj-embedded-server-examples/pom.xml b/opendj-embedded-server-examples/pom.xml
index f85cac3..5180fb0 100644
--- a/opendj-embedded-server-examples/pom.xml
+++ b/opendj-embedded-server-examples/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-embedded-server-examples</artifactId>
diff --git a/opendj-embedded/pom.xml b/opendj-embedded/pom.xml
index 5ffb5ee..73ff62a 100644
--- a/opendj-embedded/pom.xml
+++ b/opendj-embedded/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-parent</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
     <name>OpenDJ Embedded Server</name>
     <artifactId>opendj-embedded</artifactId>
@@ -38,7 +38,7 @@
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-core</artifactId>
-            <version>1.5.13</version>
+            <version>1.5.18</version>
             <exclusions>
                 <exclusion>
                     <artifactId>slf4j-api</artifactId>
@@ -49,7 +49,7 @@
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
-            <version>1.5.13</version>
+            <version>1.5.18</version>
             <exclusions>
                 <exclusion>
                     <artifactId>slf4j-api</artifactId>
diff --git a/opendj-grizzly/pom.xml b/opendj-grizzly/pom.xml
index 7a6e175..2eff4ff 100644
--- a/opendj-grizzly/pom.xml
+++ b/opendj-grizzly/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-grizzly</artifactId>
@@ -51,6 +51,10 @@
 	            		<groupId>io.reactivex.rxjava2</groupId>
 	            		<artifactId>rxjava</artifactId>
 	            	</exclusion>
+	            	<exclusion>
+	            		<groupId>io.reactivex.rxjava3</groupId>
+	            		<artifactId>rxjava</artifactId>
+	            	</exclusion>
             </exclusions>
         </dependency>
 
@@ -62,7 +66,6 @@
         <dependency>
             <groupId>org.glassfish.grizzly</groupId>
             <artifactId>grizzly-framework</artifactId>
-            <version>${grizzly-framework.version}</version>
         </dependency>
 
         <dependency>
@@ -138,10 +141,6 @@
                 <artifactId>maven-surefire-plugin</artifactId>
             </plugin>
 
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-source-plugin</artifactId>
-            </plugin>
         </plugins>
     </build>
 
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
index 0af9bc6..d557f6c 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -85,8 +85,8 @@
 import com.forgerock.reactive.ReactiveHandler;
 import com.forgerock.reactive.Stream;
 
-import io.reactivex.exceptions.OnErrorNotImplementedException;
-import io.reactivex.internal.util.BackpressureHelper;
+import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
+import org.openidentityplatform.rxjava3.internal.util.BackpressureHelper;
 
 /**
  * Grizzly filter implementation for decoding LDAP requests and handling server side logic for SSL and SASL operations
@@ -390,7 +390,7 @@
                     return false;
                 }
                 SSLUtils.setSSLEngine(connection, sslEngine);
-                
+
                 Properties props = System.getProperties();
 
                 // Workaround for PKCS11
@@ -398,7 +398,7 @@
                 if ("none".equalsIgnoreCase(keyStoreFile)) {
                 	System.setProperty(SSLContextConfigurator.TRUST_STORE_FILE, "NONE");
                 }
-                
+
                 SSLFilter sslFilter = new SSLFilter();
                 sslFilter.setHandshakeTimeout(getLongProperty("org.forgerock.opendj.grizzly.handshakeTimeout", 10000), TimeUnit.MILLISECONDS);
                 installFilter(startTls ? new StartTLSFilter(sslFilter) : sslFilter);
@@ -661,7 +661,7 @@
                     });
                 }
             });
-            
+
         }
     }
 }
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
index 138e371..47d90d8 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
@@ -18,10 +18,13 @@
 
 import java.util.concurrent.CancellationException;
 
-import io.reactivex.exceptions.UndeliverableException;
+import io.reactivex.rxjava3.exceptions.UndeliverableException;
+
 import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage;
+
 import org.glassfish.grizzly.CompletionHandler;
 import org.glassfish.grizzly.Connection;
+
 import org.reactivestreams.Subscriber;
 import org.reactivestreams.Subscription;
 
diff --git a/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java b/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java
new file mode 100644
index 0000000..2d3c0d4
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * 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.
+ */
+
+package org.openidentityplatform.rxjava3.internal.util;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Utility class to help with backpressure-related operations such as request aggregation.
+ */
+public final class BackpressureHelper {
+    /** Utility class. */
+    private BackpressureHelper() {
+        throw new IllegalStateException("No instances!");
+    }
+
+    /**
+     * Adds two long values and caps the sum at {@link Long#MAX_VALUE}.
+     * @param a the first value
+     * @param b the second value
+     * @return the sum capped at {@link Long#MAX_VALUE}
+     */
+    public static long addCap(long a, long b) {
+        long u = a + b;
+        if (u < 0L) {
+            return Long.MAX_VALUE;
+        }
+        return u;
+    }
+
+    /**
+     * Multiplies two long values and caps the product at {@link Long#MAX_VALUE}.
+     * @param a the first value
+     * @param b the second value
+     * @return the product capped at {@link Long#MAX_VALUE}
+     */
+    public static long multiplyCap(long a, long b) {
+        long u = a * b;
+        if (((a | b) >>> 31) != 0) {
+            if (u / a != b) {
+                return Long.MAX_VALUE;
+            }
+        }
+        return u;
+    }
+
+    /**
+     * Atomically adds the positive value n to the requested value in the {@link AtomicLong} and
+     * caps the result at {@link Long#MAX_VALUE} and returns the previous value.
+     * @param requested the {@code AtomicLong} holding the current requested value
+     * @param n the value to add, must be positive (not verified)
+     * @return the original value before the add
+     */
+    public static long add(@NonNull AtomicLong requested, long n) {
+        for (;;) {
+            long r = requested.get();
+            if (r == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
+            }
+            long u = addCap(r, n);
+            if (requested.compareAndSet(r, u)) {
+                return r;
+            }
+        }
+    }
+
+    /**
+     * Atomically adds the positive value n to the requested value in the {@link AtomicLong} and
+     * caps the result at {@link Long#MAX_VALUE} and returns the previous value and
+     * considers {@link Long#MIN_VALUE} as a cancel indication (no addition then).
+     * @param requested the {@code AtomicLong} holding the current requested value
+     * @param n the value to add, must be positive (not verified)
+     * @return the original value before the add
+     */
+    public static long addCancel(@NonNull AtomicLong requested, long n) {
+        for (;;) {
+            long r = requested.get();
+            if (r == Long.MIN_VALUE) {
+                return Long.MIN_VALUE;
+            }
+            if (r == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
+            }
+            long u = addCap(r, n);
+            if (requested.compareAndSet(r, u)) {
+                return r;
+            }
+        }
+    }
+
+    /**
+     * Atomically subtract the given number (positive, not validated) from the target field unless it contains {@link Long#MAX_VALUE}.
+     * @param requested the target field holding the current requested amount
+     * @param n the produced element count, positive (not validated)
+     * @return the new amount
+     */
+    public static long produced(@NonNull AtomicLong requested, long n) {
+        for (;;) {
+            long current = requested.get();
+            if (current == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
+            }
+            long update = current - n;
+            if (update < 0L) {
+                RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update));
+                update = 0L;
+            }
+            if (requested.compareAndSet(current, update)) {
+                return update;
+            }
+        }
+    }
+
+    /**
+     * Atomically subtract the given number (positive, not validated) from the target field if
+     * it doesn't contain {@link Long#MIN_VALUE} (indicating some cancelled state) or {@link Long#MAX_VALUE} (unbounded mode).
+     * @param requested the target field holding the current requested amount
+     * @param n the produced element count, positive (not validated)
+     * @return the new amount
+     */
+    public static long producedCancel(@NonNull AtomicLong requested, long n) {
+        for (;;) {
+            long current = requested.get();
+            if (current == Long.MIN_VALUE) {
+                return Long.MIN_VALUE;
+            }
+            if (current == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
+            }
+            long update = current - n;
+            if (update < 0L) {
+                RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update));
+                update = 0L;
+            }
+            if (requested.compareAndSet(current, update)) {
+                return update;
+            }
+        }
+    }
+}
diff --git a/opendj-ldap-sdk-examples/pom.xml b/opendj-ldap-sdk-examples/pom.xml
index 8c8b6e3..6417083 100644
--- a/opendj-ldap-sdk-examples/pom.xml
+++ b/opendj-ldap-sdk-examples/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-ldap-sdk-examples</artifactId>
diff --git a/opendj-ldap-toolkit/pom.xml b/opendj-ldap-toolkit/pom.xml
index 42744ea..e318807 100644
--- a/opendj-ldap-toolkit/pom.xml
+++ b/opendj-ldap-toolkit/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-ldap-toolkit</artifactId>
diff --git a/opendj-legacy/pom.xml b/opendj-legacy/pom.xml
index 3a725d2..f95e2a0 100644
--- a/opendj-legacy/pom.xml
+++ b/opendj-legacy/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-legacy</artifactId>
   <name>OpenDJ Legacy</name>
diff --git a/opendj-maven-plugin/pom.xml b/opendj-maven-plugin/pom.xml
index 641e251..7b66d61 100644
--- a/opendj-maven-plugin/pom.xml
+++ b/opendj-maven-plugin/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>opendj-maven-plugin</artifactId>
@@ -41,13 +41,19 @@
     <dependency>
         <groupId>org.twdata.maven</groupId>
         <artifactId>mojo-executor</artifactId>
-        <version>2.3.0</version>
+        <version>2.4.1</version>
     </dependency>
     <dependency>
         <groupId>org.apache.maven.plugin-tools</groupId>
         <artifactId>maven-plugin-annotations</artifactId>
-        <version>3.6.0</version>
+        <version>3.15.1</version>
         <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>3.9.9</version>
+      <scope>provided</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml
new file mode 100644
index 0000000..3631751
--- /dev/null
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  The contents of this file are subject to the terms of the Common Development and
+  Distribution License (the License). You may not use this file except in compliance with the
+  License.
+
+  You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+  specific language governing permission and limitations under the License.
+
+  When distributing Covered Software, include this CDDL Header Notice in each file and include
+  the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+  Header, with the fields enclosed by brackets [] replaced by your own identifying
+  information: "Portions Copyright [year] [name of copyright owner]".
+
+  Copyright 2025 3A Systems,LLC.
+  ! -->
+<adm:managed-object name="end-transaction-extended-operation-handler"
+  plural-name="end-transaction-extended-operation-handlers"
+  package="org.forgerock.opendj.server.config"
+  extends="extended-operation-handler"
+  xmlns:adm="http://opendj.forgerock.org/admin"
+  xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+    An End Transaction Request is an LDAPMessage of CHOICE extendedReq
+    where the requestName is 1.3.6.1.1.21.3 and the requestValue is
+    present and contains a BER-encoded txnEndReq.
+
+    txnEndReq ::= SEQUENCE {
+    commit         BOOLEAN DEFAULT TRUE,
+    identifier     OCTET STRING }
+
+    A commit value of TRUE indicates a request to commit the transaction
+    identified by the identifier.  A commit value of FALSE indicates a
+    request to abort the identified transaction.
+
+    An End Transaction Response is an LDAPMessage sent in response to a
+    End Transaction Request.  Its response name is absent.  The
+    responseValue when present contains a BER-encoded txnEndRes.
+
+    txnEndRes ::= SEQUENCE {
+    messageID MessageID OPTIONAL,
+    -- msgid associated with non-success resultCode
+    updatesControls SEQUENCE OF updateControls SEQUENCE {
+    messageID MessageID,
+    -- msgid associated with controls
+    controls  Controls
+    } OPTIONAL
+    }
+    -- where MessageID and Controls are as specified in RFC 4511
+
+    The txnEndRes.messageID provides the message id of the update request
+    associated with a non-success response.  txnEndRes.messageID is
+    absent when resultCode of the End Transaction Response is success
+    (0).
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>
+        ds-cfg-end-transaction-extended-operation-handler
+      </ldap:name>
+      <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.extensions.EndTransactionExtendedOperation
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml
new file mode 100644
index 0000000..e70290a
--- /dev/null
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  The contents of this file are subject to the terms of the Common Development and
+  Distribution License (the License). You may not use this file except in compliance with the
+  License.
+
+  You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+  specific language governing permission and limitations under the License.
+
+  When distributing Covered Software, include this CDDL Header Notice in each file and include
+  the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+  Header, with the fields enclosed by brackets [] replaced by your own identifying
+  information: "Portions Copyright [year] [name of copyright owner]".
+
+  Copyright 2025 3A Systems,LLC.
+  ! -->
+<adm:managed-object name="start-transaction-extended-operation-handler"
+  plural-name="start-transaction-extended-operation-handlers"
+  package="org.forgerock.opendj.server.config"
+  extends="extended-operation-handler"
+  xmlns:adm="http://opendj.forgerock.org/admin"
+  xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+    A client wishing to perform a sequence of directory updates as a
+    transaction issues a Start Transaction Request.  A server that is
+    willing and able to support transactions responds to this request
+    with a Start Transaction Response providing a transaction identifier
+    and with a resultCode of success (0).  Otherwise, the server responds
+    with a Start Transaction Response with a resultCode other than
+    success indicating the nature of the failure.
+
+    The transaction identifier provided upon successful start of a
+    transaction is used in subsequent protocol messages to identify this
+    transaction.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>
+        ds-cfg-start-transaction-extended-operation-handler
+      </ldap:name>
+      <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.extensions.StartTransactionExtendedOperation
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-openidm-account-change-notification-handler/pom.xml b/opendj-openidm-account-change-notification-handler/pom.xml
index 825bed4..a0fff3a 100644
--- a/opendj-openidm-account-change-notification-handler/pom.xml
+++ b/opendj-openidm-account-change-notification-handler/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-openidm-account-change-notification-handler</artifactId>
   <name>OpenDJ account change notification handler for OpenIDM</name>
diff --git a/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml b/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
index 2b691ba..d135f74 100644
--- a/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
+++ b/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-deb</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-deb-standard</artifactId>
diff --git a/opendj-packages/opendj-deb/pom.xml b/opendj-packages/opendj-deb/pom.xml
index 31e7534..3809a10 100644
--- a/opendj-packages/opendj-deb/pom.xml
+++ b/opendj-packages/opendj-deb/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-packages</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <profiles>
diff --git a/opendj-packages/opendj-docker/pom.xml b/opendj-packages/opendj-docker/pom.xml
index ff2c653..4a32f7a 100644
--- a/opendj-packages/opendj-docker/pom.xml
+++ b/opendj-packages/opendj-docker/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-packages</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <profiles>
@@ -74,16 +74,16 @@
 		      <execution>
 		        <phase>prepare-package</phase>
 		        <configuration>
-		          <tasks>
+		          <target>
 			          <zip basedir="${project.build.directory}" destfile="${project.build.directory}/Dockerfile.zip" includes="opendj-docker/**">
 	                  	<fileset dir="${basedir}">
-	      					<include name="Dockerfile*" /> 
-	      					<include name="../opendj-openshift-template/*.yaml" /> 
+	      					<include name="Dockerfile*" />
+	      					<include name="../opendj-openshift-template/*.yaml" />
 	                      	<include name="bootstrap/**" />
 	                      	<include name="run.sh" />
 	  					</fileset>
 	                  </zip>
-		          </tasks>
+		          </target>
 		        </configuration>
 		        <goals>
 		          <goal>run</goal>
diff --git a/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml b/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
index 5650712..f4b67a9 100644
--- a/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
+++ b/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-msi</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-msi-standard</artifactId>
diff --git a/opendj-packages/opendj-msi/pom.xml b/opendj-packages/opendj-msi/pom.xml
index df36b20..d019a62 100644
--- a/opendj-packages/opendj-msi/pom.xml
+++ b/opendj-packages/opendj-msi/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-packages</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-msi</artifactId>
diff --git a/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml b/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
index 32af268..d793364 100644
--- a/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
+++ b/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-rpm</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-rpm-standard</artifactId>
diff --git a/opendj-packages/opendj-rpm/pom.xml b/opendj-packages/opendj-rpm/pom.xml
index 32e70d0..52d416a 100644
--- a/opendj-packages/opendj-rpm/pom.xml
+++ b/opendj-packages/opendj-rpm/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-packages</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <packaging>pom</packaging>
diff --git a/opendj-packages/opendj-svr4/pom.xml b/opendj-packages/opendj-svr4/pom.xml
index 25bf9e1..f59b90a 100644
--- a/opendj-packages/opendj-svr4/pom.xml
+++ b/opendj-packages/opendj-svr4/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-packages</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-svr4</artifactId>
diff --git a/opendj-packages/pom.xml b/opendj-packages/pom.xml
index 39abc6d..9d15d09 100644
--- a/opendj-packages/pom.xml
+++ b/opendj-packages/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-parent</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-packages</artifactId>
diff --git a/opendj-rest2ldap-servlet/pom.xml b/opendj-rest2ldap-servlet/pom.xml
index 53c3611..89b34f1 100644
--- a/opendj-rest2ldap-servlet/pom.xml
+++ b/opendj-rest2ldap-servlet/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>opendj-rest2ldap-servlet</artifactId>
diff --git a/opendj-rest2ldap/pom.xml b/opendj-rest2ldap/pom.xml
index a2a19b5..ca291fe 100644
--- a/opendj-rest2ldap/pom.xml
+++ b/opendj-rest2ldap/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <artifactId>opendj-parent</artifactId>
         <groupId>org.openidentityplatform.opendj</groupId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 	
     <artifactId>opendj-rest2ldap</artifactId>
diff --git a/opendj-server-example-plugin/pom.xml b/opendj-server-example-plugin/pom.xml
index b82baf33..f09bfd2 100644
--- a/opendj-server-example-plugin/pom.xml
+++ b/opendj-server-example-plugin/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-server-example-plugin</artifactId>
   <name>OpenDJ Server Example Plugin</name>
@@ -38,7 +38,6 @@
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
-      <version>3.3.2</version>
     </dependency>
   </dependencies>
   <build><finalName>${project.groupId}.${project.artifactId}</finalName>
@@ -80,10 +79,6 @@
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
         <executions>
           <execution>
diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml
index b9a9356..27c7334 100644
--- a/opendj-server-legacy/pom.xml
+++ b/opendj-server-legacy/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-server-legacy</artifactId>
   <packaging>jar</packaging>
@@ -266,7 +266,7 @@
     <dependency>
       <groupId>org.postgresql</groupId>
       <artifactId>postgresql</artifactId>
-      <version>42.7.5</version>
+      <version>[42.7.7,)</version>
     </dependency>
     <dependency>
       <groupId>org.testng</groupId>
diff --git a/opendj-server-legacy/resource/config/config.ldif b/opendj-server-legacy/resource/config/config.ldif
index f836b18..0683e93 100644
--- a/opendj-server-legacy/resource/config/config.ldif
+++ b/opendj-server-legacy/resource/config/config.ldif
@@ -14,6 +14,7 @@
 # Portions Copyright 2012-2014 Manuel Gaupp
 # Portions Copyright 2010-2016 ForgeRock AS.
 # Portions copyright 2015 Edan Idzerda
+# Portions Copyright 2025 3A Systems, LLC.
 
 # This file contains the primary Directory Server configuration.  It must not
 # be directly edited while the server is online.  The server configuration
@@ -603,6 +604,22 @@
 ds-cfg-java-class: org.opends.server.extensions.WhoAmIExtendedOperation
 ds-cfg-enabled: true
 
+dn: cn=Start Transaction,cn=Extended Operations,cn=config
+objectClass: top
+objectClass: ds-cfg-extended-operation-handler
+objectClass: ds-cfg-start-transaction-extended-operation-handler
+cn: Start Transaction
+ds-cfg-java-class: org.opends.server.extensions.StartTransactionExtendedOperation
+ds-cfg-enabled: true
+
+dn: cn=End Transaction,cn=Extended Operations,cn=config
+objectClass: top
+objectClass: ds-cfg-extended-operation-handler
+objectClass: ds-cfg-end-transaction-extended-operation-handler
+cn: End Transaction
+ds-cfg-java-class: org.opends.server.extensions.EndTransactionExtendedOperation
+ds-cfg-enabled: true
+
 dn: cn=Group Implementations,cn=config
 objectClass: top
 objectClass: ds-cfg-branch
diff --git a/opendj-server-legacy/resource/schema/02-config.ldif b/opendj-server-legacy/resource/schema/02-config.ldif
index a950d83..2f8cc52 100644
--- a/opendj-server-legacy/resource/schema/02-config.ldif
+++ b/opendj-server-legacy/resource/schema/02-config.ldif
@@ -6265,4 +6265,13 @@
   STRUCTURAL
   MAY ds-cfg-pbkdf2-iterations
   X-ORIGIN 'OpenDJ Directory Server' )
-  
\ No newline at end of file
+objectClasses: ( 1.3.6.1.4.1.60142.1.2.1
+  NAME 'ds-cfg-start-transaction-extended-operation-handler'
+  SUP ds-cfg-extended-operation-handler
+  STRUCTURAL
+  X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.60142.1.2.2
+  NAME 'ds-cfg-end-transaction-extended-operation-handler'
+  SUP ds-cfg-extended-operation-handler
+  STRUCTURAL
+  X-ORIGIN 'OpenDS Directory Server' )
\ No newline at end of file
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
index ab47f5f..7779dc7 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
@@ -117,10 +117,10 @@
 import com.forgerock.reactive.ReactiveHandler;
 import com.forgerock.reactive.Stream;
 
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.FlowableEmitter;
+import io.reactivex.rxjava3.core.FlowableOnSubscribe;
 
 /**
  * This class defines an LDAP client connection, which is a type of client connection that will be accepted by an
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java b/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
index 7487b3d..a891fee 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2009 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems, LLC.
  */
 package org.opends.server.api;
 
@@ -20,11 +21,8 @@
 import java.nio.channels.ByteChannel;
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -53,6 +51,7 @@
 import org.opends.server.types.Privilege;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
+import org.opends.server.types.operation.RollbackOperation;
 import org.opends.server.util.TimeThread;
 
 import static org.opends.messages.CoreMessages.*;
@@ -1576,4 +1575,47 @@
   {
     return getConnectionID() < 0;
   }
+
+  public class Transaction {
+      final String transactionId=UUID.randomUUID().toString().toLowerCase();
+
+      public Transaction() {
+          transactions.put(getTransactionId(),this);
+      }
+
+      public String getTransactionId() {
+          return transactionId;
+      }
+
+      final Queue<Operation> waiting=new LinkedList<>();
+      public void add(Operation operation) {
+          waiting.add(operation);
+      }
+
+      public Queue<Operation> getWaiting() {
+          return waiting;
+      }
+
+      public void clear() {
+          transactions.remove(getTransactionId());
+      }
+      final Deque<RollbackOperation> completed =new ArrayDeque<>();
+      public void success(RollbackOperation operation) {
+          completed.add(operation);
+      }
+
+      public Deque<RollbackOperation> getCompleted() {
+          return completed;
+      }
+  }
+
+  Map<String,Transaction> transactions=new ConcurrentHashMap<>();
+
+  public Transaction startTransaction() {
+      return new Transaction();
+  }
+
+  public Transaction getTransaction(String id) {
+      return transactions.get(id);
+  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java
new file mode 100644
index 0000000..9ebb554
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java
@@ -0,0 +1,131 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package org.opends.server.extensions;
+
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult;
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.server.config.server.EndTransactionExtendedOperationHandlerCfg;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.Operation;
+import org.opends.server.types.operation.RollbackOperation;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
+
+
+public class EndTransactionExtendedOperation extends ExtendedOperationHandler<EndTransactionExtendedOperationHandlerCfg>
+{
+  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+  public EndTransactionExtendedOperation()
+  {
+    super();
+  }
+
+  @Override
+  public void initializeExtendedOperationHandler(EndTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException
+  {
+    super.initializeExtendedOperationHandler(config);
+  }
+
+  @Override
+  public void processExtendedOperation(ExtendedOperation operation)
+  {
+    final EndTransactionExtendedRequest request =new EndTransactionExtendedRequest();
+    if (operation.getRequestValue()!= null) {
+        final ASN1Reader reader = ASN1.getReader(operation.getRequestValue());
+        try {
+            reader.readStartSequence();
+            if (reader.hasNextElement()&& (reader.peekType() == ASN1.UNIVERSAL_BOOLEAN_TYPE)) {
+              request.setCommit(reader.readBoolean());
+            }
+            request.setTransactionID(reader.readOctetStringAsString());
+            reader.readEndSequence();
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+    }
+
+    final ClientConnection.Transaction trn=operation.getClientConnection().getTransaction(request.getTransactionID());
+    if (trn==null) {
+        operation.setResultCode(ResultCode.CANCELLED);
+        operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+request.getTransactionID()));
+        return;
+    }
+
+    final EndTransactionExtendedResult res=EndTransactionExtendedResult.newResult(ResultCode.SUCCESS);
+    operation.setResultCode(res.getResultCode());
+    Operation currentOperation=null;
+    try {
+        while((currentOperation=trn.getWaiting().poll())!=null) {
+            if (request.isCommit()) {
+                currentOperation.run();
+                if (!ResultCode.SUCCESS.equals(currentOperation.getResultCode())) {
+                    throw new InterruptedException();
+                }
+                currentOperation.operationCompleted();
+                //res.success(currentOperation.getMessageID(),currentOperation.getResponseControls());
+            }
+        }
+    }catch (Throwable e){
+        res.setFailedMessageID(currentOperation.getMessageID());
+        operation.setResultCode(currentOperation.getResultCode());
+        operation.setErrorMessage(currentOperation.getErrorMessage());
+        //rollback
+        RollbackOperation cancelOperation=null;
+        while((cancelOperation=trn.getCompleted().pollLast())!=null) {
+            try {
+                cancelOperation.rollback();
+            }catch (Throwable e2){
+                throw new RuntimeException("rollback error",e2);
+            }
+        }
+    }finally {
+        trn.clear();
+    }
+    operation.setResponseOID(res.getOID());
+    operation.setResponseValue(res.getValue());
+  }
+
+  @Override
+  public String getExtendedOperationOID()
+  {
+    return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID;
+  }
+
+  @Override
+  public String getExtendedOperationName()
+  {
+    return "End Transaction";
+  }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java
new file mode 100644
index 0000000..b7fb0a3
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java
@@ -0,0 +1,66 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC
+ */
+package org.opends.server.extensions;
+
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.server.config.server.StartTransactionExtendedOperationHandlerCfg;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.types.InitializationException;
+
+public class StartTransactionExtendedOperation extends ExtendedOperationHandler<StartTransactionExtendedOperationHandlerCfg>
+{
+  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+  public StartTransactionExtendedOperation()
+  {
+    super();
+  }
+
+  @Override
+  public void initializeExtendedOperationHandler(StartTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException
+  {
+    super.initializeExtendedOperationHandler(config);
+  }
+
+  @Override
+  public void processExtendedOperation(ExtendedOperation operation)
+  {
+    final StartTransactionExtendedResult res=StartTransactionExtendedResult
+            .newResult(ResultCode.SUCCESS)
+            .setTransactionID(operation.getClientConnection().startTransaction().getTransactionId());
+
+    operation.setResponseOID(res.getOID());
+    operation.setResponseValue(res.getValue());
+    operation.setResultCode(res.getResultCode());
+  }
+
+  @Override
+  public String getExtendedOperationOID()
+  {
+    return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID;
+  }
+
+  @Override
+  public String getExtendedOperationName()
+  {
+    return "Start Transaction";
+  }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
index d88bdca..1ceae70 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
@@ -13,15 +13,19 @@
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems, LLC.
  */
 package org.opends.server.extensions;
 
 import java.util.Map;
 
 import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.api.DirectoryThread;
 import org.opends.server.core.DirectoryServer;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.Operation;
@@ -145,8 +149,29 @@
         {
           // The operation is not null, so process it.  Make sure that when
           // processing is complete.
-          operation.run();
-          operation.operationCompleted();
+
+          //check has transactionId control
+          ClientConnection.Transaction transaction=null;
+          if (operation instanceof AbstractOperation) {
+            String transactionId = ((AbstractOperation) operation).getTransactionId();
+            if (transactionId!=null){
+              transaction=operation.getClientConnection().getTransaction(transactionId);
+              if (transaction==null){ //unknown transactionId
+                operation.setResultCode(ResultCode.CANCELLED);
+                operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+transactionId));
+                operation.getClientConnection().sendResponse(operation);
+                continue;
+              }
+            }
+          }
+          if (transaction==null) {  //run
+            operation.run();
+            operation.operationCompleted();
+          }else { //suspend for commit
+            transaction.add(operation);
+            operation.setResultCode(ResultCode.SUCCESS);
+            operation.getClientConnection().sendResponse(operation);
+          }
         }
       }
       catch (Throwable t)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
index 61ee9f2..5f5c67c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2023-2025 3A Systems, LLC.
  */
 package org.opends.server.types;
 
@@ -29,6 +30,7 @@
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
 import org.forgerock.util.Reject;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.plugin.PluginResult.OperationResult;
@@ -721,4 +723,16 @@
     }
     return true;
   }
+
+  public String getTransactionId() {
+      for (Control control : getRequestControls()) {
+          if (control.getOID().equals(TransactionSpecificationRequestControl.OID)) {
+            if ((control instanceof LDAPControl) && ((LDAPControl)control).getValue()!=null){
+              return ((LDAPControl) control).getValue().toString();
+            }
+          }
+      }
+      return null;
+  }
+
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java
new file mode 100644
index 0000000..cdeb151
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java
@@ -0,0 +1,26 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems,LLC.
+ */
+package org.opends.server.types.operation;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.DirectoryException;
+
+public interface RollbackOperation
+{
+  void rollback() throws CanceledOperationException, DirectoryException;
+}
+
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 1b86927..03a1e3b 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -13,7 +13,7 @@
  *
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions Copyright 2024-2025 3A Systems,LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -39,6 +39,7 @@
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Syntax;
@@ -61,6 +62,7 @@
 import org.opends.server.core.PasswordPolicy;
 import org.opends.server.core.PersistentSearch;
 import org.opends.server.core.ServerContext;
+import org.opends.server.protocols.ldap.LDAPControl;
 import org.opends.server.schema.AuthPasswordSyntax;
 import org.opends.server.schema.UserPasswordSyntax;
 import org.opends.server.types.Attribute;
@@ -73,10 +75,7 @@
 import org.opends.server.types.LockManager.DNLock;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.SearchFilter;
-import org.opends.server.types.operation.PostOperationAddOperation;
-import org.opends.server.types.operation.PostResponseAddOperation;
-import org.opends.server.types.operation.PostSynchronizationAddOperation;
-import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.*;
 import org.opends.server.util.TimeThread;
 
 /**
@@ -86,7 +85,7 @@
 public class LocalBackendAddOperation
        extends AddOperationWrapper
        implements PreOperationAddOperation, PostOperationAddOperation,
-                  PostResponseAddOperation, PostSynchronizationAddOperation
+                  PostResponseAddOperation, PostSynchronizationAddOperation,RollbackOperation
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
@@ -470,6 +469,9 @@
         }
 
         backend.addEntry(entry, this);
+        if (trx!=null) {
+          trx.success(this);
+        }
       }
 
       LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest,
@@ -496,7 +498,10 @@
     }
   }
 
-
+  @Override
+  public void rollback() throws CanceledOperationException, DirectoryException {
+    backend.deleteEntry(entryDN,null);
+  }
 
   private void processSynchPostOperationPlugins()
   {
@@ -968,6 +973,10 @@
       {
         RelaxRulesControlRequested = true;
       }
+      else if (TransactionSpecificationRequestControl.OID.equals(oid))
+      {
+        trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw newDirectoryException(entryDN, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -975,9 +984,11 @@
       }
     }
   }
+  ClientConnection.Transaction trx=null;
 
   private AccessControlHandler<?> getAccessControlHandler()
   {
     return AccessControlConfigManager.getInstance().getAccessControlHandler();
   }
+
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index 7fb633e..0a10664 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -13,7 +13,7 @@
  *
  * Copyright 2008-2009 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
- * Portions Copyright 2022-2024 3A Systems, LLC.
+ * Portions Copyright 2022-2025 3A Systems, LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -23,6 +23,7 @@
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
 import org.opends.server.api.AccessControlHandler;
 import org.opends.server.api.LocalBackend;
 import org.opends.server.api.ClientConnection;
@@ -35,6 +36,7 @@
 import org.opends.server.core.DeleteOperationWrapper;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
 import org.opends.server.types.CanceledOperationException;
 import org.opends.server.types.Control;
 import org.forgerock.opendj.ldap.DN;
@@ -43,10 +45,7 @@
 import org.opends.server.types.LockManager.DNLock;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.operation.PostOperationDeleteOperation;
-import org.opends.server.types.operation.PostResponseDeleteOperation;
-import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
-import org.opends.server.types.operation.PreOperationDeleteOperation;
+import org.opends.server.types.operation.*;
 
 import static org.opends.messages.CoreMessages.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -63,7 +62,7 @@
        extends DeleteOperationWrapper
        implements PreOperationDeleteOperation, PostOperationDeleteOperation,
                   PostResponseDeleteOperation,
-                  PostSynchronizationDeleteOperation
+                  PostSynchronizationDeleteOperation, RollbackOperation
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
@@ -299,6 +298,9 @@
           return;
         }
         backend.deleteEntry(entryDN, this);
+        if (trx!=null) {
+          trx.success(this);
+        }
       }
 
       LocalBackendWorkflowElement.addPreReadResponse(this, preReadRequest, entry);
@@ -416,6 +418,10 @@
       {
         continue;
       }
+      else if (TransactionSpecificationRequestControl.OID.equals(oid))
+      {
+        trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw newDirectoryException(entry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -423,6 +429,7 @@
       }
     }
   }
+  ClientConnection.Transaction trx=null;
 
   /**
    * Handle conflict resolution.
@@ -488,4 +495,9 @@
       }
       return true;
   }
+
+  @Override
+  public void rollback() throws CanceledOperationException, DirectoryException {
+    backend.addEntry(entry,null);
+  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index a0a575d..effec13 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -13,7 +13,7 @@
  *
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions copyright 2024-2025 3A Systems,LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -29,6 +29,7 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.opends.server.api.AccessControlHandler;
 import org.opends.server.api.LocalBackend;
@@ -43,6 +44,7 @@
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.ModifyDNOperationWrapper;
 import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.Attributes;
 import org.opends.server.types.CanceledOperationException;
@@ -54,10 +56,7 @@
 import org.opends.server.types.Modification;
 import org.forgerock.opendj.ldap.RDN;
 import org.opends.server.types.SearchFilter;
-import org.opends.server.types.operation.PostOperationModifyDNOperation;
-import org.opends.server.types.operation.PostResponseModifyDNOperation;
-import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
-import org.opends.server.types.operation.PreOperationModifyDNOperation;
+import org.opends.server.types.operation.*;
 
 import static org.opends.messages.CoreMessages.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -75,7 +74,7 @@
   implements PreOperationModifyDNOperation,
              PostOperationModifyDNOperation,
              PostResponseModifyDNOperation,
-             PostSynchronizationModifyDNOperation
+             PostSynchronizationModifyDNOperation, RollbackOperation
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
@@ -439,6 +438,9 @@
           return;
         }
         currentBackend.renameEntry(entryDN, newEntry, this);
+        if (trx!=null) {
+          trx.success(this);
+        }
       }
 
       // Attach the pre-read and/or post-read controls to the response if
@@ -576,6 +578,10 @@
       {
         continue;
       }
+      else if (TransactionSpecificationRequestControl.OID.equals(oid))
+      {
+        trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -583,6 +589,7 @@
       }
     }
   }
+  ClientConnection.Transaction trx=null;
 
   private AccessControlHandler<?> getAccessControlHandler()
   {
@@ -816,4 +823,9 @@
           }
       }
   }
+
+  @Override
+  public void rollback() throws CanceledOperationException, DirectoryException {
+     backend.renameEntry(newEntry.getName(), currentEntry, this);
+  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 6995427..f2c7f78 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -13,7 +13,7 @@
  *
  * Copyright 2008-2011 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions Copyright 2024-2025 3A Systems,LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -35,6 +35,7 @@
 import org.forgerock.opendj.ldap.RDN;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.MatchingRule;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -60,6 +61,7 @@
 import org.opends.server.core.PasswordPolicy;
 import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
 import org.opends.server.schema.AuthPasswordSyntax;
 import org.opends.server.schema.UserPasswordSyntax;
 import org.opends.server.types.AcceptRejectWarn;
@@ -77,10 +79,7 @@
 import org.opends.server.types.Privilege;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.operation.PostOperationModifyOperation;
-import org.opends.server.types.operation.PostResponseModifyOperation;
-import org.opends.server.types.operation.PostSynchronizationModifyOperation;
-import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.*;
 
 import static org.opends.messages.CoreMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -96,7 +95,7 @@
        extends ModifyOperationWrapper
        implements PreOperationModifyOperation, PostOperationModifyOperation,
                   PostResponseModifyOperation,
-                  PostSynchronizationModifyOperation
+                  PostSynchronizationModifyOperation, RollbackOperation
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
@@ -500,6 +499,9 @@
         }
 
         backend.replaceEntry(currentEntry, modifiedEntry, this);
+        if (trx!=null) {
+          trx.success(this);
+        }
 
         if (isAuthnManagedLocally())
         {
@@ -697,6 +699,10 @@
       {
         RelaxRulesControlRequested = true;
       }
+      else if (TransactionSpecificationRequestControl.OID.equals(oid))
+      {
+        trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw newDirectoryException(currentEntry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -704,6 +710,7 @@
       }
     }
   }
+  ClientConnection.Transaction trx=null;
 
   private void processNonPasswordModifications() throws DirectoryException
   {
@@ -1658,4 +1665,9 @@
           }
       }
   }
+
+  @Override
+  public void rollback() throws CanceledOperationException, DirectoryException {
+      backend.replaceEntry(modifiedEntry,currentEntry, this);
+  }
 }
diff --git a/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java
new file mode 100644
index 0000000..916f253
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java
@@ -0,0 +1,260 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2025 3A Systems, LLC.
+ */
+package org.openidentityplatform.opendj;
+
+
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
+import org.forgerock.opendj.ldap.requests.*;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest;
+import static org.testng.Assert.assertThrows;
+
+@Test(sequential = true)
+public class Rfc5808TestCase extends DirectoryServerTestCase {
+    Connection connection;
+
+    @BeforeClass
+    public void startServer() throws Exception {
+        TestCaseUtils.startServer();
+        TestCaseUtils.initializeTestBackend(true);
+
+        final LDAPConnectionFactory factory =new LDAPConnectionFactory("localhost", TestCaseUtils.getServerLdapPort());
+        connection = factory.getConnection();
+        connection.bind("cn=Directory Manager", "password".toCharArray());
+        assertThat(connection.isValid()).isTrue();
+    }
+
+    @Test
+    public void test() throws LdapException {
+        //unknown transaction in TransactionSpecificationRequestControl
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                AddRequest add=Requests
+                        .newAddRequest("ou=People,o=test")
+                        .addAttribute("objectClass", "top", "organizationalUnit")
+                        .addAttribute("ou", "People")
+                        .addControl(new TransactionSpecificationRequestControl("bad"))
+                        ;
+                Result result = connection.add(add);
+            }
+        });
+
+        //unknown transaction in EndTransactionExtendedRequest
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown").setCommit(true));
+            }
+        });
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown"));
+            }
+        });
+
+        //commit
+        StartTransactionExtendedResult resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+        assertThat(resStart.isSuccess()).isTrue();
+        assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+        String transactionID=resStart.getTransactionID();
+        assertThat(transactionID).isNotEmpty();
+
+        AddRequest add=Requests
+                .newAddRequest("ou=People,o=test")
+                .addAttribute("objectClass", "top", "organizationalUnit")
+                .addAttribute("ou", "People")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+                ;
+        Result result = connection.add(add);
+        assertThat(result.isSuccess()).isTrue();
+
+        add= Requests.newAddRequest("sn=bjensen,ou=People,o=test")
+                .addAttribute("objectClass","top","person")
+                .addAttribute("cn","bjensen")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.add(add);
+        assertThat(result.isSuccess()).isTrue();
+
+        ModifyDNRequest mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2")
+        .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.modifyDN(mdn);
+        assertThat(result.isSuccess()).isTrue();
+
+        ModifyRequest edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test")
+                .addModification(ModificationType.REPLACE,"cn","bjensen2")
+        .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.modify(edit);
+        assertThat(result.isSuccess()).isTrue();
+
+        DeleteRequest delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test")
+        .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.delete(delete);
+        assertThat(result.isSuccess()).isTrue();
+
+        assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)");
+            }
+        });
+
+        EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID));
+        assertThat(resEnd.isSuccess()).isTrue();
+        assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3");
+
+        //check commit successfully
+        assertThat(connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)")).isNotNull();
+
+        //check transaction finished
+        String finalTransactionID = transactionID;
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID).setCommit(true));
+            }
+        });
+
+        //rollback by EndTransactionExtendedRequest
+        resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+        assertThat(resStart.isSuccess()).isTrue();
+        assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+        transactionID=resStart.getTransactionID();
+        assertThat(transactionID).isNotEmpty();
+
+        add=Requests
+                .newAddRequest("ou=People2,o=test")
+                .addAttribute("objectClass", "top", "organizationalUnit")
+                .addAttribute("ou", "People2")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+                ;
+        result = connection.add(add);
+        assertThat(result.isSuccess()).isTrue();
+
+        resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID).setCommit(false));
+        assertThat(resEnd.isSuccess()).isTrue();
+        assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3");
+
+        //check transaction finished
+        String finalTransactionID1 = transactionID;
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID1).setCommit(false));
+            }
+        });
+
+        //check rollback successfully
+        assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People2)");
+            }
+        });
+
+        //rollback by error
+        resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+        assertThat(resStart.isSuccess()).isTrue();
+        assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+        transactionID=resStart.getTransactionID();
+        assertThat(transactionID).isNotEmpty();
+
+        add= Requests.newAddRequest("sn=bjensen0,ou=People,o=test")
+                .addAttribute("objectClass","top","person")
+                .addAttribute("cn","bjensen0")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.add(add);
+        assertThat(result.isSuccess()).isTrue();
+
+        add= Requests.newAddRequest("sn=bjensen,ou=People,o=test")
+                .addAttribute("objectClass","top","person")
+                .addAttribute("cn","bjensen")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.add(add);
+        assertThat(result.isSuccess()).isTrue();
+
+        mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+                ;
+        result = connection.modifyDN(mdn);
+        assertThat(result.isSuccess()).isTrue();
+
+        edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test")
+                .addModification(ModificationType.REPLACE,"cn","bjensen2")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+                ;
+        result = connection.modify(edit);
+        assertThat(result.isSuccess()).isTrue();
+
+        delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+                ;
+        result = connection.delete(delete);
+        assertThat(result.isSuccess()).isTrue();
+
+        delete=Requests.newDeleteRequest("sn=bjensen3,ou=People,o=test")
+                .addControl(new TransactionSpecificationRequestControl(transactionID))
+        ;
+        result = connection.delete(delete);
+        assertThat(result.isSuccess()).isTrue();
+
+        String finalTransactionID3 = transactionID;
+        assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID3).setCommit(true));
+            }
+        });
+
+        //check transaction finished
+        String finalTransactionID2 = transactionID;
+        assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID2).setCommit(false));
+            }
+        });
+
+        //check rollback successfully
+        assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+            @Override
+            public void run() throws Throwable {
+                connection.searchSingleEntry("ou=People,o=test",SearchScope.SINGLE_LEVEL,"(cn=bjensen0)");
+            }
+        });
+
+    }
+}
diff --git a/opendj-server-msad-plugin/pom.xml b/opendj-server-msad-plugin/pom.xml
index 886fb18..54b5706 100644
--- a/opendj-server-msad-plugin/pom.xml
+++ b/opendj-server-msad-plugin/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.openidentityplatform.opendj</groupId>
         <artifactId>opendj-parent</artifactId>
-        <version>4.9.5-SNAPSHOT</version>
+        <version>4.10.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>opendj-server-msad-plugin</artifactId>
diff --git a/opendj-server/pom.xml b/opendj-server/pom.xml
index d6c618a..6cb37e6 100644
--- a/opendj-server/pom.xml
+++ b/opendj-server/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-    <version>4.9.5-SNAPSHOT</version>
+    <version>4.10.1-SNAPSHOT</version>
   </parent>
   <artifactId>opendj-server</artifactId>
   <name>OpenDJ Server NG</name>
@@ -129,10 +129,6 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
       </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-      </plugin>
     </plugins>
   </build>
   <reporting>
diff --git a/pom.xml b/pom.xml
index 580cd21..5ff4e73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,13 +13,13 @@
   information: "Portions Copyright [year] [name of copyright owner]".
 
   Copyright 2011-2016 ForgeRock AS.
-  Portions  Copyright 2017-2024 3A Systems, LLC.
+  Portions  Copyright 2017-2025 3A Systems, LLC.
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 	<groupId>org.openidentityplatform.opendj</groupId>
     <artifactId>opendj-parent</artifactId>
-	<version>4.9.5-SNAPSHOT</version>
+	  <version>4.10.1-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>OpenDJ Directory Services Project</name>
@@ -35,10 +35,9 @@
         <product.locales>ca_ES,es,de,fr,ja,ko,pl,zh_CN,zh_TW</product.locales>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <localized.jars.classifier>i18n</localized.jars.classifier>
-        <commons.version>2.2.5-SNAPSHOT</commons.version>
-        <freemarker.version>2.3.31</freemarker.version>
-        <grizzly-framework.version>2.3.35</grizzly-framework.version>
-        <metrics-core.version>3.1.2</metrics-core.version>
+        <commons.version>2.4.0</commons.version>
+        <freemarker.version>2.3.34</freemarker.version>
+        <metrics-core.version>4.2.30</metrics-core.version>
         <maven.compiler.target>11</maven.compiler.target>
         <maven.compiler.source>11</maven.compiler.source>
         <!-- OSGi bundles properties -->
@@ -119,36 +118,28 @@
       	<tag>HEAD</tag>
   	</scm>
 	<repositories>
-		<repository>
-			<id>ossrh-snapshots</id>
-			<name>Sonatype OSS Repository</name>
-			<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
-			<snapshots><enabled>true</enabled></snapshots>
-		</repository>
-	</repositories>
+	        <repository>
+	            <name>Central Portal Snapshots</name>
+	            <id>central-portal-snapshots</id>
+	            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
+	            <releases>
+	                <enabled>false</enabled>
+	            </releases>
+	            <snapshots>
+	                <enabled>true</enabled>
+	            </snapshots>
+	        </repository>
+	    </repositories>
 	<pluginRepositories>
 		<pluginRepository>
-			<id>ossrh-snapshots</id>
-			<name>Sonatype OSS Repository</name>
-			<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+			<name>Central Portal Snapshots</name>
+	            <id>central-portal-snapshots</id>
+	            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
 			<snapshots>
 				<enabled>true</enabled>
 			</snapshots>
 		</pluginRepository>
 	</pluginRepositories>
-	<distributionManagement>
-		<repository>
-			<id>ossrh</id>
-			<name>Sonatype OSS Repository</name>
-			<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
-		</repository>
-		<snapshotRepository>
-			<id>ossrh</id>
-			<name>Sonatype OSS Repository</name>
-			<url>https://oss.sonatype.org/content/repositories/snapshots</url>
-		</snapshotRepository>
-	</distributionManagement>
-
     <dependencyManagement>
         <dependencies>
         	<dependency>
@@ -315,26 +306,16 @@
 			  </configuration>
 			</plugin>
 			<plugin>
-		      <groupId>org.sonatype.plugins</groupId>
-		      <artifactId>nexus-staging-maven-plugin</artifactId>
-		      <version>1.6.13</version>
-		      <extensions>true</extensions>
-		      <configuration>
-		        <serverId>ossrh</serverId>
-		        <nexusUrl>https://oss.sonatype.org/</nexusUrl>
-		        <autoReleaseAfterClose>false</autoReleaseAfterClose>
-		        <stagingProgressTimeoutMinutes>15</stagingProgressTimeoutMinutes>
-		        <keepStagingRepositoryOnFailure>true</keepStagingRepositoryOnFailure>
-		        <keepStagingRepositoryOnCloseRuleFailure>true</keepStagingRepositoryOnCloseRuleFailure>
-		      </configuration>
-		      <dependencies>
-		      	<dependency>
-			        <groupId>com.google.guava</groupId>
-			        <artifactId>guava</artifactId>
-			        <version>15.0</version>
-			     </dependency>
-		      </dependencies>
-		    </plugin>
+				<groupId>org.sonatype.central</groupId>
+				<artifactId>central-publishing-maven-plugin</artifactId>
+				<version>0.8.0</version>
+				<extensions>true</extensions>
+				<configuration>
+				    <publishingServerId>ossrh</publishingServerId>
+				    <autoPublish>true</autoPublish>
+				    <waitMaxTime>5400</waitMaxTime>
+				</configuration>
+			    </plugin>
         </plugins>
 
         <pluginManagement>
@@ -348,7 +329,7 @@
                     <inherited>true</inherited>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
-                    <version>3.13.0</version>
+                    <version>3.14.0</version>
                     <configuration>
                         <fork>true</fork>
                         <compilerArgs>
@@ -364,83 +345,69 @@
 				<plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-assembly-plugin</artifactId>
-                    <version>3.6.0</version>
+                    <version>3.7.1</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
 			        <artifactId>maven-release-plugin</artifactId>
-			        <version>3.0.0-M7</version>
+			        <version>3.1.1</version>
 			        <configuration>
  			        	<signTag>true</signTag>
  			        	<tagNameFormat>@{project.version}</tagNameFormat>
  			        	<allowTimestampedSnapshots>false</allowTimestampedSnapshots>
  			        </configuration>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
-                    <version>3.3.0</version>
+                    <version>3.4.2</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-war-plugin</artifactId>
-                    <version>3.2.3</version>
+                    <version>3.4.0</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-shade-plugin</artifactId>
-                    <version>3.2.1</version>
+                    <version>3.6.0</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-failsafe-plugin</artifactId>
-                    <version>3.2.5</version>
+                    <version>3.5.3</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-resources-plugin</artifactId>
                     <version>3.3.1</version>
                 </plugin>
-                
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-clean-plugin</artifactId>
-                    <version>3.1.0</version>
+                    <version>3.4.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-dependency-plugin</artifactId>
-                    <version>3.6.0</version>
+                    <version>3.8.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-javadoc-plugin</artifactId>
-                    <version>3.10.1</version>
+                    <version>3.11.2</version>
                 </plugin>
-
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-source-plugin</artifactId>
-                    <version>3.2.0</version>
-                    <executions>
-                        <execution>
-                            <goals>
-                                <goal>jar</goal>
-                            </goals>
-                        </execution>
-                    </executions>
-                </plugin>
-
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
-                    <version>3.2.5</version> <!-- M6 DecodeException but got java.nio.BufferOverflowException -->
+                    <version>3.5.3</version> <!-- M6 DecodeException but got java.nio.BufferOverflowException -->
                     <configuration>
 						<properties>
                             <property>
@@ -464,7 +431,7 @@
                 <plugin>
                     <groupId>org.apache.felix</groupId>
                     <artifactId>maven-bundle-plugin</artifactId>
-                    <version>5.1.1</version>
+                    <version>5.1.9</version>
                     <extensions>true</extensions>
                     <configuration>
                         <instructions>
@@ -488,6 +455,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-site-plugin</artifactId>
+                    <version>3.21.0</version>
                     <configuration>
                         <locales>en</locales>
                     </configuration>
@@ -514,7 +482,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-plugin-plugin</artifactId>
-                    <version>3.6.0</version>
+                    <version>3.15.1</version>
                     <configuration>
                         <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
                     </configuration>
@@ -611,7 +579,7 @@
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>build-helper-maven-plugin</artifactId>
-                    <version>3.3.0</version>
+                    <version>3.6.0</version>
                     <executions>
                         <!-- Parse version to generate properties (major.version, minor.version, ...) -->
                         <execution>
@@ -627,7 +595,7 @@
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>buildnumber-maven-plugin</artifactId>
-                    <version>1.4</version>
+                    <version>3.2.1</version>
                     <executions>
                         <execution>
                             <id>generate-buildnumber</id>
@@ -735,7 +703,7 @@
 	                <plugin>
 				      <groupId>org.apache.maven.plugins</groupId>
 				      <artifactId>maven-gpg-plugin</artifactId>
-				      <version>1.6</version>
+				      <version>3.2.7</version>
 				      <configuration>
                       	<passphrase>${gpg.passphrase}</passphrase>
                         <useAgent>true</useAgent>
@@ -783,7 +751,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-project-info-reports-plugin</artifactId>
-                <version>2.4</version>
+                <version>3.9.0</version>
                 <reportSets>
                     <reportSet>
                         <reports>

--
Gitblit v1.10.0