[577300] Optimize CDOLockState caching

https://bugs.eclipse.org/bugs/show_bug.cgi?id=577300
diff --git a/plugins/org.eclipse.emf.cdo.common/.settings/.api_filters b/plugins/org.eclipse.emf.cdo.common/.settings/.api_filters
index 2894e75..89de9d5 100644
--- a/plugins/org.eclipse.emf.cdo.common/.settings/.api_filters
+++ b/plugins/org.eclipse.emf.cdo.common/.settings/.api_filters
@@ -457,13 +457,15 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java" type="org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockState">
+    <resource path="src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockChangeInfo.java" type="org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockChangeInfo">
         <filter id="576725006">
             <message_arguments>
-                <message_argument value="CDOLockState"/>
-                <message_argument value="AbstractCDOLockState"/>
+                <message_argument value="CDOLockChangeInfo"/>
+                <message_argument value="AbstractCDOLockChangeInfo"/>
             </message_arguments>
         </filter>
+    </resource>
+    <resource path="src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java" type="org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockState">
         <filter id="576725006">
             <message_arguments>
                 <message_argument value="InternalCDOLockState"/>
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockChangeInfo.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockChangeInfo.java
index f91a3fb..e0efd87 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockChangeInfo.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockChangeInfo.java
@@ -12,10 +12,11 @@
 
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
+import org.eclipse.emf.cdo.common.id.CDOID;
 
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 
-import java.util.List;
+import java.util.Set;
 
 /**
  * Represents a change in the lock state of a set of objects. Instances are meant to be sent from the server to the
@@ -29,12 +30,6 @@
 public interface CDOLockChangeInfo extends CDOBranchPoint
 {
   /**
-   * @return <code>true</code> if this instance signals that all {@link CDOLockState lock states} must be invalidated,
-   *         <code>false</code> otherwise
-   */
-  public boolean isInvalidateAll();
-
-  /**
    * @return The branch at which the lock changes took place, same as <code>getView().getBranch()</code>.
    */
   @Override
@@ -48,31 +43,54 @@
   public long getTimeStamp();
 
   /**
-   * @return the type of lock operation that caused the lock changes
-   */
-  public Operation getOperation();
-
-  /**
-   * @return the type of locks that were affected by the lock operation
-   */
-  public LockType getLockType();
-
-  /**
    * @return The view, represented as a {@link CDOLockOwner}, that authored the lock changes.
    */
   public CDOLockOwner getLockOwner();
 
   /**
-   * @return The new lock states of the objects that were affected by the change
-   * @deprecated As of 4.15 use the faster {@link #getNewLockStates()} method.
+   * @since 4.15
    */
-  @Deprecated
+  public CDOLockDelta[] getLockDeltas();
+
+  /**
+   * @return The new lock states of the objects that were affected by the change
+   */
   public CDOLockState[] getLockStates();
 
   /**
    * @since 4.15
    */
-  public List<CDOLockState> getNewLockStates();
+  public Set<Operation> getOperations();
+
+  /**
+   * @since 4.15
+   */
+  public Set<LockType> getLockTypes();
+
+  /**
+   * @since 4.15
+   */
+  public Set<CDOID> getAffectedIDs();
+
+  /**
+   * @return <code>true</code> if this instance signals that all {@link CDOLockState lock states} must be invalidated,
+   *         <code>false</code> otherwise
+   */
+  public boolean isInvalidateAll();
+
+  /**
+   * @return the type of lock operation that caused the lock changes
+   * @deprecated As of 4.15 use {@link #getOperations()}.
+   */
+  @Deprecated
+  public Operation getOperation();
+
+  /**
+   * @return the type of locks that were affected by the lock operation
+   * @deprecated As of 4.15 use {@link #getLockTypes()}.
+   */
+  @Deprecated
+  public LockType getLockType();
 
   /**
    * Enumerates the possible locking operations.
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockDelta.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockDelta.java
new file mode 100644
index 0000000..4d79d02
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockDelta.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.common.lock;
+
+import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
+
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+
+/**
+ * @author Eike Stepper
+ * @since 4.15
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface CDOLockDelta extends CDOIDAndBranch
+{
+  public Object getTarget();
+
+  public LockType getType();
+
+  public CDOLockOwner getOldOwner();
+
+  public CDOLockOwner getNewOwner();
+
+  public Kind getKind();
+
+  /**
+   * @author Eike Stepper
+   */
+  public enum Kind
+  {
+    ADDED, REMOVED, REMAPPED;
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockState.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockState.java
index e33bf8e..af4734b 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockState.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockState.java
@@ -57,7 +57,7 @@
    * If the 'others' argument is <code>true</code>, this method returns <code>true</code> if this lock is currently held
    * by <i>another</i> view (i.e. any view different from the requesting one), <code>false</code> otherwise.
    */
-  public boolean isLocked(LockType lockType, CDOLockOwner lockOwner, boolean others);
+  public boolean isLocked(LockType type, CDOLockOwner by, boolean others);
 
   public Set<CDOLockOwner> getReadLockOwners();
 
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockUtil.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockUtil.java
index d680925..af241f0 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockUtil.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/CDOLockUtil.java
@@ -20,17 +20,20 @@
 import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 import org.eclipse.emf.cdo.internal.common.lock.CDOLockAreaImpl;
 import org.eclipse.emf.cdo.internal.common.lock.CDOLockChangeInfoImpl;
+import org.eclipse.emf.cdo.internal.common.lock.CDOLockDeltaImpl;
 import org.eclipse.emf.cdo.internal.common.lock.CDOLockStateImpl;
 import org.eclipse.emf.cdo.internal.common.lock.DurableCDOLockOwner;
 import org.eclipse.emf.cdo.internal.common.lock.NormalCDOLockOwner;
 import org.eclipse.emf.cdo.spi.common.lock.InternalCDOLockState;
 
 import org.eclipse.net4j.util.HexUtil;
+import org.eclipse.net4j.util.ObjectUtil;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
@@ -52,6 +55,16 @@
    */
   public static final int DURABLE_VIEW_ID = 0;
 
+  /**
+   * @since 4.15
+   */
+  public static final CDOLockState[] NO_LOCK_STATES = {};
+
+  /**
+   * @since 4.15
+   */
+  public static final CDOLockDelta[] NO_LOCK_DELTAS = {};
+
   private CDOLockUtil()
   {
   }
@@ -103,19 +116,6 @@
     return -1;
   }
 
-  public static CDOLockState copyLockState(CDOLockState lockState)
-  {
-    return ((CDOLockStateImpl)lockState).copy();
-  }
-
-  /**
-   * @since 4.12
-   */
-  public static CDOLockState copyLockState(CDOLockState lockState, Object lockedObject)
-  {
-    return ((CDOLockStateImpl)lockState).copy(lockedObject);
-  }
-
   /**
    * @since 4.15
    */
@@ -123,21 +123,21 @@
   {
     InternalCDOLockState cdoLockState = new CDOLockStateImpl(lockState.getLockedObject());
 
-    for (CDOCommonView view : lockState.getReadLockOwners())
+    for (CDOCommonView readLockOwner : lockState.getReadLockOwners())
     {
-      cdoLockState.addReadLockOwner(createLockOwner(view));
+      cdoLockState.addOwner(createLockOwner(readLockOwner), LockType.READ);
     }
 
     CDOCommonView writeLockOwner = lockState.getWriteLockOwner();
     if (writeLockOwner != null)
     {
-      cdoLockState.setWriteLockOwner(createLockOwner(writeLockOwner));
+      cdoLockState.addOwner(createLockOwner(writeLockOwner), LockType.WRITE);
     }
 
     CDOCommonView writeOptionOwner = lockState.getWriteOptionOwner();
     if (writeOptionOwner != null)
     {
-      cdoLockState.setWriteOptionOwner(createLockOwner(writeOptionOwner));
+      cdoLockState.addOwner(createLockOwner(writeOptionOwner), LockType.OPTION);
     }
 
     return cdoLockState;
@@ -148,15 +148,6 @@
     return new CDOLockStateImpl(target);
   }
 
-  /**
-   * @deprecated As of 4.15 use {@link #convertLockState(LockState)}.
-   */
-  @Deprecated
-  public static CDOLockState createLockState(LockState<Object, ? extends CDOCommonView> lockState)
-  {
-    return convertLockState(lockState);
-  }
-
   public static CDOLockOwner createLockOwner(CDOCommonView view)
   {
     int sessionID = view.getSessionID();
@@ -179,22 +170,28 @@
   }
 
   /**
-   * @deprecated As of 4.15 use the faster {@link #createLockChangeInfo(CDOBranchPoint, CDOLockOwner, Operation, LockType, Collection)} method.
+   * @since 4.15
    */
-  @Deprecated
-  public static CDOLockChangeInfo createLockChangeInfo(long timestamp, CDOLockOwner lockOwner, CDOBranch branch, Operation op, LockType lockType,
-      CDOLockState[] newLockStates)
+  public static CDOLockDelta createLockDelta(Object target, LockType type, CDOLockOwner oldOwner, CDOLockOwner newOwner)
   {
-    return createLockChangeInfo(branch.getPoint(timestamp), lockOwner, op, lockType, Arrays.asList(newLockStates));
+    return CDOLockDeltaImpl.create(target, type, oldOwner, newOwner);
   }
 
   /**
    * @since 4.15
    */
-  public static CDOLockChangeInfo createLockChangeInfo(CDOBranchPoint branchPoint, CDOLockOwner lockOwner, Operation op, LockType lockType,
-      Collection<? extends CDOLockState> newLockStates)
+  public static CDOLockDelta createLockDelta(Object target)
   {
-    return new CDOLockChangeInfoImpl(branchPoint, lockOwner, op, lockType, newLockStates);
+    return CDOLockDeltaImpl.createNull(target);
+  }
+
+  /**
+   * @since 4.15
+   */
+  public static CDOLockChangeInfo createLockChangeInfo(CDOBranchPoint branchPoint, CDOLockOwner lockOwner, Collection<CDOLockDelta> lockDeltas,
+      Collection<CDOLockState> lockStates)
+  {
+    return new CDOLockChangeInfoImpl(branchPoint, lockOwner, lockDeltas, lockStates);
   }
 
   public static CDOLockChangeInfo createLockChangeInfo()
@@ -202,13 +199,6 @@
     return new CDOLockChangeInfoImpl();
   }
 
-  public static CDOLockChangeInfo createLockChangeInfo(long timestamp, CDOCommonView view, CDOBranch viewedBranch, Operation op, LockType lockType,
-      CDOLockState[] newLockStates)
-  {
-    CDOLockOwner lockOwner = createLockOwner(view);
-    return createLockChangeInfo(timestamp, lockOwner, viewedBranch, op, lockType, newLockStates);
-  }
-
   public static LockArea createLockArea(String durableLockingID, String userID, CDOBranchPoint branchPoint, boolean readOnly, Map<CDOID, LockGrade> locks)
   {
     return new CDOLockAreaImpl(durableLockingID, userID, branchPoint, readOnly, locks);
@@ -233,4 +223,91 @@
 
     return HexUtil.bytesToHex(buffer);
   }
+
+  /**
+   * @since 4.15
+   */
+  public static List<CDOLockDelta> appendLockDelta(List<CDOLockDelta> deltas, Object target, LockType type, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+  {
+    return appendLockDelta(deltas, createLockDelta(target, type, oldOwner, newOwner));
+  }
+
+  /**
+   * @since 4.15
+   */
+  public static List<CDOLockDelta> appendLockDelta(List<CDOLockDelta> deltas, CDOLockDelta delta)
+  {
+    if (delta != null)
+    {
+      if (deltas == null)
+      {
+        deltas = new ArrayList<>(1);
+      }
+
+      deltas.add(delta);
+    }
+
+    return deltas;
+  }
+
+  /**
+   * @since 4.15
+   */
+  public static CDOLockDelta[] toArray(List<CDOLockDelta> deltas)
+  {
+    if (ObjectUtil.isEmpty(deltas))
+    {
+      return NO_LOCK_DELTAS;
+    }
+
+    return deltas.toArray(new CDOLockDelta[deltas.size()]);
+  }
+
+  /**
+   * @deprecated As of 4.15 no longer supported.
+   */
+  @Deprecated
+  public static CDOLockState copyLockState(CDOLockState lockState)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @since 4.12
+   * @deprecated As of 4.15 no longer supported.
+   */
+  @Deprecated
+  public static CDOLockState copyLockState(CDOLockState lockState, Object lockedObject)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @deprecated As of 4.15 use {@link #convertLockState(LockState)}.
+   */
+  @Deprecated
+  public static CDOLockState createLockState(LockState<Object, ? extends CDOCommonView> lockState)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @deprecated As of 4.15 use {@link #createLockChangeInfo(CDOBranchPoint, CDOLockOwner, Collection, Collection)}.
+   */
+  @Deprecated
+  public static CDOLockChangeInfo createLockChangeInfo(long timestamp, CDOLockOwner lockOwner, CDOBranch branch, Operation op, LockType lockType,
+      CDOLockState[] newLockStates)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @deprecated As of 4.15 use {@link #createLockChangeInfo(CDOBranchPoint, CDOLockOwner, Collection, Collection)}.
+   */
+  @Deprecated
+  public static CDOLockChangeInfo createLockChangeInfo(long timestamp, CDOCommonView view, CDOBranch viewedBranch, Operation op, LockType lockType,
+      CDOLockState[] newLockStates)
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java
index 5352967..9156d48 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java
@@ -17,6 +17,7 @@
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * Manages all persistent aspects of durable CDO views such as {@link CDOBranchPoint branch point} and acquired locks.
@@ -198,8 +199,38 @@
       return (value & 4) != 0;
     }
 
+    /**
+     * @since 4.15
+     */
+    public void forEachLockType(Consumer<LockType> consumer)
+    {
+      if (consumer != null)
+      {
+        if (isRead())
+        {
+          consumer.accept(LockType.READ);
+        }
+
+        if (isWrite())
+        {
+          consumer.accept(LockType.WRITE);
+        }
+
+        if (isOption())
+        {
+          consumer.accept(LockType.OPTION);
+        }
+      }
+    }
+
     public LockGrade getUpdated(LockType type, boolean on)
     {
+      if (type == null && !on)
+      {
+        // Unlock all types.
+        return NONE;
+      }
+
       int mask = getMask(type);
 
       if (on)
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataInput.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataInput.java
index d1752f3..24d037b 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataInput.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataInput.java
@@ -20,6 +20,7 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
@@ -46,6 +47,8 @@
 import org.eclipse.emf.ecore.resource.ResourceSet;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Provides I/O methods for reading various CDO data types and concepts from streams.
@@ -172,6 +175,51 @@
   public CDOLockState readCDOLockState() throws IOException;
 
   /**
+   * @since 4.15
+   */
+  default List<CDOLockState> readCDOLockStates() throws IOException
+  {
+    List<CDOLockState> lockDeltas = new ArrayList<>();
+    for (;;)
+    {
+      CDOLockState lockState = readCDOLockState();
+      if (lockState == null)
+      {
+        break;
+      }
+
+      lockDeltas.add(lockState);
+    }
+
+    return lockDeltas;
+  }
+
+  /**
+   * @since 4.15
+   */
+  public CDOLockDelta readCDOLockDelta() throws IOException;
+
+  /**
+   * @since 4.15
+   */
+  default List<CDOLockDelta> readCDOLockDeltas() throws IOException
+  {
+    List<CDOLockDelta> lockDeltas = new ArrayList<>();
+    for (;;)
+    {
+      CDOLockDelta lockDelta = readCDOLockDelta();
+      if (lockDelta == null)
+      {
+        break;
+      }
+
+      lockDeltas.add(lockDelta);
+    }
+
+    return lockDeltas;
+  }
+
+  /**
    * @since 4.1
    */
   public LockArea readCDOLockArea() throws IOException;
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataOutput.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataOutput.java
index 7c4b8c8..a18b36f 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataOutput.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDODataOutput.java
@@ -21,6 +21,7 @@
 import org.eclipse.emf.cdo.common.id.CDOIDProvider;
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
@@ -48,7 +49,9 @@
 import org.eclipse.emf.ecore.EStructuralFeature;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Provides I/O methods for writing various CDO data types and concepts to streams.
@@ -189,6 +192,63 @@
   public void writeCDOLockState(CDOLockState lockState) throws IOException;
 
   /**
+   * @since 4.15
+   */
+  default void writeCDOLockStates(List<CDOLockState> lockStates, Predicate<CDOLockState> filter) throws IOException
+  {
+    if (filter != null)
+    {
+      for (CDOLockState lockState : lockStates)
+      {
+        if (filter.test(lockState))
+        {
+          writeCDOLockState(lockState);
+        }
+      }
+    }
+    else
+    {
+      for (CDOLockState state : lockStates)
+      {
+        writeCDOLockState(state);
+      }
+    }
+
+    writeCDOLockState(null);
+  }
+
+  /**
+   * @since 4.15
+   */
+  public void writeCDOLockDelta(CDOLockDelta lockDelta) throws IOException;
+
+  /**
+   * @since 4.15
+   */
+  default void writeCDOLockDeltas(List<CDOLockDelta> lockDeltas, Predicate<CDOLockDelta> filter) throws IOException
+  {
+    if (filter != null)
+    {
+      for (CDOLockDelta lockDelta : lockDeltas)
+      {
+        if (filter.test(lockDelta))
+        {
+          writeCDOLockDelta(lockDelta);
+        }
+      }
+    }
+    else
+    {
+      for (CDOLockDelta lockDelta : lockDeltas)
+      {
+        writeCDOLockDelta(lockDelta);
+      }
+    }
+
+    writeCDOLockDelta(null);
+  }
+
+  /**
    * @since 4.1
    */
   public void writeCDOLockOwner(CDOLockOwner lockOwner) throws IOException;
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDOProtocolConstants.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDOProtocolConstants.java
index 0de822d..22b7caf 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDOProtocolConstants.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/protocol/CDOProtocolConstants.java
@@ -415,6 +415,11 @@
 
   public static final int RELEASE_ALL_LOCKS = -1;
 
+  /**
+   * @since 4.15
+   */
+  public static final long DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT = -2;
+
   // //////////////////////////////////////////////////////////////////////
   // Remote Sessions
 
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockChangeInfoImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockChangeInfoImpl.java
index 3a63be6..546a5e5 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockChangeInfoImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockChangeInfoImpl.java
@@ -13,51 +13,52 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.branch.CDOBranchManager;
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
-import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.spi.common.branch.CDOBranchAdjustable;
+import org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockChangeInfo;
 
-import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+import org.eclipse.net4j.util.event.INotifier;
+import org.eclipse.net4j.util.event.Notifier;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * @author Caspar De Groot
  */
-public final class CDOLockChangeInfoImpl implements CDOLockChangeInfo, CDOBranchAdjustable
+public final class CDOLockChangeInfoImpl extends AbstractCDOLockChangeInfo implements CDOBranchAdjustable
 {
+  private static final INotifier SOURCE = new Notifier();
+
+  private static final long serialVersionUID = 1L;
+
   private CDOBranchPoint branchPoint;
 
   private final CDOLockOwner lockOwner;
 
-  private final Operation operation;
+  private final CDOLockDelta[] lockDeltas;
 
-  private final LockType lockType;
-
-  private final List<CDOLockState> lockStates;
+  private final CDOLockState[] lockStates;
 
   private final boolean isInvalidateAll;
 
-  public CDOLockChangeInfoImpl(CDOBranchPoint branchPoint, CDOLockOwner lockOwner, Operation operation, LockType lockType,
-      Collection<? extends CDOLockState> lockStates)
+  public CDOLockChangeInfoImpl(CDOBranchPoint branchPoint, CDOLockOwner lockOwner, Collection<CDOLockDelta> lockDeltas, Collection<CDOLockState> lockStates)
   {
+    super(SOURCE);
     this.branchPoint = branchPoint;
     this.lockOwner = lockOwner;
-    this.operation = operation;
-    this.lockType = lockType;
-    this.lockStates = Collections.unmodifiableList(lockStates instanceof List ? (List<? extends CDOLockState>)lockStates : new ArrayList<>(lockStates));
+    this.lockDeltas = lockDeltas.toArray(new CDOLockDelta[lockDeltas.size()]);
+    this.lockStates = lockStates.toArray(new CDOLockState[lockStates.size()]);
     isInvalidateAll = false;
   }
 
   public CDOLockChangeInfoImpl()
   {
+    super(null);
     lockOwner = null;
-    operation = null;
-    lockType = null;
+    lockDeltas = null;
     lockStates = null;
     isInvalidateAll = true;
   }
@@ -89,32 +90,19 @@
   }
 
   @Override
-  public Operation getOperation()
-  {
-    return operation;
-  }
-
-  @Override
-  public LockType getLockType()
-  {
-    return lockType;
-  }
-
-  @Override
   public CDOLockOwner getLockOwner()
   {
     return lockOwner;
   }
 
-  @Deprecated
   @Override
-  public CDOLockState[] getLockStates()
+  public CDOLockDelta[] getLockDeltas()
   {
-    return lockStates.toArray(new CDOLockState[lockStates.size()]);
+    return lockDeltas;
   }
 
   @Override
-  public List<CDOLockState> getNewLockStates()
+  public CDOLockState[] getLockStates()
   {
     return lockStates;
   }
@@ -131,16 +119,17 @@
     StringBuilder builder = new StringBuilder();
     builder.append("CDOLockChangeInfo[branchPoint=");
     builder.append(branchPoint);
-    builder.append(", operation=");
-    builder.append(operation);
-    builder.append(", lockType=");
-    builder.append(lockType == null ? "ALL" : lockType);
-    builder.append(", lockOwner=");
-    builder.append(lockOwner);
-    builder.append(", lockStates=");
-    builder.append(lockStates);
-    builder.append(", invalidateAll=");
-    builder.append(isInvalidateAll);
+
+    if (isInvalidateAll)
+    {
+      builder.append(", invalidateAll");
+    }
+    else
+    {
+      builder.append(", deltas=");
+      builder.append(Arrays.asList(lockDeltas));
+    }
+
     builder.append("]");
     return builder.toString();
   }
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockDeltaImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockDeltaImpl.java
new file mode 100644
index 0000000..4a0356e
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockDeltaImpl.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.internal.common.lock;
+
+import org.eclipse.emf.cdo.common.branch.CDOBranch;
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
+import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
+import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
+import org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockState;
+
+import org.eclipse.net4j.util.CheckUtil;
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+
+/**
+ * @author Eike Stepper
+ */
+public abstract class CDOLockDeltaImpl implements CDOLockDelta
+{
+  protected final Object target;
+
+  protected final CDOLockOwner oldOwner;
+
+  protected final CDOLockOwner newOwner;
+
+  private CDOLockDeltaImpl(Object target, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+  {
+    CheckUtil.checkArg(target, "target");
+    CheckUtil.checkArg(oldOwner != null || newOwner != null, "oldOwner != null || newOwner != null");
+
+    this.target = target;
+    this.oldOwner = oldOwner;
+    this.newOwner = newOwner;
+  }
+
+  @Override
+  public final Object getTarget()
+  {
+    return target;
+  }
+
+  @Override
+  public final CDOID getID()
+  {
+    return CDOLockUtil.getLockedObjectID(target);
+  }
+
+  @Override
+  public final CDOBranch getBranch()
+  {
+    return CDOLockUtil.getLockedObjectBranch(target);
+  }
+
+  @Override
+  public final CDOLockOwner getOldOwner()
+  {
+    return oldOwner;
+  }
+
+  @Override
+  public final CDOLockOwner getNewOwner()
+  {
+    return newOwner;
+  }
+
+  @Override
+  public final Kind getKind()
+  {
+    if (oldOwner == null)
+    {
+      return Kind.ADDED;
+    }
+
+    if (newOwner == null)
+    {
+      return Kind.REMOVED;
+    }
+
+    return Kind.REMAPPED;
+  }
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append(getKind());
+    builder.append('_');
+    builder.append(getType());
+    builder.append('[');
+    builder.append(target);
+
+    if (oldOwner != null)
+    {
+      builder.append(" - ");
+      AbstractCDOLockState.appendLockOwner(builder, oldOwner);
+    }
+
+    if (newOwner != null)
+    {
+      builder.append(" + ");
+      AbstractCDOLockState.appendLockOwner(builder, newOwner);
+    }
+
+    builder.append(']');
+    return builder.toString();
+  }
+
+  public static CDOLockDelta create(Object target, LockType type, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+  {
+    switch (type)
+    {
+    case READ:
+      return new Read(target, oldOwner, newOwner);
+
+    case WRITE:
+      return new Write(target, oldOwner, newOwner);
+
+    case OPTION:
+      return new Option(target, oldOwner, newOwner);
+
+    default:
+      throw new IllegalArgumentException("Illegal type: " + type);
+    }
+  }
+
+  public static CDOLockDelta createNull(Object target)
+  {
+    return new Null(target);
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Null implements CDOLockDelta
+  {
+    private final Object target;
+
+    public Null(Object target)
+    {
+      CheckUtil.checkArg(target, "target");
+      this.target = target;
+    }
+
+    @Override
+    public Object getTarget()
+    {
+      return target;
+    }
+
+    @Override
+    public final CDOID getID()
+    {
+      return CDOLockUtil.getLockedObjectID(target);
+    }
+
+    @Override
+    public final CDOBranch getBranch()
+    {
+      return CDOLockUtil.getLockedObjectBranch(target);
+    }
+
+    @Override
+    public LockType getType()
+    {
+      return null;
+    }
+
+    @Override
+    public CDOLockOwner getOldOwner()
+    {
+      return null;
+    }
+
+    @Override
+    public CDOLockOwner getNewOwner()
+    {
+      return null;
+    }
+
+    @Override
+    public Kind getKind()
+    {
+      return null;
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Read extends CDOLockDeltaImpl
+  {
+    public Read(Object target, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    {
+      super(target, oldOwner, newOwner);
+    }
+
+    @Override
+    public LockType getType()
+    {
+      return LockType.READ;
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Write extends CDOLockDeltaImpl
+  {
+    public Write(Object target, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    {
+      super(target, oldOwner, newOwner);
+    }
+
+    @Override
+    public LockType getType()
+    {
+      return LockType.WRITE;
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Option extends CDOLockDeltaImpl
+  {
+    public Option(Object target, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    {
+      super(target, oldOwner, newOwner);
+    }
+
+    @Override
+    public LockType getType()
+    {
+      return LockType.OPTION;
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockStateImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockStateImpl.java
index 7d5aca6..c1628fd 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockStateImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/lock/CDOLockStateImpl.java
@@ -10,33 +10,27 @@
  */
 package org.eclipse.emf.cdo.internal.common.lock;
 
-import org.eclipse.emf.cdo.common.id.CDOID;
-import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
-import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 import org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockState;
-import org.eclipse.emf.cdo.spi.common.lock.InternalCDOLockState;
 
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
  * @author Caspar De Groot
  */
-public final class CDOLockStateImpl extends AbstractCDOLockState implements InternalCDOLockState
+public final class CDOLockStateImpl extends AbstractCDOLockState
 {
   private static final Set<CDOLockOwner> NO_LOCK_OWNERS = Collections.emptySet();
 
   private static final Class<CDOLockOwner[]> ARRAY_CLASS = CDOLockOwner[].class;
 
-  private final Object lockedObject;
-
   private Object readLockOwners;
 
   private CDOLockOwner writeLockOwner;
@@ -45,15 +39,7 @@
 
   public CDOLockStateImpl(Object lockedObject)
   {
-    assert lockedObject instanceof CDOID || lockedObject instanceof CDOIDAndBranch : "lockedObject is of wrong type";
-    assert !CDOIDUtil.isNull(CDOLockUtil.getLockedObjectID(lockedObject)) : "lockedObject is null";
-    this.lockedObject = lockedObject;
-  }
-
-  @Override
-  public Object getLockedObject()
-  {
-    return lockedObject;
+    super(lockedObject);
   }
 
   @Override
@@ -92,23 +78,23 @@
   }
 
   @Override
-  public boolean isLocked(LockType lockType, CDOLockOwner lockOwner, boolean others)
+  public boolean isLocked(LockType type, CDOLockOwner by, boolean others)
   {
-    if (lockType == null)
+    if (type == null)
     {
-      return isReadLocked(lockOwner, others) || isWriteLocked(lockOwner, others) || isOptionLocked(lockOwner, others);
+      return isReadLocked(by, others) || isWriteLocked(by, others) || isOptionLocked(by, others);
     }
 
-    switch (lockType)
+    switch (type)
     {
     case READ:
-      return isReadLocked(lockOwner, others);
+      return isReadLocked(by, others);
 
     case WRITE:
-      return isWriteLocked(lockOwner, others);
+      return isWriteLocked(by, others);
 
     case OPTION:
-      return isOptionLocked(lockOwner, others);
+      return isOptionLocked(by, others);
     }
 
     return false;
@@ -171,46 +157,75 @@
   }
 
   @Override
-  public void addReadLockOwner(CDOLockOwner lockOwner)
+  protected CDOLockDelta addReadOwner(CDOLockOwner owner)
   {
     if (readLockOwners == null)
     {
-      readLockOwners = lockOwner;
-      return;
+      readLockOwners = owner;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, null, owner);
     }
 
     if (readLockOwners.getClass() == ARRAY_CLASS)
     {
       CDOLockOwner[] owners = (CDOLockOwner[])readLockOwners;
-      if (CDOLockUtil.indexOf(owners, lockOwner) == -1)
+      if (CDOLockUtil.indexOf(owners, owner) == -1)
       {
         int oldLength = owners.length;
         readLockOwners = new CDOLockOwner[oldLength + 1];
         System.arraycopy(owners, 0, readLockOwners, 0, oldLength);
-        ((CDOLockOwner[])readLockOwners)[oldLength] = lockOwner;
+        ((CDOLockOwner[])readLockOwners)[oldLength] = owner;
+
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, null, owner);
       }
 
-      return;
+      return null;
     }
 
-    if (readLockOwners != lockOwner)
+    if (readLockOwners != owner)
     {
-      readLockOwners = new CDOLockOwner[] { (CDOLockOwner)readLockOwners, lockOwner };
+      readLockOwners = new CDOLockOwner[] { (CDOLockOwner)readLockOwners, owner };
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, null, owner);
     }
+
+    return null;
   }
 
   @Override
-  public boolean removeReadLockOwner(CDOLockOwner lockOwner)
+  protected CDOLockDelta addWriteOwner(CDOLockOwner owner)
+  {
+    if (writeLockOwner == null)
+    {
+      writeLockOwner = owner;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.WRITE, null, owner);
+    }
+
+    return null;
+  }
+
+  @Override
+  protected CDOLockDelta addOptionOwner(CDOLockOwner owner)
+  {
+    if (writeOptionOwner == null)
+    {
+      writeOptionOwner = owner;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.OPTION, null, owner);
+    }
+
+    return null;
+  }
+
+  @Override
+  protected CDOLockDelta removeReadOwner(CDOLockOwner owner)
   {
     if (readLockOwners == null)
     {
-      return false;
+      return null;
     }
 
     if (readLockOwners.getClass() == ARRAY_CLASS)
     {
       CDOLockOwner[] owners = (CDOLockOwner[])readLockOwners;
-      int index = CDOLockUtil.indexOf(owners, lockOwner);
+      int index = CDOLockUtil.indexOf(owners, owner);
       if (index != -1)
       {
         int oldLength = owners.length;
@@ -227,140 +242,81 @@
           System.arraycopy(owners, index + 1, readLockOwners, index, rest);
         }
 
-        return true;
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, owner, null);
       }
 
-      return false;
+      return null;
     }
 
-    if (readLockOwners == lockOwner)
+    if (readLockOwners == owner)
     {
       readLockOwners = null;
-      return true;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, owner, null);
     }
 
-    return false;
+    return null;
   }
 
   @Override
-  public void setWriteLockOwner(CDOLockOwner lockOwner)
+  protected CDOLockDelta removeWriteOwner(CDOLockOwner owner)
   {
-    writeLockOwner = lockOwner;
-  }
-
-  @Override
-  public void setWriteOptionOwner(CDOLockOwner lockOwner)
-  {
-    writeOptionOwner = lockOwner;
-  }
-
-  @Override
-  public boolean removeOwner(CDOLockOwner lockOwner)
-  {
-    boolean changed = removeReadLockOwner(lockOwner);
-
-    if (writeLockOwner == lockOwner)
+    if (writeLockOwner == owner)
     {
       writeLockOwner = null;
-      changed = true;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.WRITE, owner, null);
     }
 
-    if (writeOptionOwner == lockOwner)
-    {
-      writeOptionOwner = null;
-      changed = true;
-    }
-
-    return changed;
+    return null;
   }
 
   @Override
-  public boolean remapOwner(CDOLockOwner oldLockOwner, CDOLockOwner newLockOwner)
+  protected CDOLockDelta removeOptionOwner(CDOLockOwner owner)
   {
-    boolean changed = false;
+    if (writeOptionOwner == owner)
+    {
+      writeOptionOwner = null;
+      return CDOLockUtil.createLockDelta(lockedObject, LockType.OPTION, owner, null);
+    }
+
+    return null;
+  }
+
+  @Override
+  public CDOLockDelta[] remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+  {
+    List<CDOLockDelta> deltas = null;
 
     if (readLockOwners != null)
     {
-      if (readLockOwners == oldLockOwner)
+      if (readLockOwners == oldOwner)
       {
-        readLockOwners = newLockOwner;
-        changed = true;
+        readLockOwners = newOwner;
+        deltas = CDOLockUtil.appendLockDelta(deltas, lockedObject, LockType.READ, oldOwner, newOwner);
       }
-
-      if (readLockOwners.getClass() == ARRAY_CLASS)
+      else if (readLockOwners.getClass() == ARRAY_CLASS)
       {
         CDOLockOwner[] owners = (CDOLockOwner[])readLockOwners;
-        int index = CDOLockUtil.indexOf(owners, oldLockOwner);
+        int index = CDOLockUtil.indexOf(owners, oldOwner);
         if (index != -1)
         {
-          ((CDOLockOwner[])readLockOwners)[index] = newLockOwner;
-          changed = true;
+          ((CDOLockOwner[])readLockOwners)[index] = newOwner;
+          deltas = CDOLockUtil.appendLockDelta(deltas, lockedObject, LockType.READ, oldOwner, newOwner);
         }
       }
     }
 
-    if (writeLockOwner == oldLockOwner)
+    if (writeLockOwner == oldOwner)
     {
-      writeLockOwner = newLockOwner;
-      changed = true;
+      writeLockOwner = newOwner;
+      deltas = CDOLockUtil.appendLockDelta(deltas, lockedObject, LockType.WRITE, oldOwner, newOwner);
     }
 
-    if (writeOptionOwner == oldLockOwner)
+    if (writeOptionOwner == oldOwner)
     {
-      writeOptionOwner = newLockOwner;
-      changed = true;
+      writeOptionOwner = newOwner;
+      deltas = CDOLockUtil.appendLockDelta(deltas, lockedObject, LockType.OPTION, oldOwner, newOwner);
     }
 
-    return changed;
-  }
-
-  public CDOLockStateImpl copy()
-  {
-    return copy(lockedObject);
-  }
-
-  public CDOLockStateImpl copy(Object lockedObject)
-  {
-    CDOLockStateImpl newLockState = new CDOLockStateImpl(lockedObject);
-    copyReadLockOwners(newLockState);
-    newLockState.writeLockOwner = writeLockOwner;
-    newLockState.writeOptionOwner = writeOptionOwner;
-    return newLockState;
-  }
-
-  private void copyReadLockOwners(CDOLockStateImpl target)
-  {
-    if (readLockOwners != null && readLockOwners.getClass() == ARRAY_CLASS)
-    {
-      CDOLockOwner[] owners = (CDOLockOwner[])readLockOwners;
-      target.readLockOwners = Arrays.copyOf(owners, owners.length);
-      return;
-    }
-
-    target.readLockOwners = readLockOwners;
-  }
-
-  @Override
-  @Deprecated
-  public void updateFrom(Object object, CDOLockState source)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public void updateFrom(CDOLockState source)
-  {
-    CDOLockStateImpl lockState = (CDOLockStateImpl)source;
-    lockState.copyReadLockOwners(this);
-
-    writeLockOwner = lockState.writeLockOwner;
-    writeOptionOwner = lockState.writeOptionOwner;
-  }
-
-  @Deprecated
-  @Override
-  public void dispose()
-  {
-    throw new UnsupportedOperationException();
+    return CDOLockUtil.toArray(deltas);
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionManagerImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionManagerImpl.java
index 83903e1..721065d 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionManagerImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionManagerImpl.java
@@ -508,10 +508,8 @@
 
     try
     {
-      @SuppressWarnings("deprecation")
-      List<RevisionInfo> additionalRevisionInfos = revisionLoader instanceof RevisionLoader3
-          ? ((RevisionLoader3)revisionLoader).loadRevisions(infosToLoad, branchPoint, referenceChunk, prefetchDepth, prefetchLockStates)
-          : revisionLoader.loadRevisions(infosToLoad, branchPoint, referenceChunk, prefetchDepth);
+      List<RevisionInfo> additionalRevisionInfos = ((RevisionLoader3)revisionLoader).loadRevisions(infosToLoad, branchPoint, referenceChunk, prefetchDepth,
+          prefetchLockStates);
 
       if (additionalRevisionInfos != null)
       {
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockChangeInfo.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockChangeInfo.java
new file mode 100644
index 0000000..8d88b55
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockChangeInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.spi.common.lock;
+
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
+
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+import org.eclipse.net4j.util.event.Event;
+import org.eclipse.net4j.util.event.INotifier;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Eike Stepper
+ * @since 4.15
+ */
+public abstract class AbstractCDOLockChangeInfo extends Event implements CDOLockChangeInfo
+{
+  private static final long serialVersionUID = 1L;
+
+  public AbstractCDOLockChangeInfo(INotifier notifier)
+  {
+    super(notifier);
+  }
+
+  @Override
+  public final Set<Operation> getOperations()
+  {
+    Set<Operation> result = new HashSet<>();
+
+    for (CDOLockDelta delta : getLockDeltas())
+    {
+      CDOLockDelta.Kind kind = delta.getKind();
+
+      switch (kind)
+      {
+      case ADDED:
+        result.add(Operation.LOCK);
+        break;
+
+      case REMOVED:
+        result.add(Operation.UNLOCK);
+        break;
+
+      case REMAPPED:
+        // Do nothing.
+        break;
+
+      default:
+        throw new AssertionError("Invalid kind: " + kind);
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public final Set<LockType> getLockTypes()
+  {
+    Set<LockType> result = new HashSet<>();
+
+    for (CDOLockDelta delta : getLockDeltas())
+    {
+      LockType type = delta.getType();
+      result.add(type);
+    }
+
+    return result;
+  }
+
+  @Override
+  public final Set<CDOID> getAffectedIDs()
+  {
+    Set<CDOID> ids = new HashSet<>();
+
+    for (CDOLockDelta delta : getLockDeltas())
+    {
+      CDOID id = delta.getID();
+      ids.add(id);
+    }
+
+    return ids;
+  }
+
+  @Override
+  @Deprecated
+  public final Operation getOperation()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final LockType getLockType()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java
index 13c48fc..a16e854 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/AbstractCDOLockState.java
@@ -12,12 +12,17 @@
 
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
+import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 
 import org.eclipse.net4j.util.ObjectUtil;
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -26,32 +31,56 @@
  * @since 4.15
  * @noextend This class is not intended to be extended by clients.
  */
-public abstract class AbstractCDOLockState implements CDOLockState
+public abstract class AbstractCDOLockState implements InternalCDOLockState
 {
-  public AbstractCDOLockState()
+  protected Object lockedObject;
+
+  public AbstractCDOLockState(Object lockedObject)
   {
+    assert lockedObject instanceof CDOID || lockedObject instanceof CDOIDAndBranch : "lockedObject is of wrong type";
+    assert !CDOIDUtil.isNull(CDOLockUtil.getLockedObjectID(lockedObject)) : "lockedObject is null";
+    this.lockedObject = lockedObject;
   }
 
   @Override
-  public CDOBranch getBranch()
+  public final Object getLockedObject()
   {
-    return CDOLockUtil.getLockedObjectBranch(getLockedObject());
+    return lockedObject;
   }
 
   @Override
-  public CDOID getID()
+  public final CDOID getID()
   {
-    return CDOLockUtil.getLockedObjectID(getLockedObject());
+    return CDOLockUtil.getLockedObjectID(lockedObject);
   }
 
   @Override
-  public int hashCode()
+  public void remapID(CDOID newID)
   {
-    return Objects.hashCode(getLockedObject());
+    if (lockedObject instanceof CDOID)
+    {
+      lockedObject = newID;
+    }
+    else
+    {
+      lockedObject = CDOIDUtil.createIDAndBranch(newID, ((CDOIDAndBranch)lockedObject).getBranch());
+    }
   }
 
   @Override
-  public boolean equals(Object obj)
+  public final CDOBranch getBranch()
+  {
+    return CDOLockUtil.getLockedObjectBranch(lockedObject);
+  }
+
+  @Override
+  public final int hashCode()
+  {
+    return Objects.hashCode(lockedObject);
+  }
+
+  @Override
+  public final boolean equals(Object obj)
   {
     if (this == obj)
     {
@@ -69,7 +98,7 @@
     }
 
     CDOLockState other = (CDOLockState)obj;
-    if (!getLockedObject().equals(other.getLockedObject()))
+    if (!lockedObject.equals(other.getLockedObject()))
     {
       return false;
     }
@@ -93,10 +122,10 @@
   }
 
   @Override
-  public String toString()
+  public final String toString()
   {
     StringBuilder builder = new StringBuilder("CDOLockState[lockedObject=");
-    builder.append(getLockedObject());
+    builder.append(lockedObject);
 
     Set<CDOLockOwner> readLockOwners = getReadLockOwners();
     builder.append(", readLockOwners=");
@@ -106,7 +135,7 @@
       boolean first = true;
       for (CDOLockOwner lockOwner : readLockOwners)
       {
-        appendLockOwner(builder, lockOwner, first);
+        AbstractCDOLockState.appendLockOwner(builder, lockOwner, first);
         first = false;
       }
     }
@@ -127,16 +156,137 @@
     return builder.toString();
   }
 
-  private static void appendLockOwner(StringBuilder builder, CDOLockOwner lockOwner, boolean first)
+  @Override
+  public final CDOLockDelta addOwner(CDOLockOwner owner, LockType type)
+  {
+    switch (type)
+    {
+    case READ:
+      return addReadOwner(owner);
+
+    case WRITE:
+      return addWriteOwner(owner);
+
+    case OPTION:
+      return addOptionOwner(owner);
+
+    default:
+      throw new IllegalArgumentException("Illegal type: " + type);
+    }
+  }
+
+  @Override
+  public final CDOLockDelta removeOwner(CDOLockOwner owner, LockType type)
+  {
+    switch (type)
+    {
+    case READ:
+      return removeReadOwner(owner);
+
+    case WRITE:
+      return removeWriteOwner(owner);
+
+    case OPTION:
+      return removeOptionOwner(owner);
+
+    default:
+      throw new IllegalArgumentException("Illegal type: " + type);
+    }
+  }
+
+  @Override
+  public final CDOLockDelta[] clearOwner(CDOLockOwner owner)
+  {
+    List<CDOLockDelta> deltas = null;
+    deltas = CDOLockUtil.appendLockDelta(deltas, removeReadOwner(owner));
+    deltas = CDOLockUtil.appendLockDelta(deltas, removeWriteOwner(owner));
+    deltas = CDOLockUtil.appendLockDelta(deltas, removeOptionOwner(owner));
+    return CDOLockUtil.toArray(deltas);
+  }
+
+  protected abstract CDOLockDelta addReadOwner(CDOLockOwner owner);
+
+  protected abstract CDOLockDelta addWriteOwner(CDOLockOwner owner);
+
+  protected abstract CDOLockDelta addOptionOwner(CDOLockOwner owner);
+
+  protected abstract CDOLockDelta removeReadOwner(CDOLockOwner owner);
+
+  protected abstract CDOLockDelta removeWriteOwner(CDOLockOwner owner);
+
+  protected abstract CDOLockDelta removeOptionOwner(CDOLockOwner owner);
+
+  @Override
+  @Deprecated
+  public final void addReadLockOwner(CDOLockOwner owner)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final boolean removeReadLockOwner(CDOLockOwner owner)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final void setWriteLockOwner(CDOLockOwner owner)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final void setWriteOptionOwner(CDOLockOwner owner)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final boolean removeOwner(CDOLockOwner owner)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final void updateFrom(CDOLockState source)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final void updateFrom(Object object, CDOLockState source)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public final void dispose()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  public static void appendLockOwner(StringBuilder builder, CDOLockOwner lockOwner, boolean first)
   {
     if (!first)
     {
       builder.append("+");
     }
 
-    String durableLockingID = lockOwner.getDurableLockingID();
     builder.append('[');
+    appendLockOwner(builder, lockOwner);
+    builder.append(']');
+  }
 
+  public static void appendLockOwner(StringBuilder builder, CDOLockOwner lockOwner)
+  {
+    String durableLockingID = lockOwner.getDurableLockingID();
     if (durableLockingID != null)
     {
       builder.append(durableLockingID);
@@ -147,7 +297,5 @@
       builder.append(':');
       builder.append(lockOwner.getViewID());
     }
-
-    builder.append(']');
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/InternalCDOLockState.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/InternalCDOLockState.java
index 6ffdbb4..3f45531 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/InternalCDOLockState.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/lock/InternalCDOLockState.java
@@ -10,9 +10,13 @@
  */
 package org.eclipse.emf.cdo.spi.common.lock;
 
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+
 /**
  * If the meaning of this type isn't clear, there really should be more of a description here...
  *
@@ -30,43 +34,83 @@
   @Deprecated
   public static final CDOLockState UNLOCKED = null;
 
-  public void addReadLockOwner(CDOLockOwner lockOwner);
-
-  public boolean removeReadLockOwner(CDOLockOwner lockOwner);
-
-  public void setWriteLockOwner(CDOLockOwner lockOwner);
-
-  public void setWriteOptionOwner(CDOLockOwner lockOwner);
-
   /**
-   * @since 4.4
+   * @since 4.15
    */
-  public boolean removeOwner(CDOLockOwner lockOwner);
+  public CDOLockDelta addOwner(CDOLockOwner owner, LockType type);
 
   /**
    * @since 4.15
    */
-  public boolean remapOwner(CDOLockOwner oldLockOwner, CDOLockOwner newLockOwner);
+  public CDOLockDelta removeOwner(CDOLockOwner owner, LockType type);
+
+  /**
+   * @since 4.15
+   */
+  public CDOLockDelta[] clearOwner(CDOLockOwner owner);
+
+  /**
+   * @since 4.15
+   */
+  public CDOLockDelta[] remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner);
+
+  /**
+   * @since 4.15
+   */
+  public void remapID(CDOID newID);
+
+  /**
+   * @deprecated As of 4.15 use {@link #addOwner(CDOLockOwner, LockType) addOwner(owner, LockType.READ)}.
+   */
+  @Deprecated
+  public void addReadLockOwner(CDOLockOwner owner);
+
+  /**
+   * @deprecated As of 4.15 use {@link #removeOwner(CDOLockOwner, LockType) removeOwner(owner, LockType.READ)}.
+   */
+  @Deprecated
+  public boolean removeReadLockOwner(CDOLockOwner owner);
+
+  /**
+   * @deprecated As of 4.15 use {@link #addOwner(CDOLockOwner, LockType) addOwner(owner, LockType.WRITE)}
+   * or {@link #removeOwner(CDOLockOwner, LockType) removeOwner(owner, LockType.WRITE)}.
+   */
+  @Deprecated
+  public void setWriteLockOwner(CDOLockOwner owner);
+
+  /**
+   * @deprecated As of 4.15 use {@link #addOwner(CDOLockOwner, LockType) addOwner(owner, LockType.OPTION)}.
+   * or {@link #removeOwner(CDOLockOwner, LockType) removeOwner(owner, LockType.OPTION)}.
+   */
+  @Deprecated
+  public void setWriteOptionOwner(CDOLockOwner owner);
+
+  /**
+   * @since 4.4
+   * @deprecated As of 4.16 use {@link #clearOwner(CDOLockOwner)}.
+   */
+  @Deprecated
+  public boolean removeOwner(CDOLockOwner owner);
 
   /**
    * Update the {@link CDOLockOwner lockOwners} of this lock state from the one passed in.
    *
    * @since 4.5
+   * @deprecated As of 4.15 not supported anymore.
    */
+  @Deprecated
   public void updateFrom(CDOLockState source);
 
   /**
    * @since 4.2
-   * @deprecated As of 4.5 use {@link InternalCDOLockState#updateFrom(CDOLockState)} instead.
-   * The lockedObject field cannot be changed because it is used to compute the hash code.
-   * Instantiate a new {@link CDOLockState} object if you want to update the lockedObject field.
+   * @deprecated As of 4.5 not supported anymore.
    */
   @Deprecated
   public void updateFrom(Object object, CDOLockState source);
 
   /**
    * @since 4.2
-   * @deprecated As of 4.15 no longer used.
+   * @deprecated As of 4.15 not supported anymore.
    */
   @Deprecated
   public void dispose();
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataInputImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataInputImpl.java
index 4dd4d6c..c675aea 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataInputImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataInputImpl.java
@@ -26,7 +26,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLobStore;
 import org.eclipse.emf.cdo.common.lob.CDOLobUtil;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
-import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -338,18 +338,10 @@
 
     CDOBranchPoint branchPoint = readCDOBranchPoint();
     CDOLockOwner lockOwner = readCDOLockOwner();
-    Operation operation = readEnum(Operation.class);
-    LockType lockType = readCDOLockType();
+    List<CDOLockDelta> lockDeltas = readCDOLockDeltas();
+    List<CDOLockState> lockStates = readCDOLockStates();
 
-    int n = readXInt();
-    List<CDOLockState> lockStates = new ArrayList<>(n);
-
-    for (int i = 0; i < n; i++)
-    {
-      lockStates.add(readCDOLockState());
-    }
-
-    return CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, operation, lockType, lockStates);
+    return CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, lockDeltas, lockStates);
   }
 
   @Override
@@ -377,6 +369,11 @@
   public CDOLockOwner readCDOLockOwner() throws IOException
   {
     int session = readXInt();
+    if (session == CDOLockUtil.DURABLE_SESSION_ID - 1)
+    {
+      return null;
+    }
+
     int view = readXInt();
     String lockAreaID = readString();
     return CDOLockUtil.createLockOwner(session, view, lockAreaID);
@@ -411,27 +408,57 @@
     for (int i = 0; i < nReadLockOwners; i++)
     {
       CDOLockOwner lockOwner = readCDOLockOwner();
-      lockState.addReadLockOwner(lockOwner);
+      lockState.addOwner(lockOwner, LockType.READ);
     }
 
     boolean hasWriteLock = readBoolean();
     if (hasWriteLock)
     {
       CDOLockOwner lockOwner = readCDOLockOwner();
-      lockState.setWriteLockOwner(lockOwner);
+      lockState.addOwner(lockOwner, LockType.WRITE);
     }
 
     boolean hasWriteOption = readBoolean();
     if (hasWriteOption)
     {
       CDOLockOwner lockOwner = readCDOLockOwner();
-      lockState.setWriteOptionOwner(lockOwner);
+      lockState.addOwner(lockOwner, LockType.OPTION);
     }
 
     return lockState;
   }
 
   @Override
+  public CDOLockDelta readCDOLockDelta() throws IOException
+  {
+    byte opcode = readByte();
+    if (opcode == -1)
+    {
+      return null;
+    }
+
+    Object target;
+    CDOID id = readCDOID();
+
+    if (opcode >= 10)
+    {
+      opcode -= 10;
+      CDOBranch branch = readCDOBranch();
+      target = CDOIDUtil.createIDAndBranch(id, branch);
+    }
+    else
+    {
+      target = id;
+    }
+
+    LockType type = LockType.values()[opcode];
+    CDOLockOwner oldOwner = readCDOLockOwner();
+    CDOLockOwner newOwner = readCDOLockOwner();
+
+    return CDOLockUtil.createLockDelta(target, type, oldOwner, newOwner);
+  }
+
+  @Override
   public LockType readCDOLockType() throws IOException
   {
     return readEnum(LockType.class);
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataOutputImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataOutputImpl.java
index eaa5ee9..ef42f27 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataOutputImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/protocol/CDODataOutputImpl.java
@@ -21,6 +21,7 @@
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -67,9 +68,8 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -298,45 +298,14 @@
     if (lockChangeInfo.isInvalidateAll())
     {
       writeBoolean(true);
+      return;
     }
-    else
-    {
-      writeBoolean(false);
-      writeCDOBranchPoint(lockChangeInfo);
-      writeCDOLockOwner(lockChangeInfo.getLockOwner());
-      writeEnum(lockChangeInfo.getOperation());
-      writeCDOLockType(lockChangeInfo.getLockType());
 
-      Collection<CDOLockState> lockStates = lockChangeInfo.getNewLockStates();
-
-      if (filter != null)
-      {
-        List<CDOLockState> filtered = new ArrayList<>(lockStates.size());
-        for (CDOLockState lockState : lockStates)
-        {
-          Object lockedObject = lockState.getLockedObject();
-          CDOID id = CDOLockUtil.getLockedObjectID(lockedObject);
-          if (filter.contains(id))
-          {
-            filtered.add(lockState);
-          }
-        }
-
-        writeXInt(filtered.size());
-        for (CDOLockState lockState : filtered)
-        {
-          writeCDOLockState(lockState);
-        }
-      }
-      else
-      {
-        writeXInt(lockStates.size());
-        for (CDOLockState lockState : lockStates)
-        {
-          writeCDOLockState(lockState);
-        }
-      }
-    }
+    writeBoolean(false);
+    writeCDOBranchPoint(lockChangeInfo);
+    writeCDOLockOwner(lockChangeInfo.getLockOwner());
+    writeCDOLockDeltas(Arrays.asList(lockChangeInfo.getLockDeltas()), filter == null ? null : delta -> filter.contains(delta.getID()));
+    writeCDOLockStates(Arrays.asList(lockChangeInfo.getLockStates()), filter == null ? null : state -> filter.contains(state.getID()));
   }
 
   @Override
@@ -361,9 +330,62 @@
   @Override
   public void writeCDOLockOwner(CDOLockOwner lockOwner) throws IOException
   {
-    writeXInt(lockOwner.getSessionID());
-    writeXInt(lockOwner.getViewID());
-    writeString(lockOwner.getDurableLockingID());
+    if (lockOwner == null)
+    {
+      writeXInt(CDOLockUtil.DURABLE_SESSION_ID - 1);
+    }
+    else
+    {
+      writeXInt(lockOwner.getSessionID());
+      writeXInt(lockOwner.getViewID());
+      writeString(lockOwner.getDurableLockingID());
+    }
+  }
+
+  @Override
+  public void writeCDOLockDelta(CDOLockDelta lockDelta) throws IOException
+  {
+    if (lockDelta == null)
+    {
+      writeByte(-1);
+      return;
+    }
+
+    byte opcode = 0;
+    CDOID id;
+    CDOBranch branch;
+
+    Object target = lockDelta.getTarget();
+    if (target instanceof CDOIDAndBranch)
+    {
+      CDOIDAndBranch idAndBranch = (CDOIDAndBranch)target;
+      id = idAndBranch.getID();
+      branch = idAndBranch.getBranch();
+      opcode += 10;
+    }
+    else if (target instanceof CDOID)
+    {
+      id = (CDOID)target;
+      branch = null;
+    }
+    else
+    {
+      throw new AssertionError("Unexpected type: " + target.getClass().getSimpleName());
+    }
+
+    LockType type = lockDelta.getType();
+    opcode += type.ordinal();
+
+    writeByte(opcode);
+    writeCDOID(id);
+
+    if (opcode >= 10)
+    {
+      writeCDOBranch(branch);
+    }
+
+    writeCDOLockOwner(lockDelta.getOldOwner());
+    writeCDOLockOwner(lockDelta.getNewOwner());
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/revision/InternalCDORevisionManager.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/revision/InternalCDORevisionManager.java
index 5646f16..d12845c 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/revision/InternalCDORevisionManager.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/spi/common/revision/InternalCDORevisionManager.java
@@ -125,18 +125,18 @@
    */
   public interface RevisionLoader
   {
-    /**
-     * @deprecated As of 4.15 use {@link RevisionLoader3#loadRevisions(List, CDOBranchPoint, int, int, boolean)}.
-     */
-    @Deprecated
-    public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth);
-
     public InternalCDORevision loadRevisionByVersion(CDOID id, CDOBranchVersion branchVersion, int referenceChunk);
 
     /**
      * @since 4.3
      */
     public void handleRevisions(EClass eClass, CDOBranch branch, boolean exactBranch, long timeStamp, boolean exactTime, CDORevisionHandler handler);
+
+    /**
+     * @deprecated As of 4.15 use {@link RevisionLoader3#loadRevisions(List, CDOBranchPoint, int, int, boolean)}.
+     */
+    @Deprecated
+    public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth);
   }
 
   /**
diff --git a/plugins/org.eclipse.emf.cdo.dawn/src/org/eclipse/emf/cdo/dawn/notifications/BasicDawnLockingHandler.java b/plugins/org.eclipse.emf.cdo.dawn/src/org/eclipse/emf/cdo/dawn/notifications/BasicDawnLockingHandler.java
index 24ef218..daff4cf 100644
--- a/plugins/org.eclipse.emf.cdo.dawn/src/org/eclipse/emf/cdo/dawn/notifications/BasicDawnLockingHandler.java
+++ b/plugins/org.eclipse.emf.cdo.dawn/src/org/eclipse/emf/cdo/dawn/notifications/BasicDawnLockingHandler.java
@@ -12,15 +12,13 @@
 
 import org.eclipse.emf.cdo.CDOObject;
 import org.eclipse.emf.cdo.common.id.CDOID;
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
-import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.dawn.editors.IDawnEditor;
 import org.eclipse.emf.cdo.dawn.editors.IDawnEditorSupport;
 import org.eclipse.emf.cdo.dawn.spi.DawnState;
 import org.eclipse.emf.cdo.view.CDOView;
 import org.eclipse.emf.cdo.view.CDOViewLocksChangedEvent;
 
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -38,31 +36,12 @@
   @Override
   public void handleLocksChangedEvent(CDOViewLocksChangedEvent event)
   {
-    CDOViewLocksChangedEvent lockEvent = event;
-
-    Collection<CDOLockState> lockStates = lockEvent.getNewLockStates();
-
     Map<Object, DawnState> changedObjects = new HashMap<>();
+    CDOView view = editor.getDawnEditorSupport().getView();
 
-    for (CDOLockState state : lockStates)
+    for (CDOLockDelta lockDelta : event.getLockDeltas())
     {
-      Object lockedObject = state.getLockedObject();
-
-      CDOView view = editor.getDawnEditorSupport().getView();
-      CDOID id;
-      if (lockedObject instanceof CDOID)
-      {
-        id = (CDOID)lockedObject;
-      }
-      else if (lockedObject instanceof CDOIDAndBranch)
-      {
-        id = ((CDOIDAndBranch)lockedObject).getID();
-      }
-      else
-      {
-        throw new RuntimeException("Unexpected object type: " + lockedObject);
-      }
-
+      CDOID id = lockDelta.getID();
       if (id != null)
       {
         CDOObject object = view.getObject(id);
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/RecoveringCDOSessionImpl.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/RecoveringCDOSessionImpl.java
index 7ec5861..2949938 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/RecoveringCDOSessionImpl.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/RecoveringCDOSessionImpl.java
@@ -23,6 +23,7 @@
 import org.eclipse.emf.cdo.transaction.CDOTransaction;
 
 import org.eclipse.emf.internal.cdo.view.CDOViewImpl;
+import org.eclipse.emf.internal.cdo.view.CDOViewImpl.DurableLockProcessor;
 
 import org.eclipse.net4j.Net4jUtil;
 import org.eclipse.net4j.connector.IConnector;
@@ -320,6 +321,8 @@
 
     private final String durableLockingID;
 
+    private final DurableLockProcessor durableLockProcessor;
+
     private final CDOBranchPoint branchPoint;
 
     private final CDOViewImpl.OptionsImpl options;
@@ -333,10 +336,12 @@
       if (durableLockingID == null)
       {
         branchPoint = CDOBranchUtil.copyBranchPoint(view);
+        durableLockProcessor = null;
       }
       else
       {
         branchPoint = null;
+        durableLockProcessor = ((CDOViewImpl)view).createDurableLockProcessor();
       }
 
       options = (CDOViewImpl.OptionsImpl)view.options();
@@ -347,7 +352,8 @@
     {
       if (durableLockingID != null)
       {
-        sessionProtocol.openView(viewID, !transaction, durableLockingID);
+        sessionProtocol.openView(viewID, !transaction, durableLockingID, durableLockProcessor);
+        durableLockProcessor.run();
       }
       else
       {
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
index 1d993af..c91f576 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
@@ -28,6 +28,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lob.CDOLobInfo;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
@@ -86,6 +87,7 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -197,20 +199,6 @@
   }
 
   @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void renameBranch(int branchID, String oldName, String newName)
   {
     send(new RenameBranchRequest(this, branchID, oldName, newName));
@@ -246,13 +234,6 @@
     return send(new LoadChunkRequest(this, revision, feature, accessIndex, fetchIndex, fromIndex, toIndex));
   }
 
-  @Deprecated
-  @Override
-  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
-  {
-    throw new UnsupportedOperationException();
-  }
-
   @Override
   public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth,
       boolean prefetchLockStates)
@@ -286,9 +267,9 @@
   }
 
   @Override
-  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID)
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID, BiConsumer<CDOID, LockGrade> consumer)
   {
-    return send(new OpenViewRequest(this, viewID, readOnly, durableLockingID));
+    return send(new OpenViewRequest(this, viewID, readOnly, durableLockingID, consumer));
   }
 
   @Override
@@ -330,14 +311,6 @@
   }
 
   @Override
-  @Deprecated
-  public LockObjectsResult lockObjects(List<InternalCDORevision> revisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
-      throws InterruptedException
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public LockObjectsResult lockObjects2(List<CDORevisionKey> revisionKeys, int viewID, CDOBranch viewedBranch, LockType lockType, boolean recursive,
       long timeout) throws InterruptedException
   {
@@ -480,28 +453,12 @@
   }
 
   @Override
-  @Deprecated
-  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
-      Collection<CDOLob<?>> lobs, OMMonitor monitor)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public CommitTransactionResult commitTransaction(InternalCDOCommitContext context, OMMonitor monitor)
   {
     return send(new CommitTransactionRequest(this, context), monitor);
   }
 
   @Override
-  @Deprecated
-  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
-      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public CommitTransactionResult commitDelegation(InternalCDOCommitContext context, OMMonitor monitor)
   {
     return send(new CommitDelegationRequest(this, context), monitor);
@@ -574,14 +531,6 @@
   }
 
   @Override
-  @Deprecated
-  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
-      CDORevisionAvailabilityInfo sourceBaseInfo)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public MergeDataResult loadMergeData2(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo,
       CDORevisionAvailabilityInfo targetBaseInfo, CDORevisionAvailabilityInfo sourceBaseInfo)
   {
@@ -589,14 +538,7 @@
   }
 
   @Override
-  @Deprecated
-  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth)
+  public List<CDOLockState> getLockStates2(int branchID, Collection<CDOID> ids, int depth)
   {
     return send(new LockStateRequest(this, branchID, ids, depth));
   }
@@ -626,13 +568,6 @@
   }
 
   @Override
-  @Deprecated
-  public void requestChangeCredentials()
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void requestChangeServerPassword(AtomicReference<char[]> receiver)
   {
     send(new ChangeCredentialsRequest(this, receiver), new Monitor());
@@ -764,4 +699,85 @@
       REVISION_LOADING.stop(request);
     }
   }
+
+  @Override
+  @Deprecated
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public LockObjectsResult lockObjects(List<InternalCDORevision> revisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
+      throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
+      Collection<CDOLob<?>> lobs, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
+      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
+      CDORevisionAvailabilityInfo sourceBaseInfo)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void requestChangeCredentials()
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CommitTransactionRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CommitTransactionRequest.java
index fde0664..f838249 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CommitTransactionRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CommitTransactionRequest.java
@@ -27,6 +27,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOBlob;
 import org.eclipse.emf.cdo.common.lob.CDOClob;
 import org.eclipse.emf.cdo.common.lob.CDOLob;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
@@ -160,6 +161,7 @@
     out.writeXInt(commitNumber);
     out.writeString(commitComment);
     CDOBranchUtil.writeBranchPointOrNull(out, commitMergeSource);
+    out.writeXLong(transaction.options().getOptimisticLockingTimeout());
 
     out.writeXInt(locksOnNewObjects.size());
     out.writeXInt(idsToUnlock.size());
@@ -310,7 +312,7 @@
 
     result = confirmingResult(in);
     confirmingMappingNewObjects(in, result);
-    confirmingNewLockStates(in, result);
+    confirmingLocks(in, result);
     confirmingNewPermissions(in, result);
     confirmingNewCommitData(in, result);
     return result;
@@ -382,17 +384,13 @@
     }
   }
 
-  protected void confirmingNewLockStates(CDODataInput in, CommitTransactionResult result) throws IOException
+  protected void confirmingLocks(CDODataInput in, CommitTransactionResult result) throws IOException
   {
-    int n = in.readXInt();
-    CDOLockState[] newLockStates = new CDOLockState[n];
+    List<CDOLockDelta> lockDeltas = in.readCDOLockDeltas();
+    result.setLockDeltas(lockDeltas);
 
-    for (int i = 0; i < n; i++)
-    {
-      newLockStates[i] = in.readCDOLockState();
-    }
-
-    result.setNewLockStates(newLockStates);
+    List<CDOLockState> lockStates = in.readCDOLockStates();
+    result.setLockStates(lockStates);
   }
 
   protected void confirmingNewPermissions(CDODataInput in, CommitTransactionResult result) throws IOException
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LoadRevisionsRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LoadRevisionsRequest.java
index 4a32922..66fffac 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LoadRevisionsRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LoadRevisionsRequest.java
@@ -212,18 +212,7 @@
 
     if (in.readBoolean())
     {
-      List<CDOLockState> lockStates = new ArrayList<>();
-
-      for (;;)
-      {
-        CDOLockState lockState = in.readCDOLockState();
-        if (lockState == null)
-        {
-          break;
-        }
-
-        lockStates.add(lockState);
-      }
+      List<CDOLockState> lockStates = in.readCDOLockStates();
 
       int noLockStateKeys = in.readXInt();
       if (noLockStateKeys != 0)
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockAreaRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockAreaRequest.java
index 7295e87..dd9bba7 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockAreaRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockAreaRequest.java
@@ -22,9 +22,9 @@
  */
 public class LockAreaRequest extends CDOClientRequest<String>
 {
-  private CDOView view;
+  private final CDOView view;
 
-  private boolean create;
+  private final boolean create;
 
   public LockAreaRequest(CDOClientProtocol protocol, CDOView view, boolean create)
   {
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockObjectsRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockObjectsRequest.java
index 1c96f19..1e63eef 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockObjectsRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockObjectsRequest.java
@@ -11,6 +11,7 @@
  */
 package org.eclipse.emf.cdo.internal.net4j.protocol;
 
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
@@ -78,6 +79,7 @@
     boolean succesful = in.readBoolean();
     boolean timeout = in.readBoolean();
     boolean waitForUpdate = in.readBoolean();
+    long timestamp = in.readXLong();
     long requiredTimestamp = in.readXLong();
 
     int nStaleRevisions = in.readXInt();
@@ -87,16 +89,10 @@
       staleRevisions[i] = in.readCDORevisionKey();
     }
 
-    long timestamp = in.readXLong();
+    List<CDOLockDelta> lockDeltas = in.readCDOLockDeltas();
+    List<CDOLockState> lockStates = in.readCDOLockStates();
 
-    int n = in.readXInt();
-    CDOLockState[] newLockStates = new CDOLockState[n];
-    for (int i = 0; i < n; i++)
-    {
-      newLockStates[i] = in.readCDOLockState();
-    }
-
-    return new LockObjectsResult(succesful, timeout, waitForUpdate, requiredTimestamp, staleRevisions, newLockStates, timestamp);
+    return new LockObjectsResult(succesful, timeout, waitForUpdate, requiredTimestamp, staleRevisions, lockDeltas, lockStates, timestamp);
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockStateRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockStateRequest.java
index 4485e65..68d7c2f 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockStateRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/LockStateRequest.java
@@ -19,11 +19,12 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * @author Caspar De Groot
  */
-public class LockStateRequest extends CDOClientRequest<CDOLockState[]>
+public class LockStateRequest extends CDOClientRequest<List<CDOLockState>>
 {
   private int branchID;
 
@@ -61,16 +62,9 @@
   }
 
   @Override
-  protected CDOLockState[] confirming(CDODataInput in) throws IOException
+  protected List<CDOLockState> confirming(CDODataInput in) throws IOException
   {
-    int n = in.readXInt();
-    CDOLockState[] lockStates = new CDOLockState[n];
-    for (int i = 0; i < n; i++)
-    {
-      lockStates[i] = in.readCDOLockState();
-    }
-
-    return lockStates;
+    return in.readCDOLockStates();
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/OpenViewRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/OpenViewRequest.java
index fc6d940..5b4263b 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/OpenViewRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/OpenViewRequest.java
@@ -11,25 +11,31 @@
 package org.eclipse.emf.cdo.internal.net4j.protocol;
 
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockAreaNotFoundException;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 
 import java.io.IOException;
+import java.util.function.BiConsumer;
 
 /**
  * @author Eike Stepper
  */
 public class OpenViewRequest extends CDOClientRequest<CDOBranchPoint>
 {
-  private int viewID;
+  private final int viewID;
 
-  private boolean readOnly;
+  private final boolean readOnly;
 
-  private CDOBranchPoint branchPoint;
+  private final CDOBranchPoint branchPoint;
 
-  private String durableLockingID;
+  private final String durableLockingID;
+
+  private final BiConsumer<CDOID, LockGrade> consumer;
 
   public OpenViewRequest(CDOClientProtocol protocol, int viewID, boolean readOnly, CDOBranchPoint branchPoint)
   {
@@ -37,14 +43,18 @@
     this.viewID = viewID;
     this.readOnly = readOnly;
     this.branchPoint = branchPoint;
+    durableLockingID = null;
+    consumer = null;
   }
 
-  public OpenViewRequest(CDOClientProtocol protocol, int viewID, boolean readOnly, String durableLockingID)
+  public OpenViewRequest(CDOClientProtocol protocol, int viewID, boolean readOnly, String durableLockingID, BiConsumer<CDOID, LockGrade> consumer)
   {
     super(protocol, CDOProtocolConstants.SIGNAL_OPEN_VIEW);
     this.viewID = viewID;
     this.readOnly = readOnly;
     this.durableLockingID = durableLockingID;
+    this.consumer = consumer;
+    branchPoint = null;
   }
 
   @Override
@@ -62,6 +72,7 @@
     {
       out.writeBoolean(false);
       out.writeString(durableLockingID);
+      out.writeBoolean(consumer != null);
     }
   }
 
@@ -70,7 +81,24 @@
   {
     if (in.readBoolean())
     {
-      return in.readCDOBranchPoint();
+      CDOBranchPoint branchPoint = in.readCDOBranchPoint();
+
+      if (consumer != null)
+      {
+        for (;;)
+        {
+          CDOID id = in.readCDOID();
+          if (CDOIDUtil.isNull(id))
+          {
+            break;
+          }
+
+          LockGrade lockGrade = in.readEnum(LockGrade.class);
+          consumer.accept(id, lockGrade);
+        }
+      }
+
+      return branchPoint;
     }
 
     if (durableLockingID != null)
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/UnlockObjectsRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/UnlockObjectsRequest.java
index 470ff72..ab65b23 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/UnlockObjectsRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/UnlockObjectsRequest.java
@@ -11,6 +11,7 @@
 package org.eclipse.emf.cdo.internal.net4j.protocol;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
@@ -22,6 +23,7 @@
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * @author Simon McDuff
@@ -76,15 +78,10 @@
   protected UnlockObjectsResult confirming(CDODataInput in) throws IOException
   {
     long timestamp = in.readXLong();
-    int n = in.readXInt();
-    CDOLockState[] newLockStates = new CDOLockState[n];
+    List<CDOLockDelta> lockDeltas = in.readCDOLockDeltas();
+    List<CDOLockState> lockStates = in.readCDOLockStates();
 
-    for (int i = 0; i < n; i++)
-    {
-      newLockStates[i] = in.readCDOLockState();
-    }
-
-    return new UnlockObjectsResult(newLockStates, timestamp);
+    return new UnlockObjectsResult(timestamp, lockDeltas, lockStates);
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.server.admin/src/org/eclipse/emf/cdo/server/internal/admin/CDOAdminServerRepository.java b/plugins/org.eclipse.emf.cdo.server.admin/src/org/eclipse/emf/cdo/server/internal/admin/CDOAdminServerRepository.java
index a8ac5a7..b920790 100644
--- a/plugins/org.eclipse.emf.cdo.server.admin/src/org/eclipse/emf/cdo/server/internal/admin/CDOAdminServerRepository.java
+++ b/plugins/org.eclipse.emf.cdo.server.admin/src/org/eclipse/emf/cdo/server/internal/admin/CDOAdminServerRepository.java
@@ -190,13 +190,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isSupportingEcore()
-  {
-    return delegate.isSupportingEcore();
-  }
-
-  @Override
   public boolean isSerializingCommits()
   {
     return delegate.isSerializingCommits();
@@ -245,12 +238,6 @@
     return AdapterUtil.adapt(this, adapter, false);
   }
 
-  @Override
-  public String toString()
-  {
-    return delegate.toString();
-  }
-
   public void write(ExtendedDataOutputStream out) throws IOException
   {
     out.writeString(getName());
@@ -297,4 +284,17 @@
       synchronizer.removeListener(delegateSynchronizerListener);
     }
   }
+
+  @Override
+  public String toString()
+  {
+    return delegate.toString();
+  }
+
+  @Override
+  @Deprecated
+  public boolean isSupportingEcore()
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
index 2ce3641..3a81f22 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
@@ -107,7 +107,6 @@
 import java.io.Reader;
 import java.io.Writer;
 import java.sql.Connection;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -167,35 +166,6 @@
   }
 
   @Override
-  @Deprecated
-  public org.eclipse.emf.cdo.server.db.IPreparedStatementCache getStatementCache()
-  {
-    return new org.eclipse.emf.cdo.server.db.IPreparedStatementCache()
-    {
-      @Override
-      public void setConnection(Connection connection)
-      {
-        // Do nothing
-      }
-
-      @Override
-      public IDBPreparedStatement getPreparedStatement(String sql, ReuseProbability reuseProbability)
-      {
-        org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability converted = //
-            org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability.values()[reuseProbability.ordinal()];
-
-        return connection.prepareStatement(sql, converted);
-      }
-
-      @Override
-      public void releasePreparedStatement(PreparedStatement ps)
-      {
-        DBUtil.close(ps);
-      }
-    };
-  }
-
-  @Override
   public DBStoreChunkReader createChunkReader(InternalCDORevision revision, EStructuralFeature feature)
   {
     return new DBStoreChunkReader(this, revision, feature);
@@ -211,24 +181,6 @@
     return dbAdapter.openSchemaTransaction(database, connection);
   }
 
-  /**
-   * Returns an iterator that iterates over all objects in the store and makes their CDOIDs available for processing.
-   * This method is supposed to be called very infrequently, for example during the recovery from a crash.
-   *
-   * @since 2.0
-   * @deprecated Not used by the framework anymore.
-   */
-  @Deprecated
-  public CloseableIterator<CDOID> readObjectIDs()
-  {
-    if (TRACER.isEnabled())
-    {
-      TRACER.trace("Selecting object IDs"); //$NON-NLS-1$
-    }
-
-    return getStore().getMappingStrategy().readObjectIDs(this);
-  }
-
   public CDOClassifierRef readObjectType(CDOID id)
   {
     if (TRACER.isEnabled())
@@ -546,13 +498,6 @@
     }
   }
 
-  @Deprecated
-  @Override
-  protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, OMMonitor monitor)
-  {
-    writeCommitInfo(branch, timeStamp, previousTimeStamp, userID, comment, null, monitor);
-  }
-
   @Override
   protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, CDOBranchPoint mergeSource,
       OMMonitor monitor)
@@ -1215,20 +1160,6 @@
   }
 
   @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void renameBranch(int branchID, String oldName, String newName)
   {
     checkBranchingSupport();
@@ -1709,13 +1640,6 @@
   }
 
   @Override
-  public void unlock(String durableLockingID)
-  {
-    DurableLockingManager manager = getStore().getDurableLockingManager();
-    manager.unlock(this, durableLockingID);
-  }
-
-  @Override
   public List<CDOID> readUnitRoots()
   {
     UnitMappingTable unitMappingTable = getStore().getUnitMappingTable();
@@ -1828,4 +1752,45 @@
       return super.cancel();
     }
   }
+
+  @Override
+  @Deprecated
+  public org.eclipse.emf.cdo.server.db.IPreparedStatementCache getStatementCache()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  public CloseableIterator<CDOID> readObjectIDs()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  @Override
+  protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DurableLockingManager.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DurableLockingManager.java
index 22ebaf5..c7fc602 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DurableLockingManager.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DurableLockingManager.java
@@ -31,6 +31,7 @@
 import org.eclipse.net4j.db.DBException;
 import org.eclipse.net4j.db.DBType;
 import org.eclipse.net4j.db.DBUtil;
+import org.eclipse.net4j.db.IDBConnection;
 import org.eclipse.net4j.db.IDBDatabase;
 import org.eclipse.net4j.db.IDBDatabase.RunnableWithSchema;
 import org.eclipse.net4j.db.IDBPreparedStatement;
@@ -134,7 +135,8 @@
       }
     }
 
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsertLockArea, ReuseProbability.LOW);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlInsertLockArea, ReuseProbability.LOW);
 
     try
     {
@@ -167,7 +169,8 @@
 
   private void insertLocks(DBStoreAccessor accessor, String durableLockingID, Map<CDOID, LockGrade> locks)
   {
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsertLock, ReuseProbability.MEDIUM);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlInsertLock, ReuseProbability.MEDIUM);
 
     try
     {
@@ -196,7 +199,8 @@
 
   public LockArea getLockArea(DBStoreAccessor accessor, String durableLockingID) throws LockAreaNotFoundException
   {
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectLockArea, ReuseProbability.MEDIUM);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlSelectLockArea, ReuseProbability.MEDIUM);
     ResultSet resultSet = null;
 
     try
@@ -229,6 +233,7 @@
 
   public void getLockAreas(DBStoreAccessor accessor, String userIDPrefix, Handler handler)
   {
+    IDBConnection connection = accessor.getDBConnection();
     IDBPreparedStatement stmt = null;
     ResultSet resultSet = null;
 
@@ -236,11 +241,11 @@
     {
       if (userIDPrefix.length() == 0)
       {
-        stmt = accessor.getDBConnection().prepareStatement(sqlSelectAllLockAreas, ReuseProbability.MEDIUM);
+        stmt = connection.prepareStatement(sqlSelectAllLockAreas, ReuseProbability.MEDIUM);
       }
       else
       {
-        stmt = accessor.getDBConnection().prepareStatement(sqlSelectLockAreas, ReuseProbability.MEDIUM);
+        stmt = connection.prepareStatement(sqlSelectLockAreas, ReuseProbability.MEDIUM);
         stmt.setString(1, userIDPrefix + "%");
       }
 
@@ -275,7 +280,8 @@
   {
     unlockWithoutCommit(accessor, durableLockingID);
 
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlDeleteLockArea, ReuseProbability.LOW);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlDeleteLockArea, ReuseProbability.LOW);
 
     try
     {
@@ -309,18 +315,21 @@
 
   public void unlock(DBStoreAccessor accessor, String durableLockingID, LockType type, Collection<? extends Object> objectsToUnlock)
   {
-    changeLocks(accessor, durableLockingID, type, objectsToUnlock, false);
-  }
-
-  public void unlock(DBStoreAccessor accessor, String durableLockingID)
-  {
-    unlockWithoutCommit(accessor, durableLockingID);
-    commit(accessor);
+    if (objectsToUnlock == null)
+    {
+      unlockWithoutCommit(accessor, durableLockingID);
+      commit(accessor);
+    }
+    else
+    {
+      changeLocks(accessor, durableLockingID, type, objectsToUnlock, false);
+    }
   }
 
   private void unlockWithoutCommit(DBStoreAccessor accessor, String durableLockingID)
   {
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlDeleteLocks, ReuseProbability.MEDIUM);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlDeleteLocks, ReuseProbability.MEDIUM);
 
     try
     {
@@ -432,7 +441,8 @@
 
   private Map<CDOID, LockGrade> getLockMap(DBStoreAccessor accessor, String durableLockingID)
   {
-    IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectLocks, ReuseProbability.MEDIUM);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmt = connection.prepareStatement(sqlSelectLocks, ReuseProbability.MEDIUM);
     ResultSet resultSet = null;
 
     try
@@ -469,20 +479,22 @@
       return;
     }
 
-    String sql = on ? sqlInsertLock : sqlDeleteLock;
+    String sqlInsertOrDelete = on ? sqlInsertLock : sqlDeleteLock;
 
-    IDBPreparedStatement stmtSelect = accessor.getDBConnection().prepareStatement(sqlSelectLock, ReuseProbability.MEDIUM);
-    IDBPreparedStatement stmtInsertOrDelete = accessor.getDBConnection().prepareStatement(sql, ReuseProbability.MEDIUM);
-    IDBPreparedStatement stmtUpdate = accessor.getDBConnection().prepareStatement(sqlUpdateLock, ReuseProbability.MEDIUM);
+    IDBConnection connection = accessor.getDBConnection();
+    IDBPreparedStatement stmtSelect = connection.prepareStatement(sqlSelectLock, ReuseProbability.MEDIUM);
+    IDBPreparedStatement stmtInsertOrDelete = connection.prepareStatement(sqlInsertOrDelete, ReuseProbability.MEDIUM);
+    IDBPreparedStatement stmtUpdate = connection.prepareStatement(sqlUpdateLock, ReuseProbability.MEDIUM);
     ResultSet resultSet = null;
 
+    InternalLockManager lockManager = accessor.getStore().getRepository().getLockingManager();
+
     try
     {
       stmtSelect.setString(1, durableLockingID);
       stmtInsertOrDelete.setString(1, durableLockingID);
       stmtUpdate.setString(2, durableLockingID);
 
-      InternalLockManager lockManager = accessor.getStore().getRepository().getLockingManager();
       for (Object key : keys)
       {
         CDOID id = lockManager.getLockKeyID(key);
@@ -515,7 +527,7 @@
         }
       }
 
-      accessor.getDBConnection().commit();
+      connection.commit();
     }
     catch (SQLException e)
     {
@@ -574,7 +586,8 @@
   {
     try
     {
-      accessor.getDBConnection().commit();
+      IDBConnection connection = accessor.getDBConnection();
+      connection.commit();
     }
     catch (SQLException ex)
     {
diff --git a/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeFileHandle.java b/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeFileHandle.java
index e5f56fd..b19e062 100644
--- a/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeFileHandle.java
+++ b/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeFileHandle.java
@@ -23,6 +23,7 @@
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
 import org.eclipse.emf.cdo.common.lob.CDOLobStore;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
@@ -499,6 +500,12 @@
   }
 
   @Override
+  public CDOLockDelta readCDOLockDelta() throws IOException
+  {
+    return in().readCDOLockDelta();
+  }
+
+  @Override
   public CDOLockState readCDOLockState() throws IOException
   {
     return in().readCDOLockState();
@@ -822,6 +829,12 @@
   }
 
   @Override
+  public void writeCDOLockDelta(CDOLockDelta lockDelta) throws IOException
+  {
+    out().writeCDOLockDelta(lockDelta);
+  }
+
+  @Override
   public void writeCDOLockOwner(CDOLockOwner lockOwner) throws IOException
   {
     out().writeCDOLockOwner(lockOwner);
diff --git a/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeStoreReader.java b/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeStoreReader.java
index f1e68ca..f3e9451 100644
--- a/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeStoreReader.java
+++ b/plugins/org.eclipse.emf.cdo.server.lissome/src/org/eclipse/emf/cdo/server/internal/lissome/LissomeStoreReader.java
@@ -486,9 +486,9 @@
   }
 
   @Override
+  @Deprecated
   public void unlock(String durableLockingID)
   {
-    // Implemented in LissomeStoreWriter
     throw new UnsupportedOperationException();
   }
 
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/CommitTransactionIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/CommitTransactionIndication.java
index 210055a..56d4056 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/CommitTransactionIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/CommitTransactionIndication.java
@@ -19,8 +19,8 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
-import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.model.EMFUtil;
 import org.eclipse.emf.cdo.common.model.EMFUtil.ExtResourceSet;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
@@ -31,7 +31,6 @@
 import org.eclipse.emf.cdo.common.security.CDOPermission;
 import org.eclipse.emf.cdo.etypes.EtypesPackage;
 import org.eclipse.emf.cdo.server.IPermissionManager;
-import org.eclipse.emf.cdo.server.IView;
 import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
@@ -43,7 +42,6 @@
 import org.eclipse.emf.cdo.spi.server.InternalView;
 
 import org.eclipse.net4j.util.WrappedException;
-import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
 
 import org.eclipse.emf.ecore.EClass;
@@ -121,6 +119,7 @@
     int commitNumber = in.readXInt();
     String commitComment = in.readString();
     CDOBranchPoint commitMergeSource = CDOBranchUtil.readBranchPointOrNull(in);
+    long optimisticLockingTimeout = in.readXLong();
 
     CDOLockState[] locksOnNewObjects = new CDOLockState[in.readXInt()];
     CDOID[] idsToUnlock = new CDOID[in.readXInt()];
@@ -256,6 +255,7 @@
 
       commitContext.setCommitNumber(commitNumber);
       commitContext.setLastUpdateTime(lastUpdateTime);
+      commitContext.setOptimisticLockingTimeout(optimisticLockingTimeout);
       commitContext.setClearResourcePathCache(clearResourcePathCache);
       commitContext.setUsingEcore(usingEcore);
       commitContext.setUsingEtypes(usingEtypes);
@@ -309,7 +309,7 @@
       {
         respondingResult(out);
         respondingMappingNewObjects(out);
-        respondingNewLockStates(out);
+        respondingLocks(out);
         respondingNewPermissions(out);
         respondingNewCommitData(out);
       }
@@ -369,22 +369,13 @@
     out.writeCDOID(CDOID.NULL);
   }
 
-  protected void respondingNewLockStates(CDODataOutput out) throws Exception
+  protected void respondingLocks(CDODataOutput out) throws Exception
   {
-    List<LockState<Object, IView>> newLockStates = commitContext.getPostCommmitLockStates();
-    if (newLockStates != null)
-    {
-      out.writeXInt(newLockStates.size());
-      for (LockState<Object, IView> lockState : newLockStates)
-      {
-        CDOLockState cdoLockState = CDOLockUtil.convertLockState(lockState);
-        out.writeCDOLockState(cdoLockState);
-      }
-    }
-    else
-    {
-      out.writeXInt(0);
-    }
+    List<CDOLockDelta> lockDeltas = commitContext.getLockDeltas();
+    out.writeCDOLockDeltas(lockDeltas, null);
+
+    List<CDOLockState> lockStates = commitContext.getLockStates();
+    out.writeCDOLockStates(lockStates, null);
   }
 
   protected void respondingNewPermissions(CDODataOutput out) throws Exception
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockObjectsIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockObjectsIndication.java
index b86169b..75a3bcb 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockObjectsIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockObjectsIndication.java
@@ -12,7 +12,6 @@
  */
 package org.eclipse.emf.cdo.server.internal.net4j.protocol;
 
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
@@ -72,6 +71,7 @@
     out.writeBoolean(result.isSuccessful());
     out.writeBoolean(result.isTimedOut());
     out.writeBoolean(result.isWaitForUpdate());
+    out.writeXLong(result.getTimestamp());
     out.writeXLong(result.getRequiredTimestamp());
 
     CDORevisionKey[] staleRevisions = result.getStaleRevisions();
@@ -81,13 +81,7 @@
       out.writeCDORevisionKey(revKey);
     }
 
-    out.writeXLong(result.getTimestamp());
-
-    CDOLockState[] newLockStates = result.getNewLockStates();
-    out.writeXInt(newLockStates.length);
-    for (CDOLockState lockState : newLockStates)
-    {
-      out.writeCDOLockState(lockState);
-    }
+    out.writeCDOLockDeltas(result.getLockDeltas(), null);
+    out.writeCDOLockStates(result.getLockStates(), null);
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockStateIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockStateIndication.java
index 35c71e8..92ec733 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockStateIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/LockStateIndication.java
@@ -22,6 +22,7 @@
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 import org.eclipse.emf.cdo.common.revision.CDORevisionManager;
 import org.eclipse.emf.cdo.common.revision.CDORevisionProvider;
+import org.eclipse.emf.cdo.internal.server.LockingManager.LockStateCollector;
 import org.eclipse.emf.cdo.server.IView;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
 import org.eclipse.emf.cdo.spi.common.revision.ManagedRevisionProvider;
@@ -34,7 +35,6 @@
 import org.eclipse.emf.ecore.EStructuralFeature;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collection;
 
 /**
@@ -42,10 +42,16 @@
  */
 public class LockStateIndication extends CDOServerReadIndication
 {
-  private Collection<CDOLockState> existingLockStates;
+  private final LockStateCollector existingLockStates = new LockStateCollector();
+
+  private InternalLockManager lockManager;
+
+  private CDOBranch branch;
 
   private int prefetchDepth = CDOLockState.DEPTH_NONE;
 
+  private CDORevisionProvider revisionProvider;
+
   public LockStateIndication(CDOServerProtocol protocol)
   {
     super(protocol, CDOProtocolConstants.SIGNAL_LOCK_STATE);
@@ -56,13 +62,10 @@
   {
     InternalRepository repository = getRepository();
     CDOBranchManager branchManager = repository.getBranchManager();
-    InternalLockManager lockManager = repository.getLockingManager();
+    lockManager = repository.getLockingManager();
 
     int branchID = in.readXInt();
-    CDOBranch branch = branchManager.getBranch(branchID);
-
-    existingLockStates = new ArrayList<>();
-    CDORevisionProvider revisionProvider;
+    branch = branchManager.getBranch(branchID);
 
     int idsLength = in.readXInt();
     if (idsLength < 0)
@@ -73,32 +76,26 @@
       CDORevisionManager revisionManager = repository.getRevisionManager();
       revisionProvider = new ManagedRevisionProvider(revisionManager, branch.getHead());
     }
-    else
-    {
-      revisionProvider = null;
-    }
 
     if (idsLength == 0)
     {
-      Collection<LockState<Object, IView>> lockStates = lockManager.getLockStates();
-      for (LockState<Object, IView> lockState : lockStates)
-      {
-        existingLockStates.add(CDOLockUtil.convertLockState(lockState));
-      }
+      lockManager.getLockStates(existingLockStates);
     }
     else
     {
+      int depth = prefetchDepth >= CDOLockState.DEPTH_NONE ? prefetchDepth : Integer.MAX_VALUE;
+
       for (int i = 0; i < idsLength; i++)
       {
         CDOID id = in.readCDOID();
-        prefetchLockStates(lockManager, prefetchDepth >= CDOLockState.DEPTH_NONE ? prefetchDepth : Integer.MAX_VALUE, id, branch, revisionProvider);
+        prefetchLockStates(depth, id);
       }
     }
   }
 
-  private void prefetchLockStates(InternalLockManager lockManager, int depth, CDOID id, CDOBranch branch, CDORevisionProvider revisionProvider)
+  private void prefetchLockStates(int depth, CDOID id)
   {
-    addLockState(lockManager, id, branch);
+    addLockState(id);
 
     if (depth > CDOLockState.DEPTH_NONE)
     {
@@ -121,7 +118,7 @@
             Object value = revision.getValue(reference);
             if (value instanceof CDOID)
             {
-              prefetchLockStates(lockManager, depth, (CDOID)value, branch, revisionProvider);
+              prefetchLockStates(depth, (CDOID)value);
             }
             else if (value instanceof Collection<?>)
             {
@@ -133,7 +130,7 @@
                 // instanceof CDOID. (See bug 339313.)
                 if (e instanceof CDOID)
                 {
-                  prefetchLockStates(lockManager, depth, (CDOID)e, branch, revisionProvider);
+                  prefetchLockStates(depth, (CDOID)e);
                 }
               }
             }
@@ -143,7 +140,7 @@
     }
   }
 
-  private void addLockState(InternalLockManager lockManager, CDOID id, CDOBranch branch)
+  private void addLockState(CDOID id)
   {
     Object key = lockManager.getLockKey(id, branch);
 
@@ -157,10 +154,6 @@
   @Override
   protected void responding(CDODataOutput out) throws IOException
   {
-    out.writeXInt(existingLockStates.size());
-    for (CDOLockState lockState : existingLockStates)
-    {
-      out.writeCDOLockState(lockState);
-    }
+    out.writeCDOLockStates(existingLockStates, null);
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenViewIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenViewIndication.java
index e2d8cb8..71e225a 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenViewIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenViewIndication.java
@@ -11,24 +11,35 @@
 package org.eclipse.emf.cdo.server.internal.net4j.protocol;
 
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
+import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockAreaNotFoundException;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
+import org.eclipse.emf.cdo.server.IView;
 import org.eclipse.emf.cdo.spi.server.InternalLockManager;
 import org.eclipse.emf.cdo.spi.server.InternalSession;
-import org.eclipse.emf.cdo.spi.server.InternalView;
+
+import org.eclipse.net4j.util.io.IORuntimeException;
 
 import java.io.IOException;
+import java.util.function.BiConsumer;
 
 /**
  * @author Eike Stepper
  */
 public class OpenViewIndication extends CDOServerReadIndication
 {
-  private InternalView newView;
+  private int viewID;
 
-  private String message;
+  private boolean readOnly;
+
+  private CDOBranchPoint branchPoint;
+
+  private String durableLockingID;
+
+  private boolean respondLockGrades;
 
   public OpenViewIndication(CDOServerProtocol protocol)
   {
@@ -38,14 +49,29 @@
   @Override
   protected void indicating(CDODataInput in) throws IOException
   {
-    InternalSession session = getSession();
-
-    int viewID = in.readXInt();
-    boolean readOnly = in.readBoolean();
+    viewID = in.readXInt();
+    readOnly = in.readBoolean();
 
     if (in.readBoolean())
     {
-      CDOBranchPoint branchPoint = in.readCDOBranchPoint();
+      branchPoint = in.readCDOBranchPoint();
+    }
+    else
+    {
+      durableLockingID = in.readString();
+      respondLockGrades = in.readBoolean();
+    }
+  }
+
+  @Override
+  protected void responding(CDODataOutput out) throws IOException
+  {
+    InternalSession session = getSession();
+
+    if (branchPoint != null)
+    {
+      IView newView;
+
       if (readOnly)
       {
         newView = session.openView(viewID, branchPoint);
@@ -54,15 +80,58 @@
       {
         newView = session.openTransaction(viewID, branchPoint);
       }
+
+      respondNewView(out, newView, null);
     }
     else
     {
       InternalLockManager lockManager = getRepository().getLockingManager();
+      String message = null;
+
+      BiConsumer<CDOID, LockGrade> lockConsumer = respondLockGrades ? (id, lockGrade) -> {
+        try
+        {
+          out.writeCDOID(id);
+          out.writeEnum(lockGrade);
+        }
+        catch (IOException ex)
+        {
+          throw new IORuntimeException(ex);
+        }
+      } : null;
 
       try
       {
-        String durableLockingID = in.readString();
-        newView = (InternalView)lockManager.openView(session, viewID, readOnly, durableLockingID);
+        try
+        {
+          lockManager.openView(session, viewID, readOnly, durableLockingID, newView -> {
+            try
+            {
+              respondNewView(out, newView, null);
+            }
+            catch (IOException ex)
+            {
+              throw new IORuntimeException(ex);
+            }
+          }, lockConsumer);
+        }
+        catch (IORuntimeException ex)
+        {
+          Throwable cause = ex.getCause();
+          if (cause instanceof IOException)
+          {
+            throw (IOException)cause;
+          }
+
+          throw ex;
+        }
+
+        if (respondLockGrades)
+        {
+          out.writeCDOID(null);
+        }
+
+        return;
       }
       catch (LockAreaNotFoundException ex)
       {
@@ -72,11 +141,12 @@
       {
         message = ex.getMessage();
       }
+
+      respondNewView(out, null, message);
     }
   }
 
-  @Override
-  protected void responding(CDODataOutput out) throws IOException
+  private static void respondNewView(CDODataOutput out, IView newView, String message) throws IOException
   {
     if (newView != null)
     {
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockDelegationIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockDelegationIndication.java
index 2e008cd..4a138d7 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockDelegationIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockDelegationIndication.java
@@ -13,10 +13,13 @@
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
+import org.eclipse.emf.cdo.server.IView;
 import org.eclipse.emf.cdo.spi.server.InternalLockManager;
 import org.eclipse.emf.cdo.spi.server.InternalSession;
 import org.eclipse.emf.cdo.spi.server.InternalView;
 
+import org.eclipse.net4j.util.concurrent.Holder;
+
 import java.io.IOException;
 
 /**
@@ -56,9 +59,11 @@
   @Override
   protected InternalView getView(int viewID)
   {
+    Holder<IView> viewHolder = new Holder<>();
+
     InternalLockManager lockManager = getRepository().getLockingManager();
     InternalSession session = getSession();
-    view = (InternalView)lockManager.openView(session, InternalSession.TEMP_VIEW_ID, true, lockAreaID);
-    return view;
+    lockManager.openView(session, InternalSession.TEMP_VIEW_ID, true, lockAreaID, viewHolder, null);
+    return view = (InternalView)viewHolder.get();
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockObjectsIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockObjectsIndication.java
index 70e26a8..7852a38 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockObjectsIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/UnlockObjectsIndication.java
@@ -12,7 +12,6 @@
 package org.eclipse.emf.cdo.server.internal.net4j.protocol;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
@@ -76,11 +75,7 @@
   protected void responding(CDODataOutput out) throws IOException
   {
     out.writeXLong(result.getTimestamp());
-    CDOLockState[] newLockStates = result.getNewLockStates();
-    out.writeXInt(newLockStates.length);
-    for (CDOLockState state : newLockStates)
-    {
-      out.writeCDOLockState(state);
-    }
+    out.writeCDOLockDeltas(result.getLockDeltas(), null);
+    out.writeCDOLockStates(result.getLockStates(), null);
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/.settings/.api_filters b/plugins/org.eclipse.emf.cdo.server/.settings/.api_filters
index 2d7a2c5..a7a965d 100644
--- a/plugins/org.eclipse.emf.cdo.server/.settings/.api_filters
+++ b/plugins/org.eclipse.emf.cdo.server/.settings/.api_filters
@@ -87,19 +87,6 @@
         </filter>
     </resource>
     <resource path="src/org/eclipse/emf/cdo/internal/server/Repository.java" type="org.eclipse.emf.cdo.internal.server.Repository">
-        <filter id="574619656">
-            <message_arguments>
-                <message_argument value="CDOReplicationInfo"/>
-                <message_argument value="Repository"/>
-            </message_arguments>
-        </filter>
-        <filter id="574660632">
-            <message_arguments>
-                <message_argument value="InternalRepository"/>
-                <message_argument value="CDOCommonRepository"/>
-                <message_argument value="Repository"/>
-            </message_arguments>
-        </filter>
         <filter id="574664731">
             <message_arguments>
                 <message_argument value="org.eclipse.emf.cdo.internal.server.Repository.replicateRaw(CDODataOutput, int, long)"/>
@@ -457,6 +444,72 @@
                 <message_argument value="InternalLockManager"/>
             </message_arguments>
         </filter>
+        <filter id="574619656">
+            <message_arguments>
+                <message_argument value="IRWOLockManager&lt;OBJECT, CONTEXT&gt;"/>
+                <message_argument value="InternalLockManager"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="lock(IRWLockManager.LockType, IView, Collection&lt;? extends Object&gt;, long)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="lock(IRWLockManager.LockType, IView, Object, long)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="lock(IView, Collection&lt;? extends Object&gt;, IRWLockManager.LockType, int, long, IRWOLockManager.LockDeltaHandler&lt;Object,IView&gt;, Consumer&lt;RWOLockManager.LockState&lt;Object,IView&gt;&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="lock2(IRWLockManager.LockType, IView, Collection&lt;? extends Object&gt;, long)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="lock3(IView, Collection&lt;? extends Object&gt;, IRWLockManager.LockType, int, long, IRWOLockManager.LockDeltaHandler&lt;Object,IView&gt;, Consumer&lt;RWOLockManager.LockState&lt;Object,IView&gt;&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock(IRWLockManager.LockType, IView, Collection&lt;? extends Object&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock(IView)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock(IView, Collection&lt;? extends Object&gt;, IRWLockManager.LockType, int, IRWOLockManager.LockDeltaHandler&lt;Object,IView&gt;, Consumer&lt;RWOLockManager.LockState&lt;Object,IView&gt;&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock2(IRWLockManager.LockType, IView, Collection&lt;? extends Object&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock2(IView)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock2(IView, Collection&lt;? extends Object&gt;)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1211105284">
+            <message_arguments>
+                <message_argument value="unlock3(IView, Collection&lt;? extends Object&gt;, IRWLockManager.LockType, int, IRWOLockManager.LockDeltaHandler&lt;Object,IView&gt;, Consumer&lt;RWOLockManager.LockState&lt;Object,IView&gt;&gt;)"/>
+            </message_arguments>
+        </filter>
     </resource>
     <resource path="src/org/eclipse/emf/cdo/spi/server/InternalRepository.java" type="org.eclipse.emf.cdo.spi.server.InternalRepository">
         <filter id="571473929">
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/DelegatingCommitContext.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/DelegatingCommitContext.java
index d9e9f37..013d318 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/DelegatingCommitContext.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/DelegatingCommitContext.java
@@ -15,7 +15,9 @@
 import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDReference;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitData;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.server.IStoreAccessor;
 import org.eclipse.emf.cdo.server.IStoreAccessor.CommitContext;
@@ -33,6 +35,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * @author Eike Stepper
@@ -150,13 +153,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isAutoReleaseLocksEnabled()
-  {
-    return getDelegate().isAutoReleaseLocksEnabled();
-  }
-
-  @Override
   public CDOLockState[] getLocksOnNewObjects()
   {
     return getDelegate().getLocksOnNewObjects();
@@ -169,6 +165,18 @@
   }
 
   @Override
+  public List<CDOLockDelta> getLockDeltas()
+  {
+    return getDelegate().getLockDeltas();
+  }
+
+  @Override
+  public List<CDOLockState> getLockStates()
+  {
+    return getDelegate().getLockStates();
+  }
+
+  @Override
   public CDOBranchVersion[] getDetachedObjectVersions()
   {
     return getDelegate().getDetachedObjectVersions();
@@ -187,12 +195,6 @@
   }
 
   @Override
-  public List<LockState<Object, IView>> getPostCommmitLockStates()
-  {
-    return getDelegate().getPostCommmitLockStates();
-  }
-
-  @Override
   public byte getRollbackReason()
   {
     return getDelegate().getRollbackReason();
@@ -209,4 +211,79 @@
   {
     return getDelegate().getXRefs();
   }
+
+  @Override
+  public CDOBranchPoint getCommitMergeSource()
+  {
+    return getDelegate().getCommitMergeSource();
+  }
+
+  @Override
+  public byte getSecurityImpact()
+  {
+    return getDelegate().getSecurityImpact();
+  }
+
+  @Override
+  public Map<CDOID, InternalCDORevision> getOldRevisions()
+  {
+    return getDelegate().getOldRevisions();
+  }
+
+  @Override
+  public Map<CDOID, InternalCDORevision> getNewRevisions()
+  {
+    return getDelegate().getNewRevisions();
+  }
+
+  @Override
+  public CommitData getOriginalCommmitData()
+  {
+    return getDelegate().getOriginalCommmitData();
+  }
+
+  @Override
+  public <T> T getData(Object key)
+  {
+    return getDelegate().getData(key);
+  }
+
+  @Override
+  public <T> T setData(Object key, T data)
+  {
+    return getDelegate().setData(key, data);
+  }
+
+  @Override
+  public void modify(Consumer<ModificationContext> modifier)
+  {
+    getDelegate().modify(modifier);
+  }
+
+  @Override
+  @Deprecated
+  public boolean isAutoReleaseLocksEnabled()
+  {
+    return getDelegate().isAutoReleaseLocksEnabled();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> getPostCommmitLockStates()
+  {
+    return getDelegate().getPostCommmitLockStates();
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  @SuppressWarnings("unused")
+  private static final class InternalCompletenessChecker extends DelegatingCommitContext
+  {
+    @Override
+    protected CommitContext getDelegate()
+    {
+      return null;
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockingManager.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockingManager.java
index 256987f..531f26c 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockingManager.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockingManager.java
@@ -17,7 +17,10 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
+import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
@@ -33,6 +36,7 @@
 import org.eclipse.emf.cdo.server.IView;
 import org.eclipse.emf.cdo.server.StoreThreadLocal;
 import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil;
+import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranch;
 import org.eclipse.emf.cdo.spi.common.revision.ManagedRevisionProvider;
 import org.eclipse.emf.cdo.spi.server.InternalLockManager;
 import org.eclipse.emf.cdo.spi.server.InternalRepository;
@@ -41,11 +45,12 @@
 import org.eclipse.emf.cdo.spi.server.InternalView;
 
 import org.eclipse.net4j.util.CheckUtil;
-import org.eclipse.net4j.util.ImplementationError;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.collection.ConcurrentArray;
+import org.eclipse.net4j.util.concurrent.Access;
 import org.eclipse.net4j.util.concurrent.RWOLockManager;
+import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
 import org.eclipse.net4j.util.container.ContainerEventAdapter;
 import org.eclipse.net4j.util.container.IContainer;
 import org.eclipse.net4j.util.event.EventUtil;
@@ -63,10 +68,13 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * @author Simon McDuff
@@ -100,7 +108,7 @@
       String durableLockingID = view.getDurableLockingID();
       if (durableLockingID == null)
       {
-        repository.unlock((InternalView)view, null, null, false);
+        repository.unlock((InternalView)view);
       }
       else
       {
@@ -128,6 +136,12 @@
     }
   };
 
+  private BiFunction<CDOID, CDOBranch, Object> lockKeyCreator;
+
+  private Function<Object, CDOID> lockIDExtractor;
+
+  private Function<Object, CDOBranch> lockBranchExtractor;
+
   public LockingManager()
   {
   }
@@ -145,131 +159,68 @@
   }
 
   @Override
-  public synchronized Object getLockEntryObject(Object key)
-  {
-    LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
-    return lockState == null ? null : lockState.getLockedObject();
-  }
-
-  @Override
   public Object getLockKey(CDOID id, CDOBranch branch)
   {
-    if (repository.isSupportingBranches())
-    {
-      return CDOIDUtil.createIDAndBranch(id, branch);
-    }
-
-    return id;
+    return lockKeyCreator.apply(id, branch);
   }
 
   @Override
-  public synchronized Map<CDOID, LockGrade> getLocks(final IView view)
+  public CDOID getLockKeyID(Object key)
+  {
+    return lockIDExtractor.apply(key);
+  }
+
+  @Override
+  public CDOBranch getLockKeyBranch(Object key)
+  {
+    return lockBranchExtractor.apply(key);
+  }
+
+  /**
+   * @category Read Access
+   */
+  @Override
+  public Map<CDOID, LockGrade> getLocks(final IView view)
   {
     final Map<CDOID, LockGrade> result = CDOIDUtil.createMap();
 
-    for (LockState<Object, IView> lockState : getObjectToLocksMap().values())
+    try (Access access = read.access())
     {
-      LockGrade grade = LockGrade.NONE;
-      if (lockState.hasLock(LockType.READ, view, false))
+      for (LockState<Object, IView> lockState : getObjectToLocksMap().values())
       {
-        grade = grade.getUpdated(LockType.READ, true);
-      }
+        LockGrade grade = LockGrade.NONE;
+        if (lockState.hasLock(LockType.READ, view, false))
+        {
+          grade = grade.getUpdated(LockType.READ, true);
+        }
 
-      if (lockState.hasLock(LockType.WRITE, view, false))
-      {
-        grade = grade.getUpdated(LockType.WRITE, true);
-      }
+        if (lockState.hasLock(LockType.WRITE, view, false))
+        {
+          grade = grade.getUpdated(LockType.WRITE, true);
+        }
 
-      if (lockState.hasLock(LockType.OPTION, view, false))
-      {
-        grade = grade.getUpdated(LockType.OPTION, true);
-      }
+        if (lockState.hasLock(LockType.OPTION, view, false))
+        {
+          grade = grade.getUpdated(LockType.OPTION, true);
+        }
 
-      if (grade != LockGrade.NONE)
-      {
-        CDOID id = getLockKeyID(lockState.getLockedObject());
-        result.put(id, grade);
+        if (grade != LockGrade.NONE)
+        {
+          CDOID id = getLockKeyID(lockState.getLockedObject());
+          result.put(id, grade);
+        }
       }
     }
 
     return result;
   }
 
-  @Override
-  @Deprecated
-  public void lock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
-  {
-    lock2(explicit, type, view, objectsToLock, false, timeout);
-  }
-
-  @Override
-  public List<LockState<Object, IView>> lock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, boolean recursive,
-      long timeout) throws InterruptedException
-  {
-    String durableLockingID = null;
-    DurableLocking accessor = null;
-
-    if (explicit)
-    {
-      durableLockingID = view.getDurableLockingID();
-      if (durableLockingID != null)
-      {
-        accessor = getDurableLocking();
-      }
-    }
-
-    long startTime = timeout == WAIT ? 0L : currentTimeMillis();
-
-    List<LockState<Object, IView>> newLockStates;
-    synchronized (this)
-    {
-      if (recursive)
-      {
-        objectsToLock = createContentSet(objectsToLock, view);
-      }
-
-      // Adjust timeout for delay we may have incurred on entering this synchronized block
-      if (timeout != WAIT)
-      {
-        timeout -= currentTimeMillis() - startTime;
-      }
-
-      newLockStates = super.lock2(type, view, objectsToLock, timeout);
-    }
-
-    if (accessor != null)
-    {
-      accessor.lock(durableLockingID, type, objectsToLock);
-    }
-
-    return newLockStates;
-  }
-
-  @Override
-  public void lock(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
-  {
-    lock2(false, type, context, objectsToLock, false, timeout);
-  }
-
-  @Override
-  public void lock(LockType type, IView context, Object objectToLock, long timeout) throws InterruptedException
-  {
-    Collection<Object> objectsToLock = new LinkedHashSet<>();
-    objectsToLock.add(objectToLock);
-    lock2(false, type, context, objectsToLock, false, timeout);
-  }
-
-  @Override
-  public List<LockState<Object, IView>> lock2(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout)
-      throws InterruptedException
-  {
-    return lock2(false, type, context, objectsToLock, false, timeout);
-  }
-
+  /**
+   * Synchronized by the caller.
+   */
   private Set<? extends Object> createContentSet(Collection<? extends Object> objectsToLock, IView view)
   {
     CDOBranch branch = view.getBranch();
-    boolean branching = repository.isSupportingBranches();
 
     CDORevisionManager revisionManager = view.getSession().getRepository().getRevisionManager();
     CDORevisionProvider revisionProvider = new ManagedRevisionProvider(revisionManager, branch.getHead());
@@ -280,111 +231,109 @@
     {
       contents.add(objectToLock);
 
-      CDOID id = branching ? ((CDOIDAndBranch)objectToLock).getID() : (CDOID)objectToLock;
+      CDOID id = getLockKeyID(objectToLock);
       CDORevision revision = revisionProvider.getRevision(id);
-      createContentSet(branch, branching, revisionProvider, revision, contents);
+      createContentSet(branch, revisionProvider, revision, contents);
     }
 
     return contents;
   }
 
-  private void createContentSet(CDOBranch branch, boolean branching, CDORevisionProvider revisionProvider, CDORevision revision, Set<Object> contents)
+  /**
+   * Synchronized by the caller.
+   */
+  private void createContentSet(CDOBranch branch, CDORevisionProvider revisionProvider, CDORevision revision, Set<Object> contents)
   {
     for (CDORevision child : CDORevisionUtil.getChildRevisions(revision, revisionProvider))
     {
       CDOID childID = child.getID();
-      contents.add(branching ? CDOIDUtil.createIDAndBranch(childID, branch) : childID);
-      createContentSet(branch, branching, revisionProvider, child, contents);
+      Object key = getLockKey(childID, branch);
+      contents.add(key);
+      createContentSet(branch, revisionProvider, child, contents);
     }
   }
 
+  /**
+   * @category Write Access
+   */
   @Override
-  @Deprecated
-  public synchronized void unlock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToUnlock)
+  public long lock(IView view, Collection<? extends Object> objects, LockType lockType, int count, long timeout, //
+      boolean recursive, boolean explicit, //
+      LockDeltaHandler<Object, IView> deltaHandler, Consumer<LockState<Object, IView>> stateHandler) //
+      throws InterruptedException, TimeoutRuntimeException
   {
-    unlock2(explicit, type, view, objectsToUnlock, false);
-  }
-
-  @Override
-  public synchronized List<LockState<Object, IView>> unlock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objects,
-      boolean recursive)
-  {
-    List<LockState<Object, IView>> newLockStates;
-    synchronized (this)
+    long modCount;
+    try (Access access = write.access())
     {
       if (recursive)
       {
         objects = createContentSet(objects, view);
       }
 
-      newLockStates = super.unlock2(type, view, objects);
+      modCount = super.lock(view, objects, lockType, count, timeout, deltaHandler, stateHandler);
     }
 
     if (explicit)
     {
-      String durableLockingID = view.getDurableLockingID();
-      if (durableLockingID != null)
+      try
       {
-        DurableLocking accessor = getDurableLocking();
-        accessor.unlock(durableLockingID, type, objects);
+        lockDurably(view, objects, lockType);
+      }
+      catch (Exception | Error ex)
+      {
+        super.unlock(view, objects, lockType, count, null, null);
+        throw ex;
       }
     }
 
-    return newLockStates;
+    return modCount;
   }
 
-  @Override
-  @Deprecated
-  public synchronized void unlock(boolean explicit, IView view)
+  private void lockDurably(IView view, Collection<? extends Object> objects, LockType lockType)
   {
-    unlock2(explicit, view);
+    String durableLockingID = view.getDurableLockingID();
+    if (durableLockingID != null)
+    {
+      DurableLocking accessor = getDurableLocking();
+      accessor.lock(durableLockingID, lockType, objects);
+    }
   }
 
+  /**
+   * @category Write Access
+   */
   @Override
-  public synchronized List<LockState<Object, IView>> unlock2(boolean explicit, IView view)
+  public long unlock(IView view, Collection<? extends Object> objects, LockType lockType, int count, //
+      boolean recursive, boolean explicit, //
+      LockDeltaHandler<Object, IView> deltaHandler, Consumer<LockState<Object, IView>> stateHandler)
   {
+    long modCount;
+    try (Access access = write.access())
+    {
+      if (recursive)
+      {
+        objects = createContentSet(objects, view);
+      }
+
+      modCount = super.unlock(view, objects, lockType, count, deltaHandler, stateHandler);
+    }
+
     if (explicit)
     {
-      String durableLockingID = view.getDurableLockingID();
-      if (durableLockingID != null)
-      {
-        DurableLocking accessor = getDurableLocking();
-        accessor.unlock(durableLockingID);
-      }
+      unlockDurably(view, objects, lockType);
     }
 
-    return super.unlock2(view);
+    return modCount;
   }
 
-  @Override
-  public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(IView context)
+  private void unlockDurably(IView view, Collection<? extends Object> objects, LockType lockType)
   {
-    return unlock2(false, context);
-  }
-
-  @Override
-  public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(IView context, Collection<? extends Object> objectsToUnlock)
-  {
-    // If no locktype is specified, use the LockType.WRITE
-    return unlock2(false, LockType.WRITE, context, objectsToUnlock, false);
-  }
-
-  @Override
-  public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(LockType type, IView context, Collection<? extends Object> objectsToUnlock)
-  {
-    return unlock2(false, type, context, objectsToUnlock, false);
-  }
-
-  @Override
-  public synchronized void unlock(IView context)
-  {
-    unlock2(context);
-  }
-
-  @Override
-  public synchronized void unlock(LockType type, IView context, Collection<? extends Object> objectsToUnlock)
-  {
-    unlock2(type, context, objectsToUnlock);
+    String durableLockingID = view.getDurableLockingID();
+    if (durableLockingID != null)
+    {
+      DurableLocking accessor = getDurableLocking();
+      accessor.unlock(durableLockingID, lockType, objects);
+    }
   }
 
   @Override
@@ -456,7 +405,8 @@
   }
 
   @Override
-  public IView openView(ISession session, int viewID, boolean readOnly, final String durableLockingID)
+  public void openView(ISession session, int viewID, boolean readOnly, String durableLockingID, Consumer<IView> viewConsumer,
+      BiConsumer<CDOID, LockGrade> lockConsumer)
   {
     synchronized (openDurableViews)
     {
@@ -509,7 +459,17 @@
       });
 
       openDurableViews.put(durableLockingID, view);
-      return view;
+      viewConsumer.accept(view);
+
+      if (lockConsumer != null)
+      {
+        for (Map.Entry<CDOID, LockGrade> entry : area.getLocks().entrySet())
+        {
+          CDOID id = entry.getKey();
+          LockGrade lockGrade = entry.getValue();
+          lockConsumer.accept(id, lockGrade);
+        }
+      }
     }
   }
 
@@ -517,9 +477,25 @@
   protected void doActivate() throws Exception
   {
     super.doActivate();
+
+    if (repository.isSupportingBranches())
+    {
+      lockKeyCreator = (id, branch) -> CDOIDUtil.createIDAndBranch(id, branch);
+      lockIDExtractor = key -> ((CDOIDAndBranch)key).getID();
+      lockBranchExtractor = key -> ((CDOIDAndBranch)key).getBranch();
+    }
+    else
+    {
+      InternalCDOBranch mainBranch = repository.getBranchManager().getMainBranch();
+
+      lockKeyCreator = (id, branch) -> id;
+      lockIDExtractor = key -> (CDOID)key;
+      lockBranchExtractor = key -> mainBranch;
+    }
+
     loadLocks();
 
-    InternalSessionManager sessionManager = getRepository().getSessionManager();
+    InternalSessionManager sessionManager = repository.getSessionManager();
     sessionManager.addListener(sessionManagerListener);
 
     for (ISession session : sessionManager.getSessions())
@@ -531,7 +507,7 @@
   @Override
   protected void doDeactivate() throws Exception
   {
-    ISessionManager sessionManager = getRepository().getSessionManager();
+    ISessionManager sessionManager = repository.getSessionManager();
     sessionManager.removeListener(sessionManagerListener);
 
     for (ISession session : sessionManager.getSessions())
@@ -604,18 +580,6 @@
   }
 
   @Override
-  public CDOID getLockKeyID(Object key)
-  {
-    CDOID id = CDOLockUtil.getLockedObjectID(key);
-    if (id == null)
-    {
-      throw new ImplementationError("Unexpected lock object: " + key);
-    }
-
-    return id;
-  }
-
-  @Override
   public void addDurableViewHandler(DurableViewHandler handler)
   {
     durableViewHandlers.add(handler);
@@ -634,6 +598,66 @@
   }
 
   /**
+   * @category Read Access
+   */
+  @Override
+  public LockGrade getLockGrade(Object key)
+  {
+    try (Access access = read.access())
+    {
+      LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
+      LockGrade grade = LockGrade.NONE;
+      if (lockState != null)
+      {
+        for (LockType type : ALL_LOCK_TYPES)
+        {
+          if (lockState.hasLock(type))
+          {
+            grade = grade.getUpdated(type, true);
+          }
+        }
+      }
+
+      return grade;
+    }
+  }
+
+  private LockArea getLockAreaNoEx(String durableLockingID)
+  {
+    try
+    {
+      return getLockArea(durableLockingID);
+    }
+    catch (LockAreaNotFoundException e)
+    {
+      return null;
+    }
+  }
+
+  @Override
+  public void updateLockArea(LockArea lockArea)
+  {
+    String durableLockingID = lockArea.getDurableLockingID();
+    DurableLocking2 accessor = getDurableLocking2();
+
+    if (lockArea.isMissing())
+    {
+      LockArea localLockArea = getLockAreaNoEx(durableLockingID);
+      if (localLockArea != null && localLockArea.getLocks().size() > 0)
+      {
+        accessor.deleteLockArea(durableLockingID);
+        DurableView deletedView = durableViews.remove(durableLockingID);
+        CheckUtil.checkNull(deletedView, "deletedView");
+      }
+    }
+    else
+    {
+      accessor.updateLockArea(lockArea);
+      new DurableLockLoader().handleLockArea(lockArea);
+    }
+  }
+
+  /**
    * @author Eike Stepper
    */
   private final class DurableView extends PlatformObject implements IView, CDOCommonView.Options
@@ -855,7 +879,7 @@
       IView view = getView(durableLockingID);
       if (view != null)
       {
-        unlock2(view);
+        LockingManager.super.unlock(view, null, null, ALL_LOCKS, null, null);
       }
       else
       {
@@ -888,9 +912,9 @@
 
       try
       {
-        lock(LockType.READ, view, readLocks, 1000L);
-        lock(LockType.WRITE, view, writeLocks, 1000L);
-        lock(LockType.OPTION, view, writeOptions, 1000L);
+        LockingManager.super.lock(view, readLocks, LockType.READ, 1, 1000L, null, null);
+        LockingManager.super.lock(view, writeLocks, LockType.WRITE, 1, 1000L, null, null);
+        LockingManager.super.lock(view, writeOptions, LockType.OPTION, 1, 1000L, null, null);
       }
       catch (InterruptedException ex)
       {
@@ -901,57 +925,196 @@
     }
   }
 
-  @Override
-  public synchronized LockGrade getLockGrade(Object key)
+  /**
+   * @author Eike Stepper
+   */
+  public static final class LockDeltaCollector extends ArrayList<CDOLockDelta> implements LockDeltaHandler<Object, IView>
   {
-    LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
-    LockGrade grade = LockGrade.NONE;
-    if (lockState != null)
+    private static final long serialVersionUID = 1L;
+
+    private Operation operation;
+
+    public LockDeltaCollector(Operation operation)
     {
-      for (LockType type : ALL_LOCK_TYPES)
+      this.operation = operation;
+    }
+
+    public LockDeltaCollector()
+    {
+    }
+
+    public Operation getOperation()
+    {
+      return operation;
+    }
+
+    public void setOperation(Operation operation)
+    {
+      this.operation = operation;
+    }
+
+    @Override
+    public void handleLockDelta(IView context, Object object, LockType lockType, int oldCount, int newCount)
+    {
+      switch (operation)
       {
-        if (lockState.hasLock(type))
+      case LOCK:
+        if (oldCount == 0 && newCount > 0)
         {
-          grade = grade.getUpdated(type, true);
+          CDOLockOwner lockOwner = context.getLockOwner();
+          CDOLockDelta lockDelta = CDOLockUtil.createLockDelta(object, lockType, null, lockOwner);
+          add(lockDelta);
         }
+        break;
+
+      case UNLOCK:
+        if (oldCount > 0 && newCount == 0)
+        {
+          CDOLockOwner lockOwner = context.getLockOwner();
+          CDOLockDelta lockDelta = CDOLockUtil.createLockDelta(object, lockType, lockOwner, null);
+          add(lockDelta);
+        }
+        break;
+
+      default:
+        throw new AssertionError();
       }
     }
-
-    return grade;
   }
 
-  private LockArea getLockAreaNoEx(String durableLockingID)
+  /**
+   * @author Eike Stepper
+   */
+  public static final class LockStateCollector extends ArrayList<CDOLockState> implements Consumer<LockState<Object, IView>>
   {
-    try
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public void accept(LockState<Object, IView> serverLockState)
     {
-      return getLockArea(durableLockingID);
+      CDOLockState commonLockState = CDOLockUtil.convertLockState(serverLockState);
+      add(commonLockState);
     }
-    catch (LockAreaNotFoundException e)
+  }
+
+  /**
+   * @deprecated
+   * @category Read Access
+   */
+  @Deprecated
+  @Override
+  public Object getLockEntryObject(Object key)
+  {
+    try (Access access = read.access())
     {
-      return null;
+      LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
+      return lockState == null ? null : lockState.getLockedObject();
     }
   }
 
   @Override
-  public void updateLockArea(LockArea lockArea)
+  @Deprecated
+  public void lock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
   {
-    String durableLockingID = lockArea.getDurableLockingID();
-    DurableLocking2 accessor = getDurableLocking2();
+    throw new UnsupportedOperationException();
+  }
 
-    if (lockArea.isMissing())
-    {
-      LockArea localLockArea = getLockAreaNoEx(durableLockingID);
-      if (localLockArea != null && localLockArea.getLocks().size() > 0)
-      {
-        accessor.deleteLockArea(durableLockingID);
-        DurableView deletedView = durableViews.remove(durableLockingID);
-        CheckUtil.checkNull(deletedView, "deletedView");
-      }
-    }
-    else
-    {
-      accessor.updateLockArea(lockArea);
-      new DurableLockLoader().handleLockArea(lockArea);
-    }
+  @Override
+  @Deprecated
+  public void unlock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(boolean explicit, IView view)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void lock(LockType type, IView view, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void lock(LockType type, IView view, Object objectToLock, long timeout) throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> lock2(LockType type, IView view, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> lock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, boolean recursive,
+      long timeout) throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(IView view)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(LockType type, IView view, Collection<? extends Object> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objects, boolean recursive)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(boolean explicit, IView view)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RWOLockManager.LockState<Object, IView>> unlock2(IView view)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RWOLockManager.LockState<Object, IView>> unlock2(IView view, Collection<? extends Object> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RWOLockManager.LockState<Object, IView>> unlock2(LockType type, IView view, Collection<? extends Object> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public IView openView(ISession session, int viewID, boolean readOnly, String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
index 2dcb2d3..06d3613 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
@@ -15,7 +15,6 @@
  */
 package org.eclipse.emf.cdo.internal.server;
 
-import org.eclipse.emf.cdo.common.CDOCommonView;
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.branch.CDOBranchHandler;
 import org.eclipse.emf.cdo.common.branch.CDOBranchManager.CDOTagList;
@@ -33,6 +32,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLobHandler;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -57,6 +57,8 @@
 import org.eclipse.emf.cdo.eresource.EresourcePackage;
 import org.eclipse.emf.cdo.etypes.EtypesPackage;
 import org.eclipse.emf.cdo.internal.common.model.CDOPackageRegistryImpl;
+import org.eclipse.emf.cdo.internal.server.LockingManager.LockDeltaCollector;
+import org.eclipse.emf.cdo.internal.server.LockingManager.LockStateCollector;
 import org.eclipse.emf.cdo.internal.server.bundle.OM;
 import org.eclipse.emf.cdo.server.IQueryHandler;
 import org.eclipse.emf.cdo.server.IQueryHandlerProvider;
@@ -118,7 +120,7 @@
 import org.eclipse.net4j.util.collection.Pair;
 import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
-import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
+import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
 import org.eclipse.net4j.util.container.Container;
 import org.eclipse.net4j.util.container.IManagedContainer;
@@ -146,7 +148,6 @@
 import java.io.OutputStream;
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -171,7 +172,9 @@
  */
 public class Repository extends Container<Object> implements InternalRepository
 {
-  public static final CDOLockState[] NO_LOCK_STATES = new CDOLockState[0];
+  private static final List<CDOLockDelta> NO_LOCK_DELTAS = Collections.emptyList();
+
+  private static final List<CDOLockState> NO_LOCK_STATES = Collections.emptyList();
 
   private static final int UNCHUNKED = CDORevision.UNCHUNKED;
 
@@ -421,13 +424,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isSupportingEcore()
-  {
-    return true;
-  }
-
-  @Override
   public boolean isSerializingCommits()
   {
     return serializingCommits;
@@ -613,20 +609,6 @@
   }
 
   @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void renameBranch(int branchID, String oldName, String newName)
   {
     if (!isSupportingBranches())
@@ -763,13 +745,6 @@
   }
 
   @Override
-  @Deprecated
-  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth,
       boolean prefetchLockStates)
   {
@@ -1383,17 +1358,6 @@
     this.commitManager = commitManager;
   }
 
-  /**
-   * @since 2.0
-   * @deprecated
-   */
-  @Override
-  @Deprecated
-  public InternalLockManager getLockManager()
-  {
-    return getLockingManager();
-  }
-
   @Override
   public InternalLockManager getLockingManager()
   {
@@ -1535,45 +1499,11 @@
   }
 
   @Override
-  @Deprecated
-  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo)
-  {
-    sendCommitNotification(sender, commitInfo, true);
-  }
-
-  @Override
-  @Deprecated
-  public CDOCommitInfoHandler[] getCommitInfoHandlers()
-  {
-    return commitInfoManager.getCommitInfoHandlers();
-  }
-
-  @Override
-  @Deprecated
-  public void addCommitInfoHandler(CDOCommitInfoHandler handler)
-  {
-    commitInfoManager.addCommitInfoHandler(handler);
-  }
-
-  @Override
-  @Deprecated
-  public void removeCommitInfoHandler(CDOCommitInfoHandler handler)
-  {
-    commitInfoManager.removeCommitInfoHandler(handler);
-  }
-
-  @Override
-  @Deprecated
-  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo, boolean clearResourcePathCache)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void sendCommitNotification(CommitNotificationInfo info)
   {
     CDOCommitInfo commitInfo = info.getCommitInfo();
     boolean isFailureCommitInfo = commitInfo.getBranch() == null;
+
     if (isFailureCommitInfo || !commitInfo.isEmpty() || info.getLockChangeInfo() != null)
     {
       sessionManager.sendCommitNotification(info);
@@ -1994,17 +1924,6 @@
   }
 
   @Override
-  @Deprecated
-  public Set<CDOID> getMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
-      CDORevisionAvailabilityInfo sourceBaseInfo, OMMonitor monitor)
-  {
-    MergeDataResult result = getMergeData2(targetInfo, sourceInfo, targetBaseInfo, sourceBaseInfo, monitor);
-    Set<CDOID> ids = result.getTargetIDs();
-    ids.addAll(result.getSourceIDs());
-    return ids;
-  }
-
-  @Override
   public MergeDataResult getMergeData2(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo,
       CDORevisionAvailabilityInfo targetBaseInfo, CDORevisionAvailabilityInfo sourceBaseInfo, OMMonitor monitor)
   {
@@ -2283,81 +2202,6 @@
     }
   }
 
-  public static List<Object> revisionKeysToObjects(List<CDORevisionKey> revisionKeys, CDOBranch viewedBranch, boolean isSupportingBranches)
-  {
-    List<Object> lockables = new ArrayList<>();
-    for (CDORevisionKey revKey : revisionKeys)
-    {
-      CDOID id = revKey.getID();
-      if (isSupportingBranches)
-      {
-        lockables.add(CDOIDUtil.createIDAndBranch(id, viewedBranch));
-      }
-      else
-      {
-        lockables.add(id);
-      }
-    }
-
-    return lockables;
-  }
-
-  @Override
-  public LockObjectsResult lock(InternalView view, LockType lockType, List<CDORevisionKey> revKeys, boolean recursive, long timeout)
-  {
-    List<Object> lockables = revisionKeysToObjects(revKeys, view.getBranch(), isSupportingBranches());
-    return lock(view, lockType, lockables, revKeys, recursive, timeout);
-  }
-
-  protected LockObjectsResult lock(InternalView view, LockType type, List<Object> lockables, List<CDORevisionKey> loadedRevs, boolean recursive, long timeout)
-  {
-    List<LockState<Object, IView>> newLockStates = null;
-
-    try
-    {
-      newLockStates = getLockingManager().lock2(true, type, view, lockables, recursive, timeout);
-    }
-    catch (TimeoutRuntimeException ex)
-    {
-      return new LockObjectsResult(false, true, false, 0, new CDORevisionKey[0], NO_LOCK_STATES, getTimeStamp());
-    }
-    catch (InterruptedException ex)
-    {
-      throw WrappedException.wrap(ex);
-    }
-
-    long[] requiredTimestamp = { 0L };
-    CDORevisionKey[] staleRevisionsArray = null;
-
-    try
-    {
-      staleRevisionsArray = checkStaleRevisions(view, loadedRevs, lockables, type, requiredTimestamp);
-    }
-    catch (IllegalArgumentException e)
-    {
-      getLockingManager().unlock2(true, type, view, lockables, recursive);
-      throw e;
-    }
-
-    // If some of the clients' revisions are stale and it has passiveUpdates disabled,
-    // then the locks are useless so we release them and report the stale revisions
-    //
-    InternalSession session = view.getSession();
-    boolean staleNoUpdate = staleRevisionsArray.length > 0 && !session.isPassiveUpdateEnabled();
-    if (staleNoUpdate)
-    {
-      getLockingManager().unlock2(true, type, view, lockables, recursive);
-      return new LockObjectsResult(false, false, false, requiredTimestamp[0], staleRevisionsArray, NO_LOCK_STATES, getTimeStamp());
-    }
-
-    CDOLockState[] array = toCDOLockStates(newLockStates);
-    List<CDOLockState> cdoLockStates = Arrays.asList(array);
-    sendLockNotifications(view, Operation.LOCK, type, cdoLockStates);
-
-    boolean waitForUpdate = staleRevisionsArray.length > 0;
-    return new LockObjectsResult(true, false, waitForUpdate, requiredTimestamp[0], staleRevisionsArray, array, getTimeStamp());
-  }
-
   private CDORevisionKey[] checkStaleRevisions(InternalView view, List<CDORevisionKey> revisionKeys, List<Object> objectsToLock, LockType lockType,
       long[] requiredTimestamp)
   {
@@ -2392,39 +2236,82 @@
     return staleRevisionsArray;
   }
 
-  private void sendLockNotifications(IView view, Operation operation, LockType lockType, Collection<? extends CDOLockState> cdoLockStates)
+  private void sendLockNotifications(IView view, List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
     CDOBranchPoint branchPoint = view.getBranch().getPoint(getTimeStamp());
     CDOLockOwner lockOwner = view.getLockOwner();
-    CDOLockChangeInfo lockChangeInfo = CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, operation, lockType, cdoLockStates);
+    CDOLockChangeInfo lockChangeInfo = CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, lockDeltas, lockStates);
 
     InternalSession sender = (InternalSession)view.getSession();
     sessionManager.sendLockNotification(sender, lockChangeInfo);
   }
 
-  // TODO (CD) This doesn't really belong here.. but getting it into CDOLockUtil isn't possible
-  public static CDOLockState[] toCDOLockStates(Collection<? extends LockState<Object, IView>> lockStates)
+  @Override
+  public LockObjectsResult lock(InternalView view, LockType lockType, List<CDORevisionKey> revKeys, boolean recursive, long timeout)
   {
-    CDOLockState[] cdoLockStates = new CDOLockState[lockStates.size()];
-    int i = 0;
+    List<Object> lockables = revisionKeysToObjects(revKeys, view.getBranch(), isSupportingBranches());
+    return doLock(view, lockType, lockables, revKeys, recursive, timeout);
+  }
 
-    for (LockState<Object, ? extends CDOCommonView> lockState : lockStates)
+  protected LockObjectsResult doLock(InternalView view, LockType lockType, List<Object> lockables, List<CDORevisionKey> loadedRevs, boolean recursive,
+      long timeout)
+  {
+    LockDeltaCollector lockDeltas = new LockDeltaCollector(Operation.LOCK);
+    LockStateCollector lockStates = new LockStateCollector();
+
+    try
     {
-      CDOLockState cdoLockState = CDOLockUtil.convertLockState(lockState);
-      cdoLockStates[i++] = cdoLockState;
+      lockingManager.lock(view, lockables, lockType, 1, timeout, recursive, true, lockDeltas, lockStates);
+    }
+    catch (TimeoutRuntimeException ex)
+    {
+      return new LockObjectsResult(false, true, false, 0, new CDORevisionKey[0], NO_LOCK_DELTAS, NO_LOCK_STATES, getTimeStamp());
+    }
+    catch (InterruptedException ex)
+    {
+      throw WrappedException.wrap(ex);
     }
 
-    return cdoLockStates;
+    long[] requiredTimestamp = { 0L };
+    CDORevisionKey[] staleRevisionsArray = null;
+
+    try
+    {
+      staleRevisionsArray = checkStaleRevisions(view, loadedRevs, lockables, lockType, requiredTimestamp);
+    }
+    catch (IllegalArgumentException ex)
+    {
+      lockingManager.unlock(view, lockables, lockType, 1, recursive, true, null, null);
+      throw ex;
+    }
+
+    // If some of the clients' revisions are stale and it has passiveUpdates disabled,
+    // then the locks are useless so we release them and report the stale revisions
+    //
+    InternalSession session = view.getSession();
+    boolean staleNoUpdate = staleRevisionsArray.length > 0 && !session.isPassiveUpdateEnabled();
+    if (staleNoUpdate)
+    {
+      lockingManager.unlock(view, lockables, lockType, 1, recursive, true, null, null);
+      return new LockObjectsResult(false, false, false, requiredTimestamp[0], staleRevisionsArray, NO_LOCK_DELTAS, NO_LOCK_STATES, getTimeStamp());
+    }
+
+    sendLockNotifications(view, lockDeltas, lockStates);
+
+    boolean waitForUpdate = staleRevisionsArray.length > 0;
+    return new LockObjectsResult(true, false, waitForUpdate, requiredTimestamp[0], staleRevisionsArray, lockDeltas, lockStates, getTimeStamp());
   }
 
   @Override
   public UnlockObjectsResult unlock(InternalView view, LockType lockType, List<CDOID> objectIDs, boolean recursive)
   {
     List<Object> unlockables = null;
+
     if (objectIDs != null)
     {
       unlockables = new ArrayList<>(objectIDs.size());
       CDOBranch branch = view.getBranch();
+
       for (CDOID id : objectIDs)
       {
         Object key = supportingBranches ? CDOIDUtil.createIDAndBranch(id, branch) : id;
@@ -2432,26 +2319,26 @@
       }
     }
 
-    return doUnlock(view, lockType, unlockables, recursive);
+    return doUnlock(view, lockType, unlockables, recursive, 1);
   }
 
-  protected UnlockObjectsResult doUnlock(InternalView view, LockType lockType, List<Object> unlockables, boolean recursive)
+  @Override
+  public UnlockObjectsResult unlock(InternalView view)
   {
-    List<LockState<Object, IView>> newLockStates = null;
-    if (lockType == null) // Signals an unlock-all operation
-    {
-      newLockStates = getLockingManager().unlock2(true, view);
-    }
-    else
-    {
-      newLockStates = getLockingManager().unlock2(true, lockType, view, unlockables, recursive);
-    }
+    return doUnlock(view, null, null, false, IRWOLockManager.ALL_LOCKS);
+  }
+
+  protected UnlockObjectsResult doUnlock(InternalView view, LockType lockType, List<Object> unlockables, boolean recursive, int count)
+  {
+    LockDeltaCollector lockDeltas = new LockDeltaCollector(Operation.UNLOCK);
+    LockStateCollector lockStates = new LockStateCollector();
+
+    lockingManager.unlock(view, unlockables, lockType, count, recursive, true, lockDeltas, lockStates);
+
+    sendLockNotifications(view, lockDeltas, lockStates);
 
     long timestamp = getTimeStamp();
-    CDOLockState[] cdoLockStates = toCDOLockStates(newLockStates);
-    sendLockNotifications(view, Operation.UNLOCK, lockType, Arrays.asList(cdoLockStates));
-
-    return new UnlockObjectsResult(cdoLockStates, timestamp);
+    return new UnlockObjectsResult(timestamp, lockDeltas, lockStates);
   }
 
   @Override
@@ -2569,13 +2456,6 @@
   }
 
   @Override
-  @Deprecated
-  public void initSystemPackages()
-  {
-    initSystemPackages(true);
-  }
-
-  @Override
   public void initSystemPackages(final boolean firstStart)
   {
     List<InternalCDOPackageUnit> newPackageUnits = new ArrayList<>();
@@ -3040,6 +2920,25 @@
     super.doDeactivate();
   }
 
+  public static List<Object> revisionKeysToObjects(List<CDORevisionKey> revisionKeys, CDOBranch viewedBranch, boolean isSupportingBranches)
+  {
+    List<Object> lockables = new ArrayList<>();
+    for (CDORevisionKey revKey : revisionKeys)
+    {
+      CDOID id = revKey.getID();
+      if (isSupportingBranches)
+      {
+        lockables.add(CDOIDUtil.createIDAndBranch(id, viewedBranch));
+      }
+      else
+      {
+        lockables.add(id);
+      }
+    }
+
+    return lockables;
+  }
+
   public static Repository get(String uuid)
   {
     synchronized (REPOSITORIES)
@@ -3101,9 +3000,9 @@
         setCommitManager(createCommitManager());
       }
 
-      if (getLockManager() == null)
+      if (getLockingManager() == null)
       {
-        setLockingManager(createLockManager());
+        setLockingManager(createLockingManager());
       }
 
       if (getUnitManager() == null)
@@ -3159,15 +3058,94 @@
       return new UnitManager(this);
     }
 
-    @Deprecated
-    protected InternalLockManager createLockManager()
-    {
-      return createLockingManager();
-    }
-
     public LockingManager createLockingManager()
     {
       return new LockingManager();
     }
   }
+
+  @Override
+  @Deprecated
+  public boolean isSupportingEcore()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public InternalLockManager getLockManager()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOCommitInfoHandler[] getCommitInfoHandlers()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void addCommitInfoHandler(CDOCommitInfoHandler handler)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void removeCommitInfoHandler(CDOCommitInfoHandler handler)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo, boolean clearResourcePathCache)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public Set<CDOID> getMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
+      CDORevisionAvailabilityInfo sourceBaseInfo, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void initSystemPackages()
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
index 2ae3076..d70388a 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
@@ -195,13 +195,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isInvalidationRunnerActive()
-  {
-    return isInvalidating();
-  }
-
-  @Override
   public boolean isInvalidating()
   {
     return false;
@@ -298,13 +291,6 @@
   }
 
   @Override
-  @Deprecated
-  public String enableDurableLocking(boolean enable)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public String enableDurableLocking()
   {
     throw new UnsupportedOperationException();
@@ -337,22 +323,6 @@
   }
 
   @Override
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void invalidate(ViewInvalidationData invalidationData)
   {
     throw new UnsupportedOperationException();
@@ -365,19 +335,6 @@
   }
 
   @Override
-  @Deprecated
-  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
-  {
-    // Do nothing
-  }
-
-  @Override
-  public void updateLockStates(Collection<? extends CDOLockState> newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
-  {
-    // Do nothing
-  }
-
-  @Override
   public void resourceLoaded(CDOResourceImpl resource, boolean loaded)
   {
     // Do nothing
@@ -503,29 +460,6 @@
     throw new UnsupportedOperationException();
   }
 
-  @Deprecated
-  public org.eclipse.emf.internal.cdo.view.CDOLockStateLoadingPolicy getLockStateLoadingPolicy()
-  {
-    return null;
-  }
-
-  @Deprecated
-  public void setLockStateLoadingPolicy(org.eclipse.emf.internal.cdo.view.CDOLockStateLoadingPolicy lockStateLoadingPolicy)
-  {
-  }
-
-  @Deprecated
-  public boolean isLockStatePrefetchEnabled()
-  {
-    return false;
-  }
-
-  @Deprecated
-  public void setLockStatePrefetchEnabled(boolean enabled)
-  {
-    throw new UnsupportedOperationException();
-  }
-
   @Override
   public CDOAdapterPolicy[] getChangeSubscriptionPolicies()
   {
@@ -557,20 +491,6 @@
   }
 
   @Override
-  @Deprecated
-  public CDOStaleReferencePolicy getStaleReferenceBehaviour()
-  {
-    return getStaleReferencePolicy();
-  }
-
-  @Override
-  @Deprecated
-  public void setStaleReferenceBehaviour(CDOStaleReferencePolicy policy)
-  {
-    setStaleReferencePolicy(policy);
-  }
-
-  @Override
   public CDOStaleReferencePolicy getStaleReferencePolicy()
   {
     return CDOStaleReferencePolicy.DEFAULT;
@@ -637,6 +557,81 @@
   }
 
   @Override
+  @Deprecated
+  public boolean isInvalidationRunnerActive()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public String enableDurableLocking(boolean enable)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  public org.eclipse.emf.internal.cdo.view.CDOLockStateLoadingPolicy getLockStateLoadingPolicy()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  public void setLockStateLoadingPolicy(org.eclipse.emf.internal.cdo.view.CDOLockStateLoadingPolicy lockStateLoadingPolicy)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  public boolean isLockStatePrefetchEnabled()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
+  public void setLockStatePrefetchEnabled(boolean enabled)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOStaleReferencePolicy getStaleReferenceBehaviour()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void setStaleReferenceBehaviour(CDOStaleReferencePolicy policy)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   protected void doBeforeDeactivate() throws Exception
   {
     closing = true;
@@ -768,20 +763,6 @@
     /**
      * Server sessions may not be used to change the user's credentials: it must
      * be done client-side by interaction with the user.
-     *
-     * @since 4.3
-     * @deprecated
-     */
-    @Override
-    @Deprecated
-    public void changeCredentials()
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Server sessions may not be used to change the user's credentials: it must
-     * be done client-side by interaction with the user.
      */
     @Override
     public char[] changeServerPassword()
@@ -988,13 +969,6 @@
     }
 
     @Override
-    @Deprecated
-    public boolean isSupportingEcore()
-    {
-      return repository.isSupportingEcore();
-    }
-
-    @Override
     public boolean isSerializingCommits()
     {
       return repository.isSerializingCommits();
@@ -1348,60 +1322,17 @@
     }
 
     @Override
-    @Deprecated
-    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @Deprecated
-    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @Deprecated
-    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache, byte securityImpact,
-        Map<CDOID, CDOPermission> newPermissions)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
     public void invalidate(InvalidationData invalidationData)
     {
       throw new UnsupportedOperationException();
     }
 
     @Override
-    @Deprecated
-    public void handleCommitNotification(CDOCommitInfo commitInfo)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @Deprecated
-    public void handleCommitNotification(CDOCommitInfo commitInfo, boolean clearResourcePathCache)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
     public void handleCommitNotification(CommitNotificationInfo info)
     {
       throw new UnsupportedOperationException();
     }
 
-    @Deprecated
-    @Override
-    public void handleLockNotification(CDOLockChangeInfo lockChangeInfo, InternalCDOView sender)
-    {
-      throw new UnsupportedOperationException();
-    }
-
     @Override
     public void handleLockNotification(CDOLockChangeInfo lockChangeInfo, InternalCDOView sender, boolean async)
     {
@@ -1409,13 +1340,6 @@
     }
 
     @Override
-    @Deprecated
-    public void handleBranchNotification(InternalCDOBranch branch)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
     public void handleViewClosed(int viewID)
     {
       throw new UnsupportedOperationException();
@@ -1428,20 +1352,6 @@
     }
 
     @Override
-    @Deprecated
-    public org.eclipse.emf.cdo.common.protocol.CDOAuthenticator getAuthenticator()
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @Deprecated
-    public void setAuthenticator(org.eclipse.emf.cdo.common.protocol.CDOAuthenticator authenticator)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
     public IPasswordCredentialsProvider getCredentialsProvider()
     {
       throw new UnsupportedOperationException();
@@ -1508,20 +1418,6 @@
     }
 
     @Override
-    @Deprecated
-    public CDORevisionAvailabilityInfo createRevisionAvailabilityInfo(CDOBranchPoint branchPoint)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @Deprecated
-    public void cacheRevisions(CDORevisionAvailabilityInfo info)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
     public MergeData getMergeData(CDOBranchPoint target, CDOBranchPoint source, CDOBranchPoint sourceBase, boolean computeChangeSets)
     {
       throw new UnsupportedOperationException();
@@ -1670,5 +1566,97 @@
     {
       throw new UnsupportedOperationException();
     }
+
+    @Override
+    @Deprecated
+    public void changeCredentials()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public boolean isSupportingEcore()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache, byte securityImpact,
+        Map<CDOID, CDOPermission> newPermissions)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void handleCommitNotification(CDOCommitInfo commitInfo)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void handleCommitNotification(CDOCommitInfo commitInfo, boolean clearResourcePathCache)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void handleLockNotification(CDOLockChangeInfo lockChangeInfo, InternalCDOView sender)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void handleBranchNotification(InternalCDOBranch branch)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public org.eclipse.emf.cdo.common.protocol.CDOAuthenticator getAuthenticator()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void setAuthenticator(org.eclipse.emf.cdo.common.protocol.CDOAuthenticator authenticator)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public CDORevisionAvailabilityInfo createRevisionAvailabilityInfo(CDOBranchPoint branchPoint)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @Deprecated
+    public void cacheRevisions(CDORevisionAvailabilityInfo info)
+    {
+      throw new UnsupportedOperationException();
+    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
index 9448ad9..625c212 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
@@ -24,7 +24,7 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
@@ -764,9 +764,9 @@
       {
         Map<CDOID, LockGrade> locks = lockingManager.getLocks(view);
 
-        for (CDOLockState lockState : lockChangeInfo.getNewLockStates())
+        for (CDOLockDelta delta : lockChangeInfo.getLockDeltas())
         {
-          Object lockedObject = lockState.getLockedObject();
+          Object lockedObject = delta.getTarget();
           CDOID id = CDOLockUtil.getLockedObjectID(lockedObject);
 
           LockGrade lockGrade = locks.get(id);
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
index 05a06ad..fbea64a 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
@@ -26,6 +26,7 @@
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -49,6 +50,8 @@
 import org.eclipse.emf.cdo.common.util.CDOQueryInfo;
 import org.eclipse.emf.cdo.internal.common.commit.FailureCommitInfo;
 import org.eclipse.emf.cdo.internal.common.model.CDOPackageRegistryImpl;
+import org.eclipse.emf.cdo.internal.server.LockingManager.LockDeltaCollector;
+import org.eclipse.emf.cdo.internal.server.LockingManager.LockStateCollector;
 import org.eclipse.emf.cdo.internal.server.bundle.OM;
 import org.eclipse.emf.cdo.server.IRepository;
 import org.eclipse.emf.cdo.server.IStoreAccessor;
@@ -82,11 +85,10 @@
 import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.collection.IndexedList;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
-import org.eclipse.net4j.util.concurrent.RWOLockManager;
+import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
 import org.eclipse.net4j.util.io.ExtendedDataInputStream;
 import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
-import org.eclipse.net4j.util.om.monitor.Monitor;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
 import org.eclipse.net4j.util.om.trace.ContextTracer;
 
@@ -97,7 +99,6 @@
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -126,8 +127,6 @@
 
   private InternalRepository repository;
 
-  private InternalLockManager lockManager;
-
   private InternalCDOPackageRegistry repositoryPackageRegistry;
 
   private boolean packageRegistryLocked;
@@ -180,10 +179,6 @@
 
   private CommitData originalCommmitData;
 
-  private Set<Object> lockedObjects = new HashSet<>();
-
-  private List<CDOID> lockedTargets;
-
   private Map<CDOID, CDOID> idMappings = CDOIDUtil.createMap();
 
   private CDOReferenceAdjuster idMapper = new CDOIDMapper(idMappings);
@@ -202,18 +197,49 @@
 
   private ExtendedDataInputStream lobs;
 
-  private CDOLockState[] locksOnNewObjects = Repository.NO_LOCK_STATES;
+  private long optimisticLockingTimeout;
 
+  private InternalLockManager lockManager;
+
+  /**
+   * The keys of objects that are locked in lockObjects().
+   */
+  private Set<Object> lockedObjects = new HashSet<>();
+
+  /**
+   * The IDs of referenced objects that are locked in lockObjects(), only relevant if ensuringReferentialIntegrity.
+   */
+  private List<CDOID> lockedTargets;
+
+  /**
+   * The lock states on new objects that the client wants to keep after the commit.
+   */
+  private CDOLockState[] locksOnNewObjects = CDOLockUtil.NO_LOCK_STATES;
+
+  /**
+   * The IDs of objects that the client wants to release after the commit, as per autoReleaseLocks.
+   */
   private CDOID[] idsToUnlock = new CDOID[0];
 
+  /**
+   * The lock deltas that are visible after the commit.
+   */
+  private final LockDeltaCollector lockDeltas = new LockDeltaCollector();
+
+  /**
+   * The lock states that are visible after the commit.
+   */
+  private final LockStateCollector lockStates = new LockStateCollector();
+
+  /**
+   * The lock state changes that are visible after the commit.
+   */
+  private CDOLockChangeInfo lockChangeInfo;
+
   private Map<Object, Object> data;
 
   private CommitNotificationInfo commitNotificationInfo = new CommitNotificationInfo();
 
-  private CDOLockChangeInfo lockChangeInfo;
-
-  private final List<LockState<Object, IView>> postCommitLockStates = new ArrayList<>();
-
   private boolean allowModify;
 
   public TransactionCommitContext(InternalTransaction transaction)
@@ -659,6 +685,12 @@
   }
 
   @Override
+  public void setOptimisticLockingTimeout(long optimisticLockingTimeout)
+  {
+    this.optimisticLockingTimeout = optimisticLockingTimeout;
+  }
+
+  @Override
   public void setCommitNumber(int commitNumber)
   {
     this.commitNumber = commitNumber;
@@ -689,20 +721,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isAutoReleaseLocksEnabled()
-  {
-    return false;
-  }
-
-  @Override
-  @Deprecated
-  public void setAutoReleaseLocksEnabled(boolean on)
-  {
-    // Do nothing.
-  }
-
-  @Override
   public CDOLockState[] getLocksOnNewObjects()
   {
     return locksOnNewObjects;
@@ -868,12 +886,6 @@
     }
   }
 
-  @Override
-  public List<LockState<Object, IView>> getPostCommmitLockStates()
-  {
-    return postCommitLockStates;
-  }
-
   protected void handleException(Throwable throwable)
   {
     try
@@ -955,16 +967,6 @@
     return timeStamp;
   }
 
-  /**
-   * @deprecated Does not seem to be used.
-   */
-  @Deprecated
-  protected void setTimeStamp(long timeStamp)
-  {
-    repository.forceCommitTimeStamp(timeStamp, new Monitor());
-    this.timeStamp = timeStamp;
-  }
-
   @Override
   public long getPreviousTimeStamp()
   {
@@ -972,6 +974,18 @@
   }
 
   @Override
+  public List<CDOLockDelta> getLockDeltas()
+  {
+    return lockDeltas;
+  }
+
+  @Override
+  public List<CDOLockState> getLockStates()
+  {
+    return lockStates;
+  }
+
+  @Override
   public void postCommit(boolean success)
   {
     try
@@ -1195,11 +1209,13 @@
       {
         try
         {
-          long timeout = repository.getOptimisticLockingTimeout();
+          long timeout = optimisticLockingTimeout == CDOProtocolConstants.DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT //
+              ? repository.getOptimisticLockingTimeout() //
+              : optimisticLockingTimeout;
 
           // First lock all objects (incl. possible ref targets).
-          // This is a transient operation, it does not check for existence!
-          lockManager.lock2(LockType.WRITE, transaction, lockedObjects, timeout);
+          // This is a transient operation, it does not check for existence of the ref targets!
+          lockManager.lock(transaction, lockedObjects, LockType.WRITE, 1, timeout, false, false, null, null);
         }
         catch (Exception ex)
         {
@@ -1254,7 +1270,7 @@
       Object key = lockManager.getLockKey(id, branch);
       lockedObjects.add(key);
 
-      // Let this object be checked for existance after it has been locked
+      // Let this object be checked for existence after it has been locked
       if (lockedTargets == null)
       {
         lockedTargets = new ArrayList<>();
@@ -1665,6 +1681,10 @@
     return accessor;
   }
 
+  /**
+   * Called from commit().
+   * Remembers the lockChangeInfo for postCommit().
+   */
   protected void updateInfraStructure(OMMonitor monitor)
   {
     try
@@ -1678,18 +1698,21 @@
       InternalCDOCommitInfoManager commitInfoManager = repository.getCommitInfoManager();
       commitInfoManager.setLastCommitOfBranch(branch, timeStamp);
 
-      releaseImplicitLocks();
+      releaseImplicitLocks(); // These have no visible impact outside of this commit.
       monitor.worked();
 
-      acquireLocksOnNewObjects();
+      CDOLockOwner lockOwner = CDOLockUtil.createLockOwner(transaction);
+
+      acquireLocksOnNewObjects(lockOwner);
       monitor.worked();
 
-      autoReleaseExplicitLocks();
+      autoReleaseExplicitLocks(lockOwner);
       monitor.worked();
 
-      if (!postCommitLockStates.isEmpty())
+      if (!lockDeltas.isEmpty())
       {
-        lockChangeInfo = createLockChangeInfo(postCommitLockStates);
+        CDOBranchPoint branchPoint = getBranchPoint();
+        lockChangeInfo = CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, lockDeltas, lockStates);
       }
 
       repository.notifyWriteAccessHandlers(transaction, this, false, monitor.fork());
@@ -1704,24 +1727,24 @@
     }
   }
 
-  protected synchronized void releaseImplicitLocks()
+  protected void releaseImplicitLocks()
   {
     // Unlock objects locked during commit
     if (!lockedObjects.isEmpty())
     {
-      lockManager.unlock2(LockType.WRITE, transaction, lockedObjects);
+      lockManager.unlock(transaction, lockedObjects, LockType.WRITE, 1, false, false, null, null);
       lockedObjects.clear();
     }
   }
 
-  protected void acquireLocksOnNewObjects() throws InterruptedException
+  protected void acquireLocksOnNewObjects(CDOLockOwner lockOwner) throws InterruptedException
   {
-    final CDOLockOwner owner = CDOLockUtil.createLockOwner(transaction);
-    final boolean mapIDs = transaction.getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE;
+    boolean mapIDs = transaction.getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE;
+    lockDeltas.setOperation(Operation.LOCK);
 
-    for (CDOLockState lockState : locksOnNewObjects)
+    for (CDOLockState lockStateOnNewObject : locksOnNewObjects)
     {
-      Object target = lockState.getLockedObject();
+      Object target = lockStateOnNewObject.getLockedObject();
 
       if (mapIDs)
       {
@@ -1733,24 +1756,18 @@
         target = idAndBranch != null ? CDOIDUtil.createIDAndBranch(newID, idAndBranch.getBranch()) : newID;
       }
 
-      LockState<Object, IView> postCommitLockState = null;
       for (LockType type : ALL_LOCK_TYPES)
       {
-        if (lockState.isLocked(type, owner, false))
+        if (lockStateOnNewObject.isLocked(type, lockOwner, false))
         {
-          List<LockState<Object, IView>> lockStates = lockManager.lock2(type, transaction, Collections.singleton(target), 0);
-          postCommitLockState = lockStates.get(0);
+          Set<Object> objects = Collections.singleton(target);
+          lockManager.lock(transaction, objects, type, 1, IRWOLockManager.NO_TIMEOUT, false, true, lockDeltas, lockStates);
         }
       }
-
-      if (postCommitLockState != null)
-      {
-        postCommitLockStates.add(postCommitLockState);
-      }
     }
   }
 
-  protected void autoReleaseExplicitLocks() throws InterruptedException
+  protected void autoReleaseExplicitLocks(CDOLockOwner lockOwner) throws InterruptedException
   {
     List<Object> targets = new ArrayList<>();
 
@@ -1772,29 +1789,8 @@
       }
     }
 
-    try
-    {
-      RWOLockManager.setUnlockAll(true);
-
-      List<LockState<Object, IView>> lockStates = lockManager.unlock2(true, LockType.WRITE, transaction, targets, false);
-      if (lockStates != null)
-      {
-        postCommitLockStates.addAll(lockStates);
-      }
-    }
-    finally
-    {
-      RWOLockManager.setUnlockAll(false);
-    }
-  }
-
-  protected CDOLockChangeInfo createLockChangeInfo(List<LockState<Object, IView>> newLockStates)
-  {
-    CDOBranchPoint branchPoint = getBranchPoint();
-    CDOLockOwner lockOwner = transaction.getLockOwner();
-    List<CDOLockState> newStates = Arrays.asList(Repository.toCDOLockStates(newLockStates));
-
-    return CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, Operation.UNLOCK, null, newStates);
+    lockDeltas.setOperation(Operation.UNLOCK);
+    lockManager.unlock(transaction, targets, null, IRWOLockManager.ALL_LOCKS, false, true, lockDeltas, lockStates);
   }
 
   protected void addNewPackageUnits(OMMonitor monitor)
@@ -1905,6 +1901,36 @@
         CDOCommonUtil.formatTimeStamp(timeStamp));
   }
 
+  @Override
+  @Deprecated
+  public boolean isAutoReleaseLocksEnabled()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void setAutoReleaseLocksEnabled(boolean on)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> getPostCommmitLockStates()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @deprecated Does not seem to be used.
+   */
+  @Deprecated
+  protected void setTimeStamp(long timeStamp)
+  {
+    throw new UnsupportedOperationException();
+  }
+
   /**
    * @author Eike Stepper
    */
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
index 517b521..5b62e06 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
@@ -160,13 +160,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isLocal(CDOID id)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void ensureLastObjectID(CDOID id)
   {
     if (getRepository().getIDGenerationLocation() == IDGenerationLocation.CLIENT)
@@ -275,7 +268,7 @@
   @Override
   public synchronized CDOBranch[] deleteBranches(int branchID, OMMonitor monitor)
   {
-    Set<CDOBranch> branches = getRepository().getBranchManager().getBranches(branchID);
+    Set<CDOBranch> deletedBranches = getRepository().getBranchManager().getBranches(branchID);
 
     monitor.begin(revisions.size());
 
@@ -284,7 +277,7 @@
       Map.Entry<Object, List<InternalCDORevision>> entry = it.next();
 
       CDOBranch revisionBranch = getBranch(entry.getKey());
-      if (branches.contains(revisionBranch))
+      if (deletedBranches.contains(revisionBranch))
       {
         it.remove();
       }
@@ -292,26 +285,31 @@
       monitor.worked();
     }
 
-    for (CDOBranch b : branches)
+    for (Iterator<CommitInfo> it = commitInfos.iterator(); it.hasNext();)
     {
-      branchInfos.remove(b.getID());
+      CommitInfo commitInfo = it.next();
+      if (deletedBranches.contains(commitInfo.getBranch()))
+      {
+        it.remove();
+      }
     }
 
-    return branches.toArray(new CDOBranch[branches.size()]);
-  }
+    for (Iterator<Map.Entry<String, LockArea>> it = lockAreas.entrySet().iterator(); it.hasNext();)
+    {
+      Map.Entry<String, LockArea> entry = it.next();
+      LockArea lockArea = entry.getValue();
+      if (deletedBranches.contains(lockArea.getBranch()))
+      {
+        it.remove();
+      }
+    }
 
-  @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
+    for (CDOBranch branch : deletedBranches)
+    {
+      branchInfos.remove(branch.getID());
+    }
 
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
+    return deletedBranches.toArray(new CDOBranch[deletedBranches.size()]);
   }
 
   @Override
@@ -978,30 +976,33 @@
     LockArea area = getLockArea(durableLockingID);
     Map<CDOID, LockGrade> locks = area.getLocks();
 
-    InternalLockManager lockManager = getRepository().getLockingManager();
-    for (Object objectToUnlock : objectsToUnlock)
+    if (objectsToUnlock == null)
     {
-      CDOID id = lockManager.getLockKeyID(objectToUnlock);
-      LockGrade grade = locks.get(id);
-      if (grade != null)
+      locks.clear();
+    }
+    else
+    {
+      InternalLockManager lockManager = getRepository().getLockingManager();
+      for (Object objectToUnlock : objectsToUnlock)
       {
-        grade = grade.getUpdated(type, false);
-        if (grade == LockGrade.NONE)
+        CDOID id = lockManager.getLockKeyID(objectToUnlock);
+        LockGrade grade = locks.get(id);
+        if (grade != null)
         {
-          locks.remove(id);
+          grade = grade.getUpdated(type, false);
+          if (grade == LockGrade.NONE)
+          {
+            locks.remove(id);
+          }
+          else
+          {
+            locks.put(id, grade);
+          }
         }
       }
     }
   }
 
-  @Override
-  public synchronized void unlock(String durableLockingID)
-  {
-    LockArea area = getLockArea(durableLockingID);
-    Map<CDOID, LockGrade> locks = area.getLocks();
-    locks.clear();
-  }
-
   public synchronized void queryLobs(List<byte[]> ids)
   {
     for (Iterator<byte[]> it = ids.iterator(); it.hasNext();)
@@ -1517,4 +1518,32 @@
       return MessageFormat.format("CommitInfo[{0}, {1}, {2}, {3}, {4}, {5}]", branch, getTimeStamp(), previousTimeStamp, userID, comment, mergeSource);
     }
   }
+
+  @Override
+  @Deprecated
+  public boolean isLocal(CDOID id)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
index e750b1d..a401782 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
@@ -142,20 +142,6 @@
   }
 
   @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void renameBranch(int branchID, String oldName, String newName)
   {
     store.renameBranch(branchID, oldName, newName);
@@ -226,13 +212,6 @@
     }
   }
 
-  @Deprecated
-  @Override
-  protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, OMMonitor monitor)
-  {
-    throw new UnsupportedOperationException();
-  }
-
   @Override
   protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, CDOBranchPoint mergeSource,
       OMMonitor monitor)
@@ -460,12 +439,6 @@
   }
 
   @Override
-  public void unlock(String durableLockingID)
-  {
-    store.unlock(durableLockingID);
-  }
-
-  @Override
   public void queryLobs(List<byte[]> ids)
   {
     store.queryLobs(ids);
@@ -506,4 +479,32 @@
 
     super.doDeactivate();
   }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  protected void writeCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/syncing/SynchronizableRepository.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/syncing/SynchronizableRepository.java
index 36ad118..398aa5f 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/syncing/SynchronizableRepository.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/syncing/SynchronizableRepository.java
@@ -25,8 +25,6 @@
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
-import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
-import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
@@ -59,7 +57,6 @@
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
 import org.eclipse.emf.cdo.spi.server.InternalCommitContext;
-import org.eclipse.emf.cdo.spi.server.InternalLockManager;
 import org.eclipse.emf.cdo.spi.server.InternalRepository;
 import org.eclipse.emf.cdo.spi.server.InternalRepositorySynchronizer;
 import org.eclipse.emf.cdo.spi.server.InternalSession;
@@ -68,7 +65,6 @@
 import org.eclipse.emf.cdo.spi.server.InternalSynchronizableRepository;
 import org.eclipse.emf.cdo.spi.server.InternalTransaction;
 import org.eclipse.emf.cdo.spi.server.InternalView;
-import org.eclipse.emf.cdo.spi.server.SyncingUtil;
 
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.collection.IndexedList;
@@ -362,51 +358,53 @@
   @Override
   public void handleLockChangeInfo(CDOLockChangeInfo lockChangeInfo)
   {
-    CDOLockOwner owner = lockChangeInfo.getLockOwner();
-    if (owner == null)
-    {
-      return;
-    }
+    throw new UnsupportedOperationException("Needs reimplementation for CDOLockDeltas");
 
-    String durableLockingID = owner.getDurableLockingID();
-    CDOBranch viewedBranch = lockChangeInfo.getBranch();
-    InternalLockManager lockManager = getLockingManager();
-    LockType lockType = lockChangeInfo.getLockType();
-
-    InternalView view = null;
-
-    try
-    {
-      view = SyncingUtil.openViewWithLockArea(replicatorSession, lockManager, viewedBranch, durableLockingID);
-      List<Object> lockables = new LinkedList<>();
-
-      for (CDOLockState lockState : lockChangeInfo.getNewLockStates())
-      {
-        lockables.add(lockState.getLockedObject());
-      }
-
-      if (lockChangeInfo.getOperation() == Operation.LOCK)
-      {
-        // If we can't lock immediately, there's a conflict, which means we're in big
-        // trouble: somehow locks were obtained on the clone but not on the master. What to do?
-        // TODO (CD) Consider this problem further
-        long timeout = 0;
-
-        super.lock(view, lockType, lockables, null, false, timeout);
-      }
-      else if (lockChangeInfo.getOperation() == Operation.UNLOCK)
-      {
-        super.doUnlock(view, lockType, lockables, false);
-      }
-      else
-      {
-        throw new IllegalStateException("Unexpected: " + lockChangeInfo.getOperation());
-      }
-    }
-    finally
-    {
-      LifecycleUtil.deactivate(view);
-    }
+    // CDOLockOwner owner = lockChangeInfo.getLockOwner();
+    // if (owner == null)
+    // {
+    // return;
+    // }
+    //
+    // String durableLockingID = owner.getDurableLockingID();
+    // CDOBranch viewedBranch = lockChangeInfo.getBranch();
+    // InternalLockManager lockManager = getLockingManager();
+    // LockType lockType = lockChangeInfo.getLockType();
+    //
+    // InternalView view = null;
+    //
+    // try
+    // {
+    // view = SyncingUtil.openViewWithLockArea(replicatorSession, lockManager, viewedBranch, durableLockingID);
+    // List<Object> lockables = new LinkedList<>();
+    //
+    // for (CDOLockState lockState : lockChangeInfo.getNewLockStates())
+    // {
+    // lockables.add(lockState.getLockedObject());
+    // }
+    //
+    // if (lockChangeInfo.getOperation() == Operation.LOCK)
+    // {
+    // // If we can't lock immediately, there's a conflict, which means we're in big
+    // // trouble: somehow locks were obtained on the clone but not on the master. What to do?
+    // // TODO (CD) Consider this problem further
+    // long timeout = 0;
+    //
+    // super.doLock(view, lockType, lockables, null, false, timeout);
+    // }
+    // else if (lockChangeInfo.getOperation() == Operation.UNLOCK)
+    // {
+    // super.doUnlock(view, lockType, lockables, false);
+    // }
+    // else
+    // {
+    // throw new IllegalStateException("Unexpected: " + lockChangeInfo.getOperation());
+    // }
+    // }
+    // finally
+    // {
+    // LifecycleUtil.deactivate(view);
+    // }
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/IStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/IStoreAccessor.java
index bc905a1..43e8bae 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/IStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/IStoreAccessor.java
@@ -21,6 +21,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOClob;
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lob.CDOLobHandler;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
@@ -425,14 +426,6 @@
     public ExtendedDataInputStream getLobs();
 
     /**
-     *
-     * @since 3.0
-     * @deprecated As of 4.5 no longer supported. See {@link #getIDsToUnlock()}.
-     */
-    @Deprecated
-    public boolean isAutoReleaseLocksEnabled();
-
-    /**
      * Returns an array of the locks on the new objects that are part of the commit operation represented by this
      * <code>CommitContext</code>.
      *
@@ -472,9 +465,14 @@
     public List<CDOIDReference> getXRefs();
 
     /**
-     * @since 4.1
+     * @since 4.15
      */
-    public List<LockState<Object, IView>> getPostCommmitLockStates();
+    public List<CDOLockDelta> getLockDeltas();
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockState> getLockStates();
 
     /**
      * @since 4.3
@@ -492,6 +490,21 @@
     public void modify(Consumer<ModificationContext> modifier);
 
     /**
+     *
+     * @since 3.0
+     * @deprecated As of 4.5 no longer supported. See {@link #getIDsToUnlock()}.
+     */
+    @Deprecated
+    public boolean isAutoReleaseLocksEnabled();
+
+    /**
+     * @since 4.1
+     * @deprecated As of 4.15 use {@link #getLockStates()}.
+     */
+    @Deprecated
+    public List<LockState<Object, IView>> getPostCommmitLockStates();
+
+    /**
      * A data and result context for the modifications in {@link CommitContext#modify(Consumer)}.
      *
      * @author Eike Stepper
@@ -858,6 +871,10 @@
 
     public void unlock(String durableLockingID, LockType type, Collection<? extends Object> objectsToUnlock);
 
+    /**
+     * @deprecated As of 4.15 use {@link #unlock(String, LockType, Collection) unlock(durableLockingID, null, null)}.
+     */
+    @Deprecated
     public void unlock(String durableLockingID);
   }
 
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalCommitContext.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalCommitContext.java
index d4b396a..8374cf5 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalCommitContext.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalCommitContext.java
@@ -149,10 +149,9 @@
   public void setLastUpdateTime(long lastUpdateTime);
 
   /**
-   * @deprecated As of 4.5 no longer supported. See {@link #setIDsToUnlock(CDOID[])}.
+   * @since 4.15
    */
-  @Deprecated
-  public void setAutoReleaseLocksEnabled(boolean on);
+  public void setOptimisticLockingTimeout(long optimisticLockingTimeout);
 
   /**
    * @since 4.1
@@ -189,4 +188,10 @@
    * @since 4.3
    */
   public void setSecurityImpact(byte securityImpact, Set<? extends Object> impactedRules);
+
+  /**
+   * @deprecated As of 4.5 no longer supported. See {@link #setIDsToUnlock(CDOID[])}.
+   */
+  @Deprecated
+  public void setAutoReleaseLocksEnabled(boolean on);
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalLockManager.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalLockManager.java
index 9e7a63a..1e9ee87 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalLockManager.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalLockManager.java
@@ -19,15 +19,24 @@
 
 import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
+import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * The type of the to-be-locked objects is either {@link CDOIDAndBranch} or {@link CDOID}, depending on whether
  * branching is supported by the repository or not.
+ * <p>
+ * The following features are supported in addition to {@link IRWOLockManager}:
+ * <ul>
+ * <li> Recursive locking
+ * <li> Distinction between explicit and implicit locking
+ * <li> Durable locking
+ * </ul>
  *
  * @author Eike Stepper
  * @since 3.0
@@ -43,11 +52,6 @@
   /**
    * @since 4.0
    */
-  public Object getLockEntryObject(Object key);
-
-  /**
-   * @since 4.0
-   */
   public Object getLockKey(CDOID id, CDOBranch branch);
 
   /**
@@ -56,49 +60,29 @@
   public CDOID getLockKeyID(Object key);
 
   /**
+   * @since 4.15
+   */
+  public CDOBranch getLockKeyBranch(Object key);
+
+  /**
    * @since 4.0
    */
   public Map<CDOID, LockGrade> getLocks(IView view);
 
   /**
-   * @since 4.0
+   * @since 4.15
    */
-  @Deprecated
-  public void lock(boolean explicit, LockType type, IView context, Collection<? extends Object> objects, long timeout) throws InterruptedException;
+  public long lock(IView view, Collection<? extends Object> objects, LockType lockType, int count, long timeout, //
+      boolean recursive, boolean explicit, //
+      LockDeltaHandler<Object, IView> deltaHandler, Consumer<LockState<Object, IView>> stateHandler) //
+      throws InterruptedException, TimeoutRuntimeException;
 
   /**
-   * @since 4.1
+   * @since 4.15
    */
-  public List<LockState<Object, IView>> lock2(boolean explicit, LockType type, IView context, Collection<? extends Object> objects, boolean recursive,
-      long timeout) throws InterruptedException;
-
-  /**
-   * Attempts to release for a given lock type, view and objects.
-   *
-   * @throws IllegalMonitorStateException
-   *           Unlocking objects without lock.
-   * @since 4.0
-   */
-  @Deprecated
-  public void unlock(boolean explicit, LockType type, IView context, Collection<? extends Object> objects);
-
-  /**
-   * @since 4.1
-   */
-  public List<LockState<Object, IView>> unlock2(boolean explicit, LockType type, IView context, Collection<? extends Object> objects, boolean recursive);
-
-  /**
-   * Attempts to release all locks(read and write) for a given view.
-   *
-   * @since 4.0
-   */
-  @Deprecated
-  public void unlock(boolean explicit, IView context);
-
-  /**
-   * @since 4.1
-   */
-  public List<LockState<Object, IView>> unlock2(boolean explicit, IView context);
+  public long unlock(IView view, Collection<? extends Object> objects, LockType lockType, int count, //
+      boolean recursive, boolean explicit, //
+      LockDeltaHandler<Object, IView> deltaHandler, Consumer<LockState<Object, IView>> stateHandler);
 
   /**
    * @since 4.0
@@ -113,13 +97,13 @@
   /**
    * @since 4.1
    */
-  // TODO (CD) I've also added this to DurableLocking2 Refactoring opportunity?
   public void updateLockArea(LockArea lockArea);
 
   /**
-   * @since 4.0
+   * @since 4.15
    */
-  public IView openView(ISession session, int viewID, boolean readOnly, String durableLockingID);
+  public void openView(ISession session, int viewID, boolean readOnly, String durableLockingID, Consumer<IView> viewConsumer,
+      BiConsumer<CDOID, LockGrade> lockConsumer);
 
   /**
    * @since 4.1
@@ -137,17 +121,86 @@
   public void getLockStates(Collection<Object> keys, BiConsumer<Object, LockState<Object, IView>> consumer);
 
   /**
-   * @since 4.4
+   * @since 4.15
    */
-  public List<LockState<Object, IView>> getLockStates();
-
-  /**
-   * @since 4.1
-   */
-  public void setLockState(Object key, LockState<Object, IView> lockState);
+  public void getLockStates(Consumer<LockState<Object, IView>> consumer);
 
   /**
    * @since 4.1
    */
   public void reloadLocks();
+
+  @Deprecated
+  public List<LockState<Object, IView>> getLockStates();
+
+  @Deprecated
+  public void setLockState(Object key, LockState<Object, IView> lockState);
+
+  @Deprecated
+  public Object getLockEntryObject(Object key);
+
+  @Deprecated
+  public void lock(boolean explicit, LockType type, IView view, Collection<? extends Object> objects, long timeout) throws InterruptedException;
+
+  @Override
+  @Deprecated
+  public void lock(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException;
+
+  @Override
+  @Deprecated
+  public void lock(LockType type, IView context, Object objectToLock, long timeout) throws InterruptedException;
+
+  @Deprecated
+  public List<LockState<Object, IView>> lock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objects, boolean recursive,
+      long timeout) throws InterruptedException;
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> lock2(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout)
+      throws InterruptedException;
+
+  @Deprecated
+  public void unlock(boolean explicit, LockType type, IView view, Collection<? extends Object> objects);
+
+  @Deprecated
+  public void unlock(boolean explicit, IView view);
+
+  @Override
+  @Deprecated
+  public void unlock(LockType type, IView context, Collection<? extends Object> objectsToUnlock);
+
+  @Override
+  @Deprecated
+  public void unlock(IView context);
+
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(boolean explicit, IView view);
+
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objects, boolean recursive);
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(LockType type, IView context, Collection<? extends Object> objectsToUnlock);
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(IView context, Collection<? extends Object> objectsToUnlock);
+
+  @Override
+  @Deprecated
+  public List<LockState<Object, IView>> unlock2(IView context);
+
+  @Override
+  @Deprecated
+  public long lock(IView context, Collection<? extends Object> objects, LockType lockType, int count, long timeout,
+      LockDeltaHandler<Object, IView> deltaHandler, Consumer<LockState<Object, IView>> stateHandler) throws InterruptedException, TimeoutRuntimeException;
+
+  @Override
+  @Deprecated
+  public long unlock(IView context, Collection<? extends Object> objects, LockType lockType, int count, LockDeltaHandler<Object, IView> deltaHandler,
+      Consumer<LockState<Object, IView>> stateHandler);
+
+  @Deprecated
+  public IView openView(ISession session, int viewID, boolean readOnly, String durableLockingID);
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalRepository.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalRepository.java
index 9c17dab..04d0eb7 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalRepository.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalRepository.java
@@ -144,13 +144,6 @@
   public void setSessionManager(InternalSessionManager sessionManager);
 
   /**
-   * @deprecated As of 4.1 use {@link #getLockingManager()}.
-   */
-  @Override
-  @Deprecated
-  public InternalLockManager getLockManager();
-
-  /**
    * @since 4.1
    */
   @Override
@@ -229,20 +222,6 @@
   public void commit(InternalCommitContext commitContext, OMMonitor monitor);
 
   /**
-   * @since 4.0
-   * @deprecated As of 4.2 use {@link #sendCommitNotification(InternalSession, CDOCommitInfo, boolean)}.
-   */
-  @Deprecated
-  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo);
-
-  /**
-   * @since 4.2
-   * @deprecated As of 4.3 use {@link #sendCommitNotification(ISessionProtocol.CommitNotificationInfo)}.
-   */
-  @Deprecated
-  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo, boolean clearResourcePathCache);
-
-  /**
    * @since 4.3
    */
   public void sendCommitNotification(CommitNotificationInfo info);
@@ -272,14 +251,6 @@
   public CDOChangeSetData getChangeSet(CDOBranchPoint startPoint, CDOBranchPoint endPoint);
 
   /**
-   * @since 4.0
-   * @deprecated As of 4.6 use {@link #getMergeData2(CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, OMMonitor)}.
-   */
-  @Deprecated
-  public Set<CDOID> getMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
-      CDORevisionAvailabilityInfo sourceBaseInfo, OMMonitor monitor);
-
-  /**
    * @since 4.6
    */
   public MergeDataResult getMergeData2(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo,
@@ -317,13 +288,6 @@
   public void setSkipInitialization(boolean skipInitialization);
 
   /**
-   * @since 4.0
-   * @deprecated As of 4.3 use {@link #initSystemPackages()}.
-   */
-  @Deprecated
-  public void initSystemPackages();
-
-  /**
    * @since 4.3
    */
   public void initSystemPackages(boolean firstStart);
@@ -344,6 +308,11 @@
   public UnlockObjectsResult unlock(InternalView view, LockType type, List<CDOID> ids, boolean recursive);
 
   /**
+   * @since 4.15
+   */
+  public UnlockObjectsResult unlock(InternalView view);
+
+  /**
    * @since 4.2
    */
   public long getOptimisticLockingTimeout();
@@ -363,9 +332,9 @@
   {
     @Override
     public InternalRepository getSource();
-
+  
     public boolean isFirstStart();
-
+  
     /**
      * @since 4.7
      */
@@ -390,4 +359,40 @@
      */
     public void doPostActivate(InternalSession session);
   }
+
+  /**
+   * @deprecated As of 4.1 use {@link #getLockingManager()}.
+   */
+  @Override
+  @Deprecated
+  public InternalLockManager getLockManager();
+
+  /**
+   * @since 4.0
+   * @deprecated As of 4.2 use {@link #sendCommitNotification(InternalSession, CDOCommitInfo, boolean)}.
+   */
+  @Deprecated
+  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo);
+
+  /**
+   * @since 4.2
+   * @deprecated As of 4.3 use {@link #sendCommitNotification(ISessionProtocol.CommitNotificationInfo)}.
+   */
+  @Deprecated
+  public void sendCommitNotification(InternalSession sender, CDOCommitInfo commitInfo, boolean clearResourcePathCache);
+
+  /**
+   * @since 4.0
+   * @deprecated As of 4.6 use {@link #getMergeData2(CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, OMMonitor)}.
+   */
+  @Deprecated
+  public Set<CDOID> getMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
+      CDORevisionAvailabilityInfo sourceBaseInfo, OMMonitor monitor);
+
+  /**
+   * @since 4.0
+   * @deprecated As of 4.3 use {@link #initSystemPackages()}.
+   */
+  @Deprecated
+  public void initSystemPackages();
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/SyncingUtil.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/SyncingUtil.java
index f775e7a..4d52677 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/SyncingUtil.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/SyncingUtil.java
@@ -13,8 +13,10 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
 import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockAreaNotFoundException;
+import org.eclipse.emf.cdo.server.IView;
 
 import org.eclipse.net4j.util.CheckUtil;
+import org.eclipse.net4j.util.concurrent.Holder;
 
 /**
  * Static methods that may help with classes related to repository synchronization.
@@ -31,29 +33,30 @@
   public static InternalView openViewWithLockArea(InternalSession session, InternalLockManager lockManager, CDOBranch viewedBranch, String lockAreaID)
   {
     LockArea lockArea;
-    InternalView view;
+    Holder<IView> viewHolder = new Holder<>();
 
     try
     {
       lockArea = lockManager.getLockArea(lockAreaID);
 
       // If we get here, the lockArea already exists.
-      view = (InternalView)lockManager.openView(session, InternalSession.TEMP_VIEW_ID, true, lockAreaID);
+      lockManager.openView(session, InternalSession.TEMP_VIEW_ID, true, lockAreaID, viewHolder, null);
     }
     catch (LockAreaNotFoundException e)
     {
       // If we get here, the lockArea does not yet exist, so we open
       // a view without a lockArea first, then create a lockArea with the given ID,
       // and associate it with the view.
-      view = session.openView(InternalSession.TEMP_VIEW_ID, viewedBranch.getHead());
+      InternalView view = session.openView(InternalSession.TEMP_VIEW_ID, viewedBranch.getHead());
       lockArea = lockManager.createLockArea(view, lockAreaID);
       view.setDurableLockingID(lockAreaID);
+      viewHolder.set(view);
     }
 
     CheckUtil.checkNull(lockAreaID, "lockAreaID");
     CheckUtil.checkNull(lockArea, "lockArea");
     CheckUtil.checkState(lockAreaID.equals(lockArea.getDurableLockingID()), "lockAreaID has incorrect value");
 
-    return view;
+    return (InternalView)viewHolder.get();
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
index de8b5b8..7ee3ff5 100644
--- a/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
+++ b/plugins/org.eclipse.emf.cdo.tests.db/src/org/eclipse/emf/cdo/tests/db/AllTestsDBH2Branching.java
@@ -29,7 +29,8 @@
   public static void initConfigSuites(ConfigTestSuite suite, TestSuite parent, IDGenerationLocation idGenerationLocation)
   {
     // Without ranges
-    suite.addScenario(parent, new H2Config().supportingBranches(true).idGenerationLocation(idGenerationLocation), JVM, NATIVE);
+    // suite.addScenario(parent, new H2Config().supportingBranches(true).idGenerationLocation(idGenerationLocation),
+    // JVM, NATIVE);
 
     // With ranges
     suite.addScenario(parent, new H2Config().supportingBranches(true).idGenerationLocation(idGenerationLocation).withRanges(true), JVM, NATIVE);
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
index b84f634..3a36359 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BranchingTest.java
@@ -943,7 +943,7 @@
   }
 
   @CleanRepositoriesBefore(reason = "Revision counting")
-  public void testhandleRevisionsAfterDetachInSubBranch() throws Exception
+  public void testHandleRevisionsAfterDetachInSubBranch() throws Exception
   {
     String name = getBranchName("subBranch");
 
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockStateCacheTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockStateCacheTest.java
index ae98719..77c3cff 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockStateCacheTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockStateCacheTest.java
@@ -55,54 +55,54 @@
     InternalCDOLockState lockState4 = getLockState(cache, view4);
 
     // Write lock.
-    lockState1.setWriteLockOwner(owner1);
+    lockState1.addOwner(owner1, LockType.WRITE);
     assertEquals(owner1, lockState1.getWriteLockOwner());
     assertEquals(owner1, lockState2.getWriteLockOwner());
     assertEquals(owner1, lockState3.getWriteLockOwner());
     assertEquals(owner1, lockState4.getWriteLockOwner());
 
     // Another write lock is not possible.
-    assertException(IllegalStateException.class, () -> lockState2.setWriteLockOwner(owner2));
+    assertException(IllegalStateException.class, () -> lockState2.addOwner(owner2, LockType.WRITE));
 
     // A read lock is not possible.
-    assertException(IllegalStateException.class, () -> lockState3.addReadLockOwner(owner3));
+    assertException(IllegalStateException.class, () -> lockState3.addOwner(owner3, LockType.READ));
 
     // A write option is not possible.
-    assertException(IllegalStateException.class, () -> lockState4.setWriteOptionOwner(owner4));
+    assertException(IllegalStateException.class, () -> lockState4.addOwner(owner4, LockType.OPTION));
 
     // Release write lock.
-    lockState1.setWriteLockOwner(null);
+    lockState1.removeOwner(owner1, LockType.WRITE);
     assertEquals(null, lockState1.getWriteLockOwner());
 
     // Read lock first time.
-    lockState1.addReadLockOwner(owner1);
+    lockState1.addOwner(owner1, LockType.READ);
     assertEquals(1, lockState1.getReadLockOwners().size());
     assertTrue(lockState1.getReadLockOwners().contains(owner1));
 
     // A write lock is not possible.
-    assertException(IllegalStateException.class, () -> lockState4.setWriteLockOwner(owner4));
+    assertException(IllegalStateException.class, () -> lockState4.addOwner(owner4, LockType.WRITE));
 
     // Read lock second time.
-    lockState1.addReadLockOwner(owner2);
+    lockState1.addOwner(owner2, LockType.READ);
     assertEquals(2, lockState1.getReadLockOwners().size());
     assertTrue(lockState1.getReadLockOwners().contains(owner1));
     assertTrue(lockState1.getReadLockOwners().contains(owner2));
 
     // A write lock is not possible.
-    assertException(IllegalStateException.class, () -> lockState4.setWriteLockOwner(owner4));
+    assertException(IllegalStateException.class, () -> lockState4.addOwner(owner4, LockType.WRITE));
 
     // Read lock third time.
-    lockState1.addReadLockOwner(owner3);
+    lockState1.addOwner(owner3, LockType.READ);
     assertEquals(3, lockState1.getReadLockOwners().size());
     assertTrue(lockState1.getReadLockOwners().contains(owner1));
     assertTrue(lockState1.getReadLockOwners().contains(owner2));
     assertTrue(lockState1.getReadLockOwners().contains(owner3));
 
     // A write lock is not possible.
-    assertException(IllegalStateException.class, () -> lockState4.setWriteLockOwner(owner4));
+    assertException(IllegalStateException.class, () -> lockState4.addOwner(owner4, LockType.WRITE));
 
     // Write option.
-    lockState1.setWriteOptionOwner(owner1);
+    lockState1.addOwner(owner1, LockType.OPTION);
     assertEquals(3, lockState1.getReadLockOwners().size());
     assertTrue(lockState1.getReadLockOwners().contains(owner1));
     assertTrue(lockState1.getReadLockOwners().contains(owner2));
@@ -457,7 +457,7 @@
     // Write locked by same owner.
     initial(1, 0) //
         .modify(0, 0, 1) //
-        .verify(IllegalStateException.class);
+        .verify(1, 0, 1);
 
     // Write locked by same owner and read locked by same owner.
     initial(1, 0, 1) //
@@ -840,36 +840,36 @@
         int reader = readers[i];
         if (reader > 0)
         {
-          lockState.addReadLockOwner(lockOwners[reader]);
+          lockState.addOwner(lockOwners[reader], LockType.READ);
         }
         else
         {
-          lockState.removeReadLockOwner(lockOwners[-reader]);
+          lockState.removeOwner(lockOwners[-reader], LockType.READ);
         }
       }
 
       if (writer == -1)
       {
-        lockState.setWriteLockOwner(null);
+        lockState.removeOwner(lockState.getWriteLockOwner(), LockType.WRITE);
       }
       else if (writer > 0)
       {
-        lockState.setWriteLockOwner(lockOwners[writer]);
+        lockState.addOwner(lockOwners[writer], LockType.WRITE);
       }
 
       if (optioner == -1)
       {
-        lockState.setWriteOptionOwner(null);
+        lockState.removeOwner(lockState.getWriteOptionOwner(), LockType.OPTION);
       }
       else if (optioner > 0)
       {
-        lockState.setWriteOptionOwner(lockOwners[optioner]);
+        lockState.addOwner(lockOwners[optioner], LockType.OPTION);
       }
     }
 
     private void update(int ownerToRemove)
     {
-      lockState.removeOwner(lockOwners[ownerToRemove]);
+      lockState.clearOwner(lockOwners[ownerToRemove]);
     }
 
     public TestExecution modify(int writer, int optioner, int... readers)
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java
index 275d4b1..65f3969 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java
@@ -27,7 +27,9 @@
 import org.eclipse.emf.cdo.spi.server.InternalRepository;
 import org.eclipse.emf.cdo.tests.model1.Category;
 import org.eclipse.emf.cdo.tests.model1.Company;
+import org.eclipse.emf.cdo.tests.model1.Customer;
 import org.eclipse.emf.cdo.tests.model1.Product1;
+import org.eclipse.emf.cdo.tests.model1.Supplier;
 import org.eclipse.emf.cdo.transaction.CDOTransaction;
 import org.eclipse.emf.cdo.util.CDOUtil;
 import org.eclipse.emf.cdo.util.CommitException;
@@ -44,13 +46,18 @@
 import org.eclipse.net4j.util.event.EventUtil;
 import org.eclipse.net4j.util.io.IOUtil;
 
+import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.spi.cdo.CDOLockStateCache;
 import org.eclipse.emf.spi.cdo.InternalCDOSession;
 import org.eclipse.emf.spi.cdo.InternalCDOTransaction;
 
+import org.eclipse.ocl.util.CollectionUtil;
+
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -104,8 +111,8 @@
     keys.add(2);
     keys.add(3);
 
-    lockingManager.lock(LockType.READ, 1, keys, 100);
-    lockingManager.unlock(1);
+    lockingManager.lock(1, keys, LockType.READ, 1, 100, null, null);
+    lockingManager.unlock(1, (Collection<Integer>)null, (LockType)null, 1, null, null);
   }
 
   public void testWriteOptions() throws Exception
@@ -114,7 +121,7 @@
 
     Set<Integer> keys = new HashSet<>();
     keys.add(1);
-    lockingManager.lock(LockType.OPTION, 1, keys, 100);
+    lockingManager.lock(1, keys, LockType.OPTION, 1, 100, null, null);
 
     // (R=Read, W=Write, WO=WriteOption)
     // Scenario 1: 1 has WO, 2 requests W -> fail
@@ -123,7 +130,7 @@
 
     try
     {
-      lockingManager.lock(LockType.WRITE, 2, keys, 100); // Must fail
+      lockingManager.lock(2, keys, LockType.WRITE, 1, 100, null, null); // Must fail
       fail("Should have thrown an exception");
     }
     catch (TimeoutRuntimeException e)
@@ -133,7 +140,7 @@
     // Scenario 2: 1 has WO, 2 requests R -> succeed
     try
     {
-      lockingManager.lock(LockType.READ, 2, keys, 100); // Must succeed
+      lockingManager.lock(2, keys, LockType.READ, 1, 100, null, null); // Must succeed
     }
     catch (TimeoutRuntimeException e)
     {
@@ -143,7 +150,7 @@
     // Scenario 3: 1 has WO, 2 has R, 1 requests W -> fail
     try
     {
-      lockingManager.lock(LockType.WRITE, 1, keys, 100); // Must fail
+      lockingManager.lock(1, keys, LockType.WRITE, 1, 100, null, null); // Must fail
       fail("Should have thrown an exception");
     }
     catch (TimeoutRuntimeException e)
@@ -153,7 +160,7 @@
     // Scenario 4: 1 has WO, 2 has R, 2 requests WO -> fail
     try
     {
-      lockingManager.lock(LockType.OPTION, 2, keys, 100); // Must fail
+      lockingManager.lock(2, keys, LockType.OPTION, 1, 100, null, null); // Must fail
       fail("Should have thrown an exception");
     }
     catch (TimeoutRuntimeException e)
@@ -161,10 +168,10 @@
     }
 
     // Scenario 5: 1 has WO, 2 has nothing, 2 requests WO -> fail
-    lockingManager.unlock(LockType.READ, 2, keys);
+    lockingManager.unlock(2, keys, LockType.READ, 1, null, null);
     try
     {
-      lockingManager.lock(LockType.OPTION, 2, keys, 100); // Must fail
+      lockingManager.lock(2, keys, LockType.OPTION, 1, 100, null, null); // Must fail
       fail("Should have thrown an exception");
     }
     catch (TimeoutRuntimeException e)
@@ -172,11 +179,11 @@
     }
 
     // Scenario 6: 1 has W, 2 has nothing, 2 requests WO -> fail
-    lockingManager.unlock(LockType.OPTION, 1, keys);
-    lockingManager.lock(LockType.WRITE, 1, keys, 100);
+    lockingManager.unlock(1, keys, LockType.OPTION, 1, null, null);
+    lockingManager.lock(1, keys, LockType.WRITE, 1, 100, null, null);
     try
     {
-      lockingManager.lock(LockType.OPTION, 2, keys, 100); // Must fail
+      lockingManager.lock(2, keys, LockType.OPTION, 1, 100, null, null); // Must fail
       fail("Should have thrown an exception");
     }
     catch (TimeoutRuntimeException e)
@@ -186,7 +193,7 @@
     // Scenario 7: 1 has W, 1 request WO -> succeed
     try
     {
-      lockingManager.lock(LockType.OPTION, 1, keys, 100); // Must succeed
+      lockingManager.lock(1, keys, LockType.OPTION, 1, 100, null, null); // Must succeed
     }
     catch (TimeoutRuntimeException e)
     {
@@ -194,11 +201,11 @@
     }
 
     // Scenario 8: 1 has W, 2 has R, 1 request WO -> succeed
-    lockingManager.unlock(LockType.OPTION, 1, keys);
-    lockingManager.lock(LockType.READ, 1, keys, 100);
+    lockingManager.unlock(1, keys, LockType.OPTION, 1, null, null);
+    lockingManager.lock(1, keys, LockType.READ, 1, 100, null, null);
     try
     {
-      lockingManager.lock(LockType.OPTION, 1, keys, 100); // Must succeed
+      lockingManager.lock(1, keys, LockType.OPTION, 1, 100, null, null); // Must succeed
     }
     catch (TimeoutRuntimeException e)
     {
@@ -219,7 +226,7 @@
         keys.add(1);
         try
         {
-          lockingManager.lock(LockType.WRITE, 1, keys, 50000);
+          lockingManager.lock(1, keys, LockType.WRITE, 1, 50000, null, null);
         }
         catch (InterruptedException ex)
         {
@@ -236,7 +243,7 @@
     keys.add(4);
 
     msg("Context 1 have readlock 1,2,3,4");
-    lockingManager.lock(LockType.READ, 1, keys, 1000);
+    lockingManager.lock(1, keys, LockType.READ, 1, 1000, null, null);
     assertEquals(true, lockingManager.hasLock(LockType.READ, 1, 1));
     assertEquals(true, lockingManager.hasLock(LockType.READ, 1, 2));
     assertEquals(true, lockingManager.hasLock(LockType.READ, 1, 3));
@@ -248,7 +255,7 @@
     keys.add(3);
 
     msg("Context 2 have readlock 1,2,3");
-    lockingManager.lock(LockType.READ, 2, keys, 1000);
+    lockingManager.lock(2, keys, LockType.READ, 1, 1000, null, null);
     assertEquals(true, lockingManager.hasLock(LockType.READ, 2, 1));
     assertEquals(true, lockingManager.hasLock(LockType.READ, 2, 2));
     assertEquals(true, lockingManager.hasLock(LockType.READ, 2, 3));
@@ -259,7 +266,7 @@
     keys.add(4);
 
     msg("Context 1 have readlock 1,2,3,4 and writeLock 4");
-    lockingManager.lock(LockType.WRITE, 1, keys, 1000);
+    lockingManager.lock(1, keys, LockType.WRITE, 1, 1000, null, null);
     assertEquals(true, lockingManager.hasLock(LockType.READ, 1, 4));
     assertEquals(true, lockingManager.hasLock(LockType.WRITE, 1, 4));
 
@@ -268,7 +275,7 @@
 
     try
     {
-      lockingManager.lock(LockType.WRITE, 1, keys, 1000);
+      lockingManager.lock(1, keys, LockType.WRITE, 1, 1000, null, null);
       fail("Should not have exception");
     }
     catch (RuntimeException expected)
@@ -284,7 +291,7 @@
     keys.add(1);
     keys.add(2);
     keys.add(3);
-    lockingManager.unlock(LockType.READ, 2, keys);
+    lockingManager.unlock(2, keys, LockType.READ, 1, null, null);
 
     new PollingTimeOuter()
     {
@@ -301,11 +308,11 @@
     RWOLockManager<Integer, Integer> lockingManager = new RWOLockManager<>();
     Set<Integer> keys = new HashSet<>();
     keys.add(1);
-    lockingManager.lock(LockType.READ, 1, keys, 10000);
-    lockingManager.unlock(LockType.READ, 1, keys);
+    lockingManager.lock(1, keys, LockType.READ, 1, 10000, null, null);
+    lockingManager.unlock(1, keys, LockType.READ, 1, null, null);
 
     // As of 4.4 should not fail anymore.
-    lockingManager.unlock(LockType.READ, 1, keys);
+    lockingManager.unlock(1, keys, LockType.READ, 1, null, null);
   }
 
   public void testReadTimeout() throws Exception
@@ -463,7 +470,7 @@
     CDOObject cdoCompany = CDOUtil.getCDOObject(company);
     CDOObject cdoCompany2 = CDOUtil.getCDOObject(company2);
 
-    transaction.lockObjects(Collections.singletonList(cdoCompany), LockType.WRITE, CDOLock.WAIT);
+    transaction.lockObjects(Collections.singletonList(cdoCompany), LockType.WRITE, CDOLock.NO_TIMEOUT);
 
     try
     {
@@ -1171,7 +1178,7 @@
     objects.add(CDOUtil.getCDOObject(category));
   }
 
-  public void _testLockContention() throws Exception
+  public void testLockContention() throws Exception
   {
     Company company1 = getModel1Factory().createCompany();
 
@@ -1227,12 +1234,155 @@
     CDOLockStateCache lockStateCache2 = ((InternalCDOSession)session2).getLockStateCache();
     CDOLockState lockState = lockStateCache2.getLockState(mainBranch, id);
     CDOLockOwner expectedLockOwner = transaction2.getLockOwner();
-    assertEquals("1", expectedLockOwner, lockState.getWriteLockOwner());
+    assertEquals(expectedLockOwner, lockState.getWriteLockOwner());
 
     sleep(200);
-    assertEquals("2", expectedLockOwner, lockState.getWriteLockOwner());
+    assertEquals(expectedLockOwner, lockState.getWriteLockOwner());
+  }
 
-    System.out.println();
+  public void testLockContentionRecursive() throws Exception
+  {
+    Company company21 = getModel1Factory().createCompany();
+
+    Supplier supplier21 = getModel1Factory().createSupplier();
+    company21.getSuppliers().add(supplier21);
+
+    Customer customer21 = getModel1Factory().createCustomer();
+    company21.getCustomers().add(customer21);
+
+    Category category21 = getModel1Factory().createCategory();
+    category21.getCategories().add(getModel1Factory().createCategory());
+    category21.getCategories().add(getModel1Factory().createCategory());
+    category21.getCategories().add(getModel1Factory().createCategory());
+    category21.getCategories().add(getModel1Factory().createCategory());
+    company21.getCategories().add(category21);
+
+    CDOSession session2 = openSession();
+    CDOTransaction transaction21 = session2.openTransaction();
+    transaction21.options().setLockNotificationEnabled(true);
+
+    CDOResource resource21 = transaction21.createResource(getResourcePath("/res1"));
+    resource21.getContents().add(company21);
+    transaction21.commit();
+
+    @SuppressWarnings("unused")
+    Set<Object> objects21 = loadView(transaction21);
+    CDOView view22 = openAndLoadView(session2);
+
+    CDOSession session3 = openSession();
+    CDOTransaction transaction31 = session3.openTransaction();
+    transaction31.options().setLockNotificationEnabled(true);
+    Company company31 = (Company)transaction31.getResource(getResourcePath("/res1")).getContents().get(0);
+    Category category31 = company31.getCategories().get(0);
+
+    @SuppressWarnings("unused")
+    Set<Object> objects31 = loadView(transaction31);
+    CDOView view32 = openAndLoadView(session3);
+
+    System.out.println(transaction21.getLockOwner() + " locking " + category21);
+    transaction21.lockObjects(CDOUtil.getCDOObjects(category21), LockType.WRITE, DEFAULT_TIMEOUT, true);
+    assertRecursiveWriteLock(transaction21.getLockOwner(), category21);
+
+    sleep(500); // Wait until all other views must have been notified.
+    assertRecursiveWriteLock(transaction21.getLockOwner(), view22.getObject(category21));
+    assertRecursiveWriteLock(transaction21.getLockOwner(), category31);
+    assertRecursiveWriteLock(transaction21.getLockOwner(), view32.getObject(category31));
+
+    Throwable[] exception = { null };
+    Thread client3 = new Thread("Client 3")
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          System.out.println(transaction21.getLockOwner() + " locking " + company31);
+          transaction31.lockObjects(CDOUtil.getCDOObjects(company31), LockType.WRITE, DEFAULT_TIMEOUT, true);
+          assertRecursiveWriteLock(transaction31.getLockOwner(), company31);
+
+          sleep(500); // Wait until all other views must have been notified.
+          assertRecursiveWriteLock(transaction31.getLockOwner(), view32.getObject(company31));
+          assertRecursiveWriteLock(transaction31.getLockOwner(), company21);
+          assertRecursiveWriteLock(transaction31.getLockOwner(), view22.getObject(company21));
+        }
+        catch (Throwable ex)
+        {
+          exception[0] = ex;
+        }
+      }
+    };
+
+    client3.start();
+    sleep(100); // Wait until client3 must have called lock().
+
+    // Unblock client3.
+    System.out.println(transaction21.getLockOwner() + " unlocking " + category21);
+    transaction21.unlockObjects(CDOUtil.getCDOObjects(category21), LockType.WRITE, true);
+    client3.join(DEFAULT_TIMEOUT);
+
+    if (exception[0] instanceof Error)
+    {
+      throw (Error)exception[0];
+    }
+
+    if (exception[0] instanceof Exception)
+    {
+      throw (Exception)exception[0];
+    }
+  }
+
+  private static CDOView openAndLoadView(CDOSession session)
+  {
+    CDOView view = session.openView();
+    view.options().setLockNotificationEnabled(true);
+
+    Set<Object> objects = loadView(view);
+    view.properties().put("objects", objects);
+
+    return view;
+  }
+
+  private static Set<Object> loadView(CDOView view)
+  {
+    Set<Object> objects = new HashSet<>();
+
+    CDOResource rootResource = view.getRootResource();
+    objects.add(rootResource);
+
+    for (Iterator<?> it = rootResource.eAllContents(); it.hasNext();)
+    {
+      objects.add(it.next());
+    }
+
+    return objects;
+  }
+
+  protected static void assertRecursiveWriteLock(CDOLockOwner owner, EObject object)
+  {
+    CDOObject cdoObject = CDOUtil.getCDOObject(object);
+    CDOLockOwner objectLockOwner = cdoObject.cdoView().getLockOwner();
+    assertRecursiveWriteLock(owner, objectLockOwner, cdoObject);
+
+    for (Iterator<?> it = cdoObject.eAllContents(); it.hasNext();)
+    {
+      CDOObject o = CDOUtil.getCDOObject((EObject)it.next());
+      assertRecursiveWriteLock(owner, objectLockOwner, o);
+    }
+  }
+
+  private static void assertRecursiveWriteLock(CDOLockOwner owner, CDOLockOwner objectLockOwner, CDOObject cdoObject)
+  {
+    CDOLock lock = cdoObject.cdoWriteLock();
+    assertEquals("owner of " + cdoObject, owner, CollectionUtil.first(lock.getOwners()));
+
+    if (objectLockOwner == owner)
+    {
+      assertEquals("isLocked of " + cdoObject, true, lock.isLocked());
+    }
+    else
+    {
+      assertEquals("isLockedByOthers of " + cdoObject, true, lock.isLockedByOthers());
+    }
   }
 
   public void testRecursiveLock() throws Exception
@@ -1548,23 +1698,25 @@
   public void testLockRefreshForHeldLock() throws Exception
   {
     // Client 1 creates and read-locks a resource.
-    CDOTransaction tx1 = openSession().openTransaction();
+    CDOSession session1 = openSession();
+    CDOTransaction tx1 = session1.openTransaction();
     CDOResource resource1 = tx1.createResource(getResourcePath("/res1"));
     tx1.commit();
     resource1.cdoReadLock().lock();
 
-    // Client 2 gets the resource created and read-locked by Client 1.
-    CDOTransaction tx2 = openSession().openTransaction();
+    // Client 2 loads the resource created and read-locked by Client 1.
+    CDOSession session2 = openSession();
+    CDOTransaction tx2 = session2.openTransaction();
     CDOResource resource2 = tx2.getResource(getResourcePath("/res1"));
 
-    // Get the lock and lock it.
+    // Client 2 gets the lock and locks it.
     CDOLock readLockBeforeClose = resource2.cdoReadLock();
     readLockBeforeClose.lock(DEFAULT_TIMEOUT);
 
     boolean isLockedByOthersBeforeClose = readLockBeforeClose.isLockedByOthers();
     assertTrue(isLockedByOthersBeforeClose);
 
-    tx1.close(); // Client 1 release read lock.
+    tx1.close(); // Client 1 releases its read lock.
 
     new PollingTimeOuter()
     {
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingNotificationsTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingNotificationsTest.java
index b8469c2..ddb1862 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingNotificationsTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingNotificationsTest.java
@@ -14,9 +14,8 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
-import org.eclipse.emf.cdo.common.lock.CDOLockState;
-import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 import org.eclipse.emf.cdo.eresource.CDOResource;
 import org.eclipse.emf.cdo.session.CDOSession;
@@ -31,58 +30,55 @@
 import org.eclipse.net4j.util.event.IEvent;
 import org.eclipse.net4j.util.tests.TestListener2;
 
-import java.util.List;
+import java.util.Collections;
 
 /**
  * @author Caspar De Groot
  */
 public class LockingNotificationsTest extends AbstractLockingTest
 {
-  /**
-   * FIXME Disabled until bug 358603 is addressed.
-   */
-  public void _testSameBranchDifferentSession_explicitRelease() throws Exception
+  public void testSameBranchDifferentSession_WithoutAutoRelease() throws Exception
   {
-    sameBranchDifferentSession(LockReleaseMode.EXPLICIT);
+    sameBranchDifferentSession(false);
   }
 
-  public void testSameBranchDifferentSession_autoRelease() throws Exception
+  public void testSameBranchDifferentSession_WithAutoRelease() throws Exception
   {
-    sameBranchDifferentSession(LockReleaseMode.AUTO);
+    sameBranchDifferentSession(true);
   }
 
-  public void testSameBranchSameSession_explicitRelease() throws Exception
+  public void testSameBranchSameSession_WithoutAutoRelease() throws Exception
   {
-    sameBranchSameSession(LockReleaseMode.EXPLICIT);
+    sameBranchSameSession(false);
   }
 
-  public void testSameBranchSameSession_autoRelease() throws Exception
+  public void testSameBranchSameSession_WithAutoRelease() throws Exception
   {
-    sameBranchSameSession(LockReleaseMode.AUTO);
+    sameBranchSameSession(true);
   }
 
   @Requires(IRepositoryConfig.CAPABILITY_BRANCHING)
   public void testDifferentBranchDifferentSession() throws Exception
   {
-    differentBranchDifferentSession(LockReleaseMode.EXPLICIT);
+    differentBranchDifferentSession(false);
   }
 
   @Requires(IRepositoryConfig.CAPABILITY_BRANCHING)
-  public void testDifferentBranchDifferentSession_autoRelease() throws Exception
+  public void testDifferentBranchDifferentSession_WithAutoRelease() throws Exception
   {
-    differentBranchDifferentSession(LockReleaseMode.AUTO);
+    differentBranchDifferentSession(true);
   }
 
   @Requires(IRepositoryConfig.CAPABILITY_BRANCHING)
   public void testDifferentBranchSameSession() throws Exception
   {
-    differentBranchSameSession(LockReleaseMode.EXPLICIT);
+    differentBranchSameSession(false);
   }
 
   @Requires(IRepositoryConfig.CAPABILITY_BRANCHING)
-  public void testDifferentBranchSameSession_autoRelease() throws Exception
+  public void testDifferentBranchSameSession_WithAutoRelease() throws Exception
   {
-    differentBranchSameSession(LockReleaseMode.AUTO);
+    differentBranchSameSession(true);
   }
 
   public void testEnableDisableNotifications() throws Exception
@@ -90,13 +86,13 @@
     CDOSession session1 = openSession();
     CDOSession session2 = openSession();
     CDOView controlView = session2.openView();
-    withExplicitRelease(session1, controlView, false);
+    withoutAutoRelease(session1, controlView, false);
 
     controlView.options().setLockNotificationEnabled(true);
-    withExplicitRelease(session1, controlView, true);
+    withoutAutoRelease(session1, controlView, true);
 
     controlView.options().setLockNotificationEnabled(false);
-    withExplicitRelease(session1, controlView, false);
+    withoutAutoRelease(session1, controlView, false);
 
     session1.close();
     session2.close();
@@ -106,13 +102,13 @@
   {
     CDOSession session1 = openSession();
     CDOView controlView = session1.openView();
-    withExplicitRelease(session1, controlView, false);
+    withoutAutoRelease(session1, controlView, false);
 
     controlView.options().setLockNotificationEnabled(true);
-    withExplicitRelease(session1, controlView, true);
+    withoutAutoRelease(session1, controlView, true);
 
     controlView.options().setLockNotificationEnabled(false);
-    withExplicitRelease(session1, controlView, false);
+    withoutAutoRelease(session1, controlView, false);
 
     session1.close();
   }
@@ -188,7 +184,7 @@
     resource.getContents().add(company);
     assertNew(company, transaction);
 
-    TestListener2 listener = new TestListener2(CDOViewLocksChangedEvent.class);
+    TestListener2 listener = new TestListener2(CDOViewLocksChangedEvent.class).dump(true, false);
     transaction.addListener(listener);
 
     lockWrite(company);
@@ -210,43 +206,43 @@
     return view;
   }
 
-  private void sameBranchDifferentSession(LockReleaseMode mode) throws Exception
+  private void sameBranchDifferentSession(boolean autoRelease) throws Exception
   {
     CDOSession session = openSession();
     CDOSession controlSession = openSession();
     CDOView controlView = openViewWithLockNotifications(controlSession, null);
 
-    if (mode == LockReleaseMode.EXPLICIT)
-    {
-      withExplicitRelease(session, controlView, true);
-    }
-    else if (mode == LockReleaseMode.AUTO)
+    if (autoRelease)
     {
       withAutoRelease(session, controlView, true);
     }
+    else
+    {
+      withoutAutoRelease(session, controlView, true);
+    }
 
     controlSession.close();
     session.close();
   }
 
-  private void sameBranchSameSession(LockReleaseMode mode) throws Exception
+  private void sameBranchSameSession(boolean autoRelease) throws Exception
   {
     CDOSession session = openSession();
     CDOView controlView = openViewWithLockNotifications(session, null);
 
-    if (mode == LockReleaseMode.EXPLICIT)
-    {
-      withExplicitRelease(session, controlView, true);
-    }
-    else if (mode == LockReleaseMode.AUTO)
+    if (autoRelease)
     {
       withAutoRelease(session, controlView, true);
     }
+    else
+    {
+      withoutAutoRelease(session, controlView, true);
+    }
 
     session.close();
   }
 
-  private void differentBranchDifferentSession(LockReleaseMode mode) throws Exception
+  private void differentBranchDifferentSession(boolean autoRelease) throws Exception
   {
     CDOSession session = openSession();
     CDOBranch subBranch = session.getBranchManager().getMainBranch().createBranch("sub1");
@@ -254,54 +250,56 @@
     CDOSession controlSession = openSession();
     CDOView controlView = openViewWithLockNotifications(controlSession, subBranch);
 
-    if (mode == LockReleaseMode.EXPLICIT)
-    {
-      withExplicitRelease(session, controlView, false);
-    }
-    else if (mode == LockReleaseMode.AUTO)
+    if (autoRelease)
     {
       withAutoRelease(session, controlView, false);
     }
+    else
+    {
+      withoutAutoRelease(session, controlView, false);
+    }
 
     controlSession.close();
     session.close();
   }
 
-  private void differentBranchSameSession(LockReleaseMode mode) throws Exception
+  private void differentBranchSameSession(boolean autoRelease) throws Exception
   {
     CDOSession session = openSession();
     CDOBranch subBranch = session.getBranchManager().getMainBranch().createBranch("sub2");
     CDOView controlView = openViewWithLockNotifications(session, subBranch);
 
-    if (mode == LockReleaseMode.EXPLICIT)
-    {
-      withExplicitRelease(session, controlView, false);
-    }
-    else if (mode == LockReleaseMode.AUTO)
+    if (autoRelease)
     {
       withAutoRelease(session, controlView, false);
     }
+    else
+    {
+      withoutAutoRelease(session, controlView, false);
+    }
 
     session.close();
   }
 
-  private void withExplicitRelease(CDOSession session1, CDOView controlView, boolean mustReceiveNotifications) throws Exception
+  private void withoutAutoRelease(CDOSession session, CDOView controlView, boolean sameBranch) throws Exception
   {
-    TestListener2 controlViewListener = new TestListener2(CDOViewLocksChangedEvent.class).dump(true, true);
+    TestListener2 controlViewListener = new TestListener2(CDOViewLocksChangedEvent.class).dump(true, false);
     controlView.addListener(controlViewListener);
 
-    CDOTransaction transaction1 = session1.openTransaction();
-    CDOResource res1 = transaction1.getOrCreateResource(getResourcePath("r1"));
-    TestListener2 transactionListener = new TestListener2(CDOViewLocksChangedEvent.class);
-    transaction1.addListener(transactionListener);
-    res1.getContents().clear();
+    CDOTransaction transaction = session.openTransaction();
+    transaction.options().setAutoReleaseLocksEnabled(false);
+
+    CDOResource resource = transaction.getOrCreateResource(getResourcePath("r1"));
+    TestListener2 transactionListener = new TestListener2(CDOViewLocksChangedEvent.class).dump(true, false);
+    transaction.addListener(transactionListener);
+    resource.getContents().clear();
     Company company = getModel1Factory().createCompany();
-    res1.getContents().add(company);
-    transaction1.commit();
+    resource.getContents().add(company);
+    transaction.commit();
 
     CDOObject cdoCompany = CDOUtil.getCDOObject(company);
     CDOObject cdoCompanyInControlView = null;
-    if (mustReceiveNotifications)
+    if (sameBranch)
     {
       cdoCompanyInControlView = controlView.getObject(cdoCompany.cdoID());
     }
@@ -311,124 +309,128 @@
     cdoCompany.cdoWriteLock().lock();
     waitForActiveLockNotifications();
 
-    if (mustReceiveNotifications)
+    if (sameBranch)
     {
       IEvent[] events = controlViewListener.waitFor(1);
       assertEquals(1, events.length);
 
       CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
-      assertLockOwner(transaction1, event.getLockOwner());
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertLockedObject(cdoCompany, lockStates.get(0));
-      assertLockOwner(transaction1, lockStates.get(0).getWriteLockOwner());
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockedObject(cdoCompany, lockDeltas[0]);
+      assertLockOwner(transaction, lockDeltas[0].getNewOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
+    }
+    else
+    {
+      sleep(100);
+      assertEquals(0, controlViewListener.getEvents().size());
     }
 
+    controlViewListener.clearEvents();
     cdoCompany.cdoWriteLock().unlock();
     waitForActiveLockNotifications();
 
-    if (mustReceiveNotifications)
+    if (sameBranch)
     {
-      IEvent[] events = controlViewListener.waitFor(2);
-      assertEquals(2, events.length);
+      IEvent[] events = controlViewListener.waitFor(1);
+      assertEquals(1, events.length);
 
-      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(1);
-      assertLockOwner(transaction1, event.getLockOwner());
+      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertLockedObject(cdoCompany, lockStates.get(0));
-      assertNull(lockStates.get(0).getWriteLockOwner());
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockedObject(cdoCompany, lockDeltas[0]);
+      assertLockOwner(transaction, lockDeltas[0].getOldOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
     }
 
     /* Test read lock */
 
+    controlViewListener.clearEvents();
     cdoCompany.cdoReadLock().lock();
     waitForActiveLockNotifications();
-    if (mustReceiveNotifications)
+
+    if (sameBranch)
     {
-      IEvent[] events = controlViewListener.waitFor(3);
-      assertEquals(3, events.length);
+      IEvent[] events = controlViewListener.waitFor(1);
+      assertEquals(1, events.length);
 
-      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(2);
-      assertLockOwner(transaction1, event.getLockOwner());
+      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertLockedObject(cdoCompany, lockStates.get(0));
-      assertEquals(1, lockStates.get(0).getReadLockOwners().size());
-      CDOLockOwner tx1Lo = CDOLockUtil.createLockOwner(transaction1);
-      assertEquals(true, lockStates.get(0).getReadLockOwners().contains(tx1Lo));
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockedObject(cdoCompany, lockDeltas[0]);
+      assertLockOwner(transaction, lockDeltas[0].getNewOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
     }
 
+    controlViewListener.clearEvents();
     cdoCompany.cdoReadLock().unlock();
     waitForActiveLockNotifications();
-    if (mustReceiveNotifications)
+
+    if (sameBranch)
     {
-      IEvent[] events = controlViewListener.waitFor(4);
-      assertEquals(4, events.length);
+      IEvent[] events = controlViewListener.waitFor(1);
+      assertEquals(1, events.length);
 
-      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(3);
-      assertLockOwner(transaction1, event.getLockOwner());
+      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertEquals(0, lockStates.get(0).getReadLockOwners().size());
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockOwner(transaction, lockDeltas[0].getOldOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
     }
 
     /* Test write option */
 
+    controlViewListener.clearEvents();
     cdoCompany.cdoWriteOption().lock();
     waitForActiveLockNotifications();
-    if (mustReceiveNotifications)
+
+    if (sameBranch)
     {
-      IEvent[] events = controlViewListener.waitFor(5);
-      assertEquals(5, events.length);
+      IEvent[] events = controlViewListener.waitFor(1);
+      assertEquals(1, events.length);
 
-      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(4);
-      assertLockOwner(transaction1, event.getLockOwner());
+      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertLockedObject(cdoCompany, lockStates.get(0));
-      assertLockOwner(transaction1, lockStates.get(0).getWriteOptionOwner());
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockedObject(cdoCompany, lockDeltas[0]);
+      assertLockOwner(transaction, lockDeltas[0].getNewOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
     }
 
+    controlViewListener.clearEvents();
     cdoCompany.cdoWriteOption().unlock();
     waitForActiveLockNotifications();
-    if (mustReceiveNotifications)
+
+    if (sameBranch)
     {
-      IEvent[] events = controlViewListener.waitFor(6);
-      assertEquals(6, events.length);
+      IEvent[] events = controlViewListener.waitFor(1);
+      assertEquals(1, events.length);
 
-      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(5);
-      assertLockOwner(transaction1, event.getLockOwner());
+      CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertLockOwner(transaction, event.getLockOwner());
 
-      List<CDOLockState> lockStates = event.getNewLockStates();
-      assertEquals(1, lockStates.size());
-      assertLockedObject(cdoCompany, lockStates.get(0));
-      assertNull(lockStates.get(0).getWriteOptionOwner());
-      assertEquals(cdoCompanyInControlView.cdoLockState(), lockStates.get(0));
-    }
-
-    List<IEvent> events = transactionListener.getEvents();
-    assertEquals(6, events.size());
-
-    if (!mustReceiveNotifications)
-    {
-      assertEquals(0, controlViewListener.getEvents().size());
+      CDOLockDelta[] lockDeltas = event.getLockDeltas();
+      assertEquals(1, lockDeltas.length);
+      assertLockedObject(cdoCompany, lockDeltas[0]);
+      assertLockOwner(transaction, lockDeltas[0].getOldOwner());
+      assertEquals(cdoCompanyInControlView.cdoLockState(), event.getLockStates()[0]);
     }
   }
 
-  private void withAutoRelease(CDOSession session, CDOView controlView, boolean mustReceiveNotifications) throws Exception
+  private void withAutoRelease(CDOSession session, CDOView controlView, boolean sameBranch) throws Exception
   {
-    TestListener2 controlViewListener = new TestListener2(CDOViewLocksChangedEvent.class);
+    TestListener2 controlViewListener = new TestListener2(CDOViewLocksChangedEvent.class).dump(true, true);
     controlView.addListener(controlViewListener);
 
     CDOTransaction transaction = session.openTransaction();
@@ -441,48 +443,69 @@
     resource.getContents().add(company);
     transaction.commit();
 
-    implicitRelease(company, LockType.WRITE, transaction, controlViewListener, mustReceiveNotifications);
-    implicitRelease(company, LockType.READ, transaction, controlViewListener, mustReceiveNotifications);
-    implicitRelease(company, LockType.OPTION, transaction, controlViewListener, mustReceiveNotifications);
+    withAutoRelease(company, LockType.READ, transaction, controlViewListener, sameBranch);
+    withAutoRelease(company, LockType.WRITE, transaction, controlViewListener, sameBranch);
+    withAutoRelease(company, LockType.OPTION, transaction, controlViewListener, sameBranch);
   }
 
-  private void implicitRelease(Company company, LockType type, CDOTransaction transaction, TestListener2 controlViewListener, boolean mustReceiveNotifications)
+  private void withAutoRelease(Company company, LockType lockType, CDOTransaction transaction, TestListener2 controlViewListener, boolean sameBranch)
       throws Exception
   {
     CDOViewLocksChangedEvent event;
     CDOObject cdoCompany = CDOUtil.getCDOObject(company);
-
     company.setName(company.getName() + "x"); // Make object DIRTY.
-    cdoCompany.cdoWriteLock().lock();
+
+    switch (lockType)
+    {
+    case READ:
+      cdoCompany.cdoReadLock().lock();
+      break;
+
+    case WRITE:
+      cdoCompany.cdoWriteLock().lock();
+      break;
+
+    case OPTION:
+      cdoCompany.cdoWriteOption().lock();
+      break;
+    }
+
+    controlViewListener.clearEvents();
     waitForActiveLockNotifications();
 
-    if (mustReceiveNotifications)
+    if (sameBranch)
     {
       controlViewListener.waitFor(1);
       event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
-      assertEquals(Operation.LOCK, event.getOperation());
-      assertEquals(LockType.WRITE, event.getLockType());
-    }
-
-    transaction.commit();
-
-    if (mustReceiveNotifications)
-    {
-      controlViewListener.waitFor(2);
-      event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(1);
-      assertEquals(Operation.UNLOCK, event.getOperation());
-      assertNull(event.getLockType());
+      assertEquals(Collections.singleton(Operation.LOCK), event.getOperations());
+      assertEquals(Collections.singleton(lockType), event.getLockTypes());
     }
     else
     {
-      sleep(1000);
+      sleep(100);
+      assertEquals(0, controlViewListener.getEvents().size());
+    }
+
+    controlViewListener.clearEvents();
+    transaction.commit();
+
+    if (sameBranch)
+    {
+      controlViewListener.waitFor(1);
+      event = (CDOViewLocksChangedEvent)controlViewListener.getEvents().get(0);
+      assertEquals(Collections.singleton(Operation.UNLOCK), event.getOperations());
+      assertEquals(Collections.singleton(lockType), event.getLockTypes());
+    }
+    else
+    {
+      sleep(100);
       assertEquals(0, controlViewListener.getEvents().size());
     }
   }
 
-  public static void assertLockedObject(CDOObject expected, CDOLockState actual)
+  public static void assertLockedObject(CDOObject expected, CDOLockDelta actual)
   {
-    Object lockedObject = actual.getLockedObject();
+    Object lockedObject = actual.getTarget();
     if (lockedObject instanceof CDOIDAndBranch)
     {
       CDOIDAndBranch idAndBranch = (CDOIDAndBranch)lockedObject;
@@ -497,15 +520,6 @@
 
   public static void assertLockOwner(CDOView expected, CDOLockOwner actual)
   {
-    CDOLockOwner lockOwner = CDOLockUtil.createLockOwner(expected);
-    assertEquals(lockOwner, actual);
-  }
-
-  /**
-   * @author Caspar De Groot
-   */
-  private static enum LockReleaseMode
-  {
-    EXPLICIT, AUTO
+    assertEquals(expected.getLockOwner(), actual);
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingSequenceTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingSequenceTest.java
index 4c0ffb8..5bdca87 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingSequenceTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingSequenceTest.java
@@ -11,6 +11,7 @@
 package org.eclipse.emf.cdo.tests;
 
 import org.eclipse.emf.cdo.CDOLock;
+import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.eresource.CDOResource;
 import org.eclipse.emf.cdo.session.CDOSession;
 import org.eclipse.emf.cdo.tests.model1.SalesOrder;
@@ -50,20 +51,22 @@
   public void testSafeCounter() throws Exception
   {
     disableConsole();
-    final SalesOrder sequence = getModel1Factory().createSalesOrder();
+    SalesOrder sequence = getModel1Factory().createSalesOrder();
+    sequence.setId(-1);
 
-    final CDOSession session = openSession();
+    CDOSession session = openSession();
     CDOTransaction transaction = session.openTransaction();
     CDOResource resource = transaction.createResource(getResourcePath("/res1"));
     resource.getContents().add(sequence);
     transaction.commit();
 
-    CountDownLatch latch = new CountDownLatch(USERS);
     User[] users = new User[USERS];
+    CountDownLatch latch = new CountDownLatch(USERS);
+    CDOLockOwner[] allocators = new CDOLockOwner[USERS * ALLOCATIONS];
 
     for (int userID = 0; userID < USERS; userID++)
     {
-      users[userID] = new User(userID, latch, sequence);
+      users[userID] = new User(latch, allocators, session.openTransaction(), sequence);
     }
 
     for (int userID = 0; userID < USERS; userID++)
@@ -100,17 +103,20 @@
   {
     private final CountDownLatch latch;
 
+    private final CDOLockOwner[] allocators;
+
     private final CDOTransaction transaction;
 
     private final SalesOrder sequence;
 
     private Exception exception;
 
-    public User(int userID, CountDownLatch latch, SalesOrder sequence)
+    public User(CountDownLatch latch, CDOLockOwner[] allocators, CDOTransaction transaction, SalesOrder sequence)
     {
-      super("User" + userID);
+      super(transaction.getLockOwner().toString());
       this.latch = latch;
-      transaction = CDOUtil.getCDOObject(sequence).cdoView().getSession().openTransaction();
+      this.allocators = allocators;
+      this.transaction = transaction;
       this.sequence = transaction.getObject(sequence);
     }
 
@@ -127,7 +133,7 @@
         CDOLock lock = CDOUtil.getCDOObject(sequence).cdoWriteLock();
         for (int allocation = 0; allocation < ALLOCATIONS; allocation++)
         {
-          for (int retry = 0; retry < RETRIES; retry++)
+          for (int retry = RETRIES; retry >= 0; --retry)
           {
             try
             {
@@ -136,7 +142,7 @@
             }
             catch (TimeoutException ex)
             {
-              if (retry == RETRIES)
+              if (retry == 0)
               {
                 exception = ex;
                 return;
@@ -146,14 +152,21 @@
             }
           }
 
-          int id = sequence.getId() + 1;
-          sequence.setId(id);
-          msg("Allocated " + id);
-
           try
           {
+            int id = sequence.getId() + 1;
+            if (allocators[id] != null)
+            {
+              throw new IllegalStateException(id + " already allocated by " + allocators[id]);
+            }
+
+            allocators[id] = transaction.getLockOwner();
+            sequence.setId(id);
+            msg("Allocated " + id);
+
             transaction.commit();
-            yield();
+            msg("Committed");
+            // yield();
           }
           catch (Exception ex)
           {
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_321986_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_321986_Test.java
index 08b0e47..f28546d 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_321986_Test.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_321986_Test.java
@@ -29,27 +29,26 @@
   public void testRollback() throws CommitException
   {
     CDOSession session1 = openSession();
-    CDOTransaction tx1 = session1.openTransaction();
+    CDOTransaction transaction1 = session1.openTransaction();
 
-    CDOResource resource1 = tx1.createResource(getResourcePath("test"));
+    CDOResource resource1 = transaction1.createResource(getResourcePath("test"));
     Address address = getModel1Factory().createAddress();
     resource1.getContents().add(address);
-    tx1.commit();
+    transaction1.commit();
 
     CDOObject cdoAddress = CDOUtil.getCDOObject(address);
     cdoAddress.cdoWriteLock().lock();
 
-    msg("Address: " + cdoAddress.cdoID());
-
     CDOSession session2 = openSession();
-    CDOTransaction tx2 = session2.openTransaction();
-    CDOResource resource2 = tx2.getResource(getResourcePath("test"));
+    CDOTransaction transaction2 = session2.openTransaction();
+    transaction2.options().setOptimisticLockingTimeout(200);
+    CDOResource resource2 = transaction2.getResource(getResourcePath("test"));
     resource2.getContents().clear();
 
     try
     {
       // We must fail here, because object was locked
-      tx2.commit();
+      transaction2.commit();
       fail("Commit should have failed");
     }
     catch (CommitException e)
@@ -59,11 +58,11 @@
 
     // Remove in the 1st transaction
     EcoreUtil.delete(address);
-    tx1.commit();
+    transaction1.commit();
 
     try
     {
-      tx2.rollback();
+      transaction2.rollback();
     }
     catch (Exception ex)
     {
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_469301_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_469301_Test.java
index 6c04c70..ca7a262 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_469301_Test.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_469301_Test.java
@@ -12,7 +12,8 @@
 
 import org.eclipse.emf.cdo.CDOObject;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
-import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta.Kind;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.eresource.CDOResource;
@@ -33,6 +34,7 @@
 import org.eclipse.emf.spi.cdo.InternalCDOTransaction;
 import org.eclipse.emf.spi.cdo.InternalCDOView;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -116,14 +118,21 @@
     controlListener.waitFor(1, DEFAULT_TIMEOUT);
 
     CDOViewLocksChangedEvent event = (CDOViewLocksChangedEvent)controlListener.getEvents().get(0);
-    assertEquals(operation, event.getOperation());
-    assertEquals(LockType.WRITE, event.getLockType());
+    assertEquals(Collections.singleton(operation), event.getOperations());
+    assertEquals(Collections.singleton(LockType.WRITE), event.getLockTypes());
     assertEquals(lockOwner, event.getLockOwner());
-    assertEquals(2, event.getNewLockStates().size());
+    assertEquals(2, event.getLockDeltas().length);
 
-    for (CDOLockState lockState : event.getNewLockStates())
+    for (CDOLockDelta lockDelta : event.getLockDeltas())
     {
-      assertEquals(event.getOperation() == Operation.LOCK ? lockOwner : null, lockState.getWriteLockOwner());
+      if (lockDelta.getKind() == Kind.ADDED)
+      {
+        assertEquals(lockOwner, lockDelta.getNewOwner());
+      }
+      else
+      {
+        assertEquals(lockOwner, lockDelta.getOldOwner());
+      }
     }
 
     controlListener.clearEvents();
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineLockingTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineLockingTest.java
index 85d9091..8cb9b55 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineLockingTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineLockingTest.java
@@ -27,6 +27,8 @@
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 import org.eclipse.net4j.util.tests.TestListener2;
 
+import java.util.Collections;
+
 /**
  * @author Caspar De Groot
  */
@@ -122,8 +124,8 @@
     session1lockListener.waitFor(1);
 
     e = (CDOSessionLocksChangedEvent)session1lockListener.getEvents().get(0);
-    assertSame(LockType.WRITE, e.getLockType());
-    assertSame(Operation.LOCK, e.getOperation());
+    assertEquals(Collections.singleton(LockType.WRITE), e.getLockTypes());
+    assertEquals(Collections.singleton(Operation.LOCK), e.getOperations());
     assertEquals(true, companyA_session1.cdoWriteLock().isLockedByOthers());
 
     // Perform the unlock in session 2, connected to clone 2
@@ -133,8 +135,8 @@
     session1lockListener.waitFor(2);
 
     e = (CDOSessionLocksChangedEvent)session1lockListener.getEvents().get(1);
-    assertSame(LockType.WRITE, e.getLockType());
-    assertSame(Operation.UNLOCK, e.getOperation());
+    assertEquals(Collections.singleton(LockType.WRITE), e.getLockTypes());
+    assertEquals(Collections.singleton(Operation.UNLOCK), e.getOperations());
     assertEquals(false, companyA_session1.cdoWriteLock().isLockedByOthers());
 
     // Now vice versa . . .
@@ -148,8 +150,8 @@
     session2lockListener.waitFor(1);
 
     e = (CDOSessionLocksChangedEvent)session2lockListener.getEvents().get(0);
-    assertSame(LockType.WRITE, e.getLockType());
-    assertSame(Operation.LOCK, e.getOperation());
+    assertEquals(Collections.singleton(LockType.WRITE), e.getLockTypes());
+    assertEquals(Collections.singleton(Operation.LOCK), e.getOperations());
     assertEquals(true, companyA_session2.cdoWriteLock().isLockedByOthers());
 
     // Perform the unlock in session 1, connected to clone 1
@@ -159,8 +161,8 @@
     session2lockListener.waitFor(2);
 
     e = (CDOSessionLocksChangedEvent)session2lockListener.getEvents().get(1);
-    assertSame(LockType.WRITE, e.getLockType());
-    assertSame(Operation.UNLOCK, e.getOperation());
+    assertEquals(Collections.singleton(LockType.WRITE), e.getLockTypes());
+    assertEquals(Collections.singleton(Operation.UNLOCK), e.getOperations());
     assertEquals(false, companyA_session2.cdoWriteLock().isLockedByOthers());
 
     // Now try an unlock-all . . .
diff --git a/plugins/org.eclipse.emf.cdo/.settings/.api_filters b/plugins/org.eclipse.emf.cdo/.settings/.api_filters
index 85a8a56..472390b 100644
--- a/plugins/org.eclipse.emf.cdo/.settings/.api_filters
+++ b/plugins/org.eclipse.emf.cdo/.settings/.api_filters
@@ -862,22 +862,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/emf/internal/cdo/session/CDOLockStateCache.java" type="org.eclipse.emf.internal.cdo.session.CDOLockStateCache$LockState">
-        <filter id="571473929">
-            <message_arguments>
-                <message_argument value="AbstractCDOLockState"/>
-                <message_argument value="LockState"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/emf/internal/cdo/session/CDOLockStateCache.java" type="org.eclipse.emf.internal.cdo.session.CDOLockStateCache$MutableLockState">
-        <filter id="574619656">
-            <message_arguments>
-                <message_argument value="InternalCDOLockState"/>
-                <message_argument value="MutableLockState"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java" type="org.eclipse.emf.internal.cdo.session.CDOLockStateCacheImpl$LockState">
         <filter id="571473929">
             <message_arguments>
@@ -886,14 +870,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java" type="org.eclipse.emf.internal.cdo.session.CDOLockStateCacheImpl$MutableLockState">
-        <filter id="574619656">
-            <message_arguments>
-                <message_argument value="InternalCDOLockState"/>
-                <message_argument value="MutableLockState"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java" type="org.eclipse.emf.internal.cdo.session.CDOSessionImpl">
         <filter id="574668824">
             <message_arguments>
@@ -944,14 +920,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/emf/internal/cdo/util/DefaultLocksChangedEvent.java" type="org.eclipse.emf.internal.cdo.util.DefaultLocksChangedEvent">
-        <filter id="574619656">
-            <message_arguments>
-                <message_argument value="CDOLockChangeInfo"/>
-                <message_argument value="DefaultLocksChangedEvent"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/emf/internal/cdo/view/AbstractCDOView.java" type="org.eclipse.emf.internal.cdo.view.AbstractCDOView">
         <filter id="574668824">
             <message_arguments>
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java
index 369a103..05c5413 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java
@@ -11,11 +11,14 @@
  */
 package org.eclipse.emf.cdo;
 
+import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.view.CDOView;
 
 import org.eclipse.net4j.util.concurrent.IRWLockManager;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.locks.Lock;
@@ -31,9 +34,10 @@
  */
 public interface CDOLock extends Lock
 {
-  public static final int WAIT = IRWLockManager.WAIT;
-
-  public static final int NO_WAIT = IRWLockManager.NO_WAIT;
+  /**
+   * @since 4.15
+   */
+  public static final long NO_TIMEOUT = IRWOLockManager.NO_TIMEOUT;
 
   /**
    * @since 4.8
@@ -71,4 +75,15 @@
    * from the requesting one), <code>false</code> otherwise.
    */
   public boolean isLockedByOthers();
+
+  /**
+   * @since 4.15
+   */
+  public Set<CDOLockOwner> getOwners();
+
+  @Deprecated
+  public static final int WAIT = IRWLockManager.WAIT;
+
+  @Deprecated
+  public static final int NO_WAIT = IRWLockManager.NO_WAIT;
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOTransaction.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOTransaction.java
index d9cb710..040bbce 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOTransaction.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOTransaction.java
@@ -24,6 +24,7 @@
 import org.eclipse.emf.cdo.common.commit.CDOChangeSetDataProvider;
 import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
 import org.eclipse.emf.cdo.common.util.CDOResourceNodeNotFoundException;
@@ -37,6 +38,7 @@
 import org.eclipse.emf.cdo.view.CDOQuery;
 import org.eclipse.emf.cdo.view.CDOView;
 
+import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 import org.eclipse.net4j.util.options.IOptionsEvent;
 
 import org.eclipse.emf.common.util.URI;
@@ -331,6 +333,18 @@
     public static final long DEFAULT_COMMIT_INFO_TIMEOUT = 60000;
 
     /**
+     * Indicates to use the timeout value that is configured on the server.
+     *
+     * @since 4.15
+     */
+    public static final long DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT = CDOProtocolConstants.DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT;
+
+    /**
+     * @since 4.15
+     */
+    public static final long NO_OPTIMISTIC_LOCKING_TIMEOUT = IRWOLockManager.NO_TIMEOUT;
+
+    /**
      * Returns the {@link CDOTransaction transaction} of this options object.
      *
      * @since 4.0
@@ -494,6 +508,26 @@
     public void setAttachedRevisionsMap(Map<CDOID, CDORevision> attachedRevisionsMap);
 
     /**
+     * Returns the number of milliseconds to wait for the successful acquisition of all required implicit locks on the server
+     * when {@link CDOTransaction#commit()} is called.
+     * <p>
+     * Default value is {@link #DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT}.
+     *
+     * @since 4.15
+     */
+    public long getOptimisticLockingTimeout();
+
+    /**
+     * Returns the number of milliseconds to wait for the successful acquisition of all required implicit locks on the server
+     * when {@link CDOTransaction#commit()} is called.
+     * <p>
+     * Default value is {@link #DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT}.
+     *
+     * @since 4.15
+     */
+    public void setOptimisticLockingTimeout(long optimisticLockingTimeout);
+
+    /**
      * Returns the number of milliseconds to wait for the transaction update when {@link CDOTransaction#commit()} is called.
      * <p>
      * Default value is 10000.
@@ -603,6 +637,19 @@
 
     /**
      * An {@link IOptionsEvent options event} fired from transaction {@link CDOTransaction#options() options} when the
+     * {@link Options#setOptimisticLockingTimeout(long) optimistic locking timeout} option has changed.
+     *
+     * @author Eike Stepper
+     * @since 4.15
+     * @noextend This interface is not intended to be extended by clients.
+     * @noimplement This interface is not intended to be implemented by clients.
+     */
+    public interface OptimisticLockingTimeout extends IOptionsEvent
+    {
+    }
+
+    /**
+     * An {@link IOptionsEvent options event} fired from transaction {@link CDOTransaction#options() options} when the
      * {@link Options#setCommitInfoTimeout(long) commit info timeout} option has changed.
      *
      * @author Eike Stepper
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/view/CDOLockStatePrefetcher.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/view/CDOLockStatePrefetcher.java
index 73e1b4e..1bc205d 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/view/CDOLockStatePrefetcher.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/view/CDOLockStatePrefetcher.java
@@ -11,6 +11,7 @@
 package org.eclipse.emf.cdo.view;
 
 import org.eclipse.emf.cdo.CDOObject;
+import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -27,12 +28,11 @@
 import org.eclipse.net4j.util.lifecycle.ILifecycle;
 import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
 
+import org.eclipse.emf.spi.cdo.CDOLockStateCache;
 import org.eclipse.emf.spi.cdo.CDOSessionProtocol;
 import org.eclipse.emf.spi.cdo.InternalCDOView;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -208,9 +208,9 @@
         {
           // Direct call the session protocol.
           CDOSessionProtocol sessionProtocol = view.getSession().getSessionProtocol();
-          CDOLockState[] loadedLockStates = sessionProtocol.getLockStates(view.getBranch().getID(), ids, event.getPrefetchDepth());
+          List<CDOLockState> loadedLockStates = sessionProtocol.getLockStates2(view.getBranch().getID(), ids, event.getPrefetchDepth());
 
-          updateLockStates(Arrays.asList(loadedLockStates), true);
+          updateLockStates(loadedLockStates, true);
 
           // Add missing lock states.
           List<CDOLockState> missingLockStates = new ArrayList<>();
@@ -265,11 +265,13 @@
     });
   }
 
-  private void updateLockStates(Collection<? extends CDOLockState> lockStates, boolean loadOnDemand)
+  private void updateLockStates(List<CDOLockState> lockStates, boolean loadOnDemand)
   {
     try
     {
-      view.updateLockStates(lockStates, loadOnDemand, null);
+      CDOBranch branch = view.getBranch();
+      CDOLockStateCache lockStateCache = view.getSession().getLockStateCache();
+      lockStateCache.addLockStates(branch, lockStates, null);
     }
     catch (Exception ex)
     {
@@ -278,39 +280,5 @@
         OM.LOG.error(ex);
       }
     }
-
-    if (updateOtherViews)
-    {
-      for (InternalCDOView otherView : view.getSession().getViews())
-      {
-        if (asyncUpdate)
-        {
-          ExecutorService executorService = view.getExecutorService();
-          executorService.submit(() -> updateLockStatesForOtherView(lockStates, loadOnDemand, otherView));
-        }
-        else
-        {
-          updateLockStatesForOtherView(lockStates, loadOnDemand, otherView);
-        }
-      }
-    }
-  }
-
-  private void updateLockStatesForOtherView(Collection<? extends CDOLockState> lockStates, boolean loadOnDemand, InternalCDOView otherview)
-  {
-    try
-    {
-      if (otherview != view && otherview.isActive() && otherview.getBranch() == view.getBranch())
-      {
-        otherview.updateLockStates(lockStates, loadOnDemand, null);
-      }
-    }
-    catch (Exception ex)
-    {
-      if (otherview.isActive())
-      {
-        OM.LOG.error(ex);
-      }
-    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java
index fde3d4e..93015ab 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java
@@ -13,6 +13,8 @@
 
 import org.eclipse.emf.cdo.CDOLock;
 import org.eclipse.emf.cdo.CDOObject;
+import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
+import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.util.LockTimeoutException;
 
 import org.eclipse.net4j.util.WrappedException;
@@ -23,6 +25,7 @@
 
 import java.text.MessageFormat;
 import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.locks.Condition;
@@ -35,6 +38,8 @@
 {
   public static final CDOLock NOOP = new NOOPLockImpl();
 
+  private static final Set<CDOLockOwner> NO_OWNERS = Collections.emptySet();
+
   private final InternalCDOObject object;
 
   private final LockType type;
@@ -73,12 +78,35 @@
   }
 
   @Override
+  public Set<CDOLockOwner> getOwners()
+  {
+    CDOLockState lockState = object.cdoLockState();
+
+    switch (type)
+    {
+    case READ:
+      return lockState.getReadLockOwners();
+
+    case WRITE:
+      CDOLockOwner writeLockOwner = lockState.getWriteLockOwner();
+      return writeLockOwner == null ? NO_OWNERS : Collections.singleton(writeLockOwner);
+
+    case OPTION:
+      CDOLockOwner writeOptionOwner = lockState.getWriteOptionOwner();
+      return writeOptionOwner == null ? NO_OWNERS : Collections.singleton(writeOptionOwner);
+
+    default:
+      throw new AssertionError();
+    }
+  }
+
+  @Override
   public void lock()
   {
     try
     {
       InternalCDOView view = object.cdoView();
-      view.lockObjects(Collections.singletonList(object), type, WAIT);
+      view.lockObjects(Collections.singletonList(object), type, NO_TIMEOUT);
     }
     catch (InterruptedException ex)
     {
@@ -132,7 +160,7 @@
     try
     {
       InternalCDOView view = object.cdoView();
-      view.lockObjects(Collections.singletonList(object), type, NO_WAIT);
+      view.lockObjects(Collections.singletonList(object), type, 0);
       return true;
     }
     catch (LockTimeoutException ex)
@@ -216,6 +244,12 @@
     }
 
     @Override
+    public Set<CDOLockOwner> getOwners()
+    {
+      return NO_OWNERS;
+    }
+
+    @Override
     public void lock()
     {
       throw new UnsupportedOperationException();
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java
index ded628c..9033d37 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOLockStateCacheImpl.java
@@ -14,20 +14,20 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
 import org.eclipse.emf.cdo.session.CDOSession;
 import org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockState;
-import org.eclipse.emf.cdo.spi.common.lock.InternalCDOLockState;
 import org.eclipse.emf.cdo.view.CDOView;
 import org.eclipse.emf.cdo.view.CDOViewTargetChangedEvent;
 
 import org.eclipse.net4j.util.CheckUtil;
 import org.eclipse.net4j.util.ImplementationError;
 import org.eclipse.net4j.util.ObjectUtil;
-import org.eclipse.net4j.util.StringUtil;
+import org.eclipse.net4j.util.ReflectUtil;
 import org.eclipse.net4j.util.collection.CollectionUtil;
 import org.eclipse.net4j.util.collection.CollectionUtil.KeepMappedValue;
 import org.eclipse.net4j.util.collection.HashBag;
@@ -37,8 +37,10 @@
 import org.eclipse.net4j.util.container.IContainer;
 import org.eclipse.net4j.util.event.IEvent;
 import org.eclipse.net4j.util.event.IListener;
+import org.eclipse.net4j.util.io.IOUtil;
 import org.eclipse.net4j.util.lifecycle.ILifecycle;
 import org.eclipse.net4j.util.lifecycle.Lifecycle;
+import org.eclipse.net4j.util.om.OMPlatform;
 
 import org.eclipse.emf.spi.cdo.CDOLockStateCache;
 import org.eclipse.emf.spi.cdo.CDOSessionProtocol;
@@ -53,6 +55,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.StringJoiner;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.BiFunction;
@@ -65,6 +68,11 @@
 {
   private static final Class<CDOLockOwner[]> ARRAY_CLASS = CDOLockOwner[].class;
 
+  private static final boolean DEBUG = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.internal.cdo.session.CDOLockStateCacheImpl.DEBUG");
+
+  private static final boolean DEBUG_STACK_TRACE = //
+      OMPlatform.INSTANCE.isProperty("org.eclipse.emf.internal.cdo.session.CDOLockStateCacheImpl.DEBUG_STACK_TRACE");
+
   private final Map<CDOBranch, ConcurrentMap<CDOID, OwnerInfo>> ownerInfosPerBranch = new HashMap<>();
 
   private final ConcurrentMap<CDOID, OwnerInfo> ownerInfosOfMainBranch = new ConcurrentHashMap<>();
@@ -152,7 +160,7 @@
   public CDOLockState getLockState(CDOBranch branch, CDOID id)
   {
     Object key = createKey(branch, id);
-    return new MutableLockState(key, this);
+    return new LockState(key, this);
   }
 
   @Override
@@ -162,7 +170,7 @@
     {
       if (loadOnDemand)
       {
-        loadAndCacheLockStates(branch, null, consumer);
+        loadLockStates(branch, null, consumer);
       }
       else
       {
@@ -176,7 +184,7 @@
 
       if (!ObjectUtil.isEmpty(missingIDs))
       {
-        loadAndCacheLockStates(branch, missingIDs, lockState -> {
+        loadLockStates(branch, missingIDs, lockState -> {
           missingIDs.remove(lockState.getID());
           consumer.accept(lockState);
         });
@@ -188,8 +196,7 @@
 
           for (CDOID id : missingIDs)
           {
-            Object key = createKey(branch, id);
-            CDOLockState defaultLockStateToCache = new ImmutableLockState(key, NoOwnerInfo.INSTANCE);
+            CDOLockState defaultLockStateToCache = getLockState(branch, id);
             defaultLockStatesToCache.add(defaultLockStateToCache);
           }
 
@@ -212,33 +219,111 @@
   }
 
   @Override
-  public void addLockStates(CDOBranch branch, Collection<? extends CDOLockState> newLockStates, Consumer<CDOLockState> consumer)
+  public synchronized void updateLockStates(CDOBranch branch, Collection<CDOLockDelta> lockDeltas, Collection<CDOLockState> lockStates,
+      Consumer<CDOLockState> consumer)
+  {
+    try
+    {
+      ConcurrentMap<CDOID, OwnerInfo> infos = getOwnerInfoMap(branch);
+
+      delta_loop: for (CDOLockDelta delta : lockDeltas)
+      {
+        CDOID id = delta.getID();
+
+        OwnerInfo info = null;
+        OwnerInfo newInfo = null;
+
+        for (int i = 50; i >= 0; --i)
+        {
+          info = infos.get(id);
+          if (info == null || delta.getType() == null)
+          {
+            for (CDOLockState lockState : lockStates)
+            {
+              if (lockState.getID() == id)
+              {
+                addLockState(infos, lockState, consumer);
+                continue delta_loop;
+              }
+            }
+
+            continue delta_loop;
+          }
+
+          try
+          {
+            newInfo = info.applyDelta(this, delta);
+            break;
+          }
+          catch (ObjectAlreadyLockedException ex)
+          {
+            if (i == 0)
+            {
+              throw new ObjectAlreadyLockedException(id, branch, ex);
+            }
+
+            try
+            {
+              wait(100);
+            }
+            catch (InterruptedException ex1)
+            {
+              throw new Error(ex1);
+            }
+          }
+        }
+
+        if (newInfo != info)
+        {
+          trace(id, info, newInfo);
+          infos.put(id, newInfo);
+        }
+
+        if (consumer != null)
+        {
+          Object target = delta.getTarget();
+          CDOLockState lockState = new LockState(target, this);
+          consumer.accept(lockState);
+        }
+      }
+    }
+    finally
+    {
+      notifyAll();
+    }
+  }
+
+  @Override
+  public void addLockStates(CDOBranch branch, Collection<? extends CDOLockState> lockStates, Consumer<CDOLockState> consumer)
   {
     ConcurrentMap<CDOID, OwnerInfo> infos = getOwnerInfoMap(branch);
 
-    for (CDOLockState loadedLockState : newLockStates)
+    for (CDOLockState lockState : lockStates)
     {
-      CDOID id = loadedLockState.getID();
-      OwnerInfo info = createOwnerInfo(loadedLockState);
-      infos.put(id, info);
-
-      if (consumer != null)
+      try
       {
-        consumer.accept(loadedLockState);
+        addLockState(infos, lockState, consumer);
+      }
+      catch (Exception ex)
+      {
+        ex.printStackTrace();
       }
     }
   }
 
   @Override
-  public void removeOwner(CDOBranch branch, CDOLockOwner owner, Consumer<CDOLockState> changeConsumer)
+  public List<CDOLockDelta> removeOwner(CDOBranch branch, CDOLockOwner owner, Consumer<CDOLockState> consumer)
   {
+    List<CDOLockDelta> deltas = new ArrayList<>();
     List<Pair<CDOID, OwnerInfo>> newInfos = new ArrayList<>();
+
     ConcurrentMap<CDOID, OwnerInfo> infos = getOwnerInfoMap(branch);
     infos.forEach((id, info) -> {
-      OwnerInfo newInfo = info.removeOwner(this, owner);
+      OwnerInfo newInfo = info.removeOwner(this, owner, lockType -> appendRemoveDelta(deltas, branch, id, lockType, owner));
       if (newInfo != info)
       {
         newInfos.add(Pair.create(id, newInfo));
+        notifyConsumer(branch, id, consumer);
       }
     });
 
@@ -247,35 +332,39 @@
       CDOID id = pair.getElement1();
       OwnerInfo newInfo = pair.getElement2();
 
-      if (newInfo == NoOwnerInfo.INSTANCE)
-      {
-        infos.remove(id);
-      }
-      else
-      {
-        infos.put(id, newInfo);
-      }
-
-      notifyConsumer(branch, id, changeConsumer);
+      OwnerInfo oldInfo = infos.put(id, newInfo);
+      trace(id, oldInfo, newInfo);
     }
+
+    return deltas;
   }
 
   @Override
   public void remapOwner(CDOBranch branch, CDOLockOwner oldOwner, CDOLockOwner newOwner)
   {
-    removeSingleOwnerInfos(oldOwner);
-    addSingleOwnerInfos(newOwner, -1);
+    for (int type = 0; type < singleOwnerInfos.length; type++)
+    {
+      for (SingleOwnerInfo info : singleOwnerInfos[type].values())
+      {
+        remapOwnerInfo(info, oldOwner, newOwner);
+      }
+    }
 
     ConcurrentMap<CDOID, OwnerInfo> infos = getOwnerInfoMap(branch);
     for (OwnerInfo info : infos.values())
     {
-      info.remapOwner(oldOwner, newOwner);
+      remapOwnerInfo(info, oldOwner, newOwner);
     }
   }
 
   @Override
   public void removeLockStates(CDOBranch branch, Collection<CDOID> ids, Consumer<CDOLockState> consumer)
   {
+    if (ObjectUtil.isEmpty(ids))
+    {
+      return;
+    }
+
     ConcurrentMap<CDOID, OwnerInfo> infos = getOwnerInfoMap(branch);
 
     for (CDOID id : ids)
@@ -377,6 +466,34 @@
     }
   }
 
+  private SingleOwnerInfo addSingleOwnerInfos(CDOLockOwner owner, int typeToReturn)
+  {
+    SingleOwnerInfo infoToReturn = null;
+
+    for (int type = 0; type < singleOwnerInfos.length; type++)
+    {
+      int finalType = type;
+
+      // Don't overwrite existing infos. Infos can exist if the owning view is durable.
+      SingleOwnerInfo info = singleOwnerInfos[type].computeIfAbsent(owner, o -> SingleOwnerInfo.create(o, finalType));
+
+      if (type == typeToReturn)
+      {
+        infoToReturn = info;
+      }
+    }
+
+    return infoToReturn;
+  }
+
+  private void removeSingleOwnerInfos(CDOLockOwner owner)
+  {
+    for (int type = 0; type < singleOwnerInfos.length; type++)
+    {
+      singleOwnerInfos[type].remove(owner);
+    }
+  }
+
   private <T> T withKey(Object key, BiFunction<CDOBranch, CDOID, T> function)
   {
     CDOBranch branch;
@@ -401,40 +518,11 @@
   {
     if (consumer != null)
     {
-      Object key = createKey(branch, id);
-      CDOLockState lockState = new MutableLockState(key, this);
+      CDOLockState lockState = getLockState(branch, id);
       consumer.accept(lockState);
     }
   }
 
-  private SingleOwnerInfo addSingleOwnerInfos(CDOLockOwner owner, int typeToReturn)
-  {
-    CheckUtil.checkArg(owner, "owner");
-    SingleOwnerInfo infoToReturn = null;
-
-    for (int type = 0; type < singleOwnerInfos.length; type++)
-    {
-      SingleOwnerInfo info = SingleOwnerInfo.create(owner, type);
-      singleOwnerInfos[type].put(owner, info);
-
-      if (type == typeToReturn)
-      {
-        infoToReturn = info;
-      }
-    }
-
-    return infoToReturn;
-  }
-
-  private void removeSingleOwnerInfos(CDOLockOwner owner)
-  {
-    CheckUtil.checkArg(owner, "owner");
-    for (int type = 0; type < singleOwnerInfos.length; type++)
-    {
-      singleOwnerInfos[type].remove(owner);
-    }
-  }
-
   private SingleOwnerInfo getSingleOwnerInfo(CDOLockOwner owner, int type)
   {
     SingleOwnerInfo info = singleOwnerInfos[type].get(owner);
@@ -502,6 +590,7 @@
         OwnerInfo newInfo = changer.apply(info, owner);
         if (newInfo != info)
         {
+          trace(id, info, newInfo);
           changed[0] = true;
           return newInfo;
         }
@@ -513,6 +602,14 @@
     });
   }
 
+  private void remapOwnerInfo(OwnerInfo info, CDOLockOwner oldOwner, CDOLockOwner newOwner)
+  {
+    if (info.remapOwner(oldOwner, newOwner) && DEBUG)
+    {
+      IOUtil.OUT().println("Remap owner: " + info);
+    }
+  }
+
   private void collectLockStates(CDOBranch branch, Collection<CDOID> ids, Set<CDOID> missingIDs, Consumer<CDOLockState> consumer)
   {
     if (branch != null)
@@ -548,12 +645,26 @@
     }
   }
 
-  private void loadAndCacheLockStates(CDOBranch branch, Set<CDOID> ids, Consumer<CDOLockState> consumer)
+  private void loadLockStates(CDOBranch branch, Set<CDOID> ids, Consumer<CDOLockState> consumer)
   {
     CDOSessionProtocol sessionProtocol = session.getSessionProtocol();
-    CDOLockState[] loadedLockStates = sessionProtocol.getLockStates(branch.getID(), ids, CDOLockState.DEPTH_NONE);
+    List<CDOLockState> lockStates = sessionProtocol.getLockStates2(branch.getID(), ids, CDOLockState.DEPTH_NONE);
 
-    addLockStates(branch, Arrays.asList(loadedLockStates), consumer);
+    addLockStates(branch, lockStates, consumer);
+  }
+
+  private void addLockState(ConcurrentMap<CDOID, OwnerInfo> infos, CDOLockState lockState, Consumer<CDOLockState> consumer)
+  {
+    CDOID id = lockState.getID();
+    OwnerInfo info = createOwnerInfo(lockState);
+
+    trace(id, null, info);
+    infos.put(id, info);
+
+    if (consumer != null)
+    {
+      consumer.accept(lockState);
+    }
   }
 
   private OwnerInfo createOwnerInfo(CDOLockState lockState)
@@ -570,8 +681,18 @@
         info = info.addReadLockOwner(this, readLockOwner);
       }
 
-      info = info.setWriteLockOwner(this, lockState.getWriteLockOwner());
-      info = info.setWriteOptionOwner(this, lockState.getWriteOptionOwner());
+      info = info.addWriteLockOwner(this, lockState.getWriteLockOwner());
+      info = info.addWriteOptionOwner(this, lockState.getWriteOptionOwner());
+    }
+    catch (ObjectAlreadyLockedException ex)
+    {
+      CDOBranch branch = lockState.getBranch();
+      if (branch == null)
+      {
+        branch = mainBranch;
+      }
+
+      throw new ObjectAlreadyLockedException(lockState.getID(), branch, ex);
     }
     catch (RuntimeException | Error ex)
     {
@@ -581,6 +702,11 @@
     return info;
   }
 
+  private boolean appendRemoveDelta(List<CDOLockDelta> deltas, CDOBranch branch, CDOID id, LockType lockType, CDOLockOwner owner)
+  {
+    return deltas.add(CDOLockUtil.createLockDelta(createKey(branch, id), lockType, owner, null));
+  }
+
   private static boolean isOwnerInfoLocked(OwnerInfo info, LockType lockType, CDOLockOwner lockOwner, boolean others)
   {
     if (lockType == null)
@@ -664,6 +790,21 @@
     return writeOptionOwner == by ^ others;
   }
 
+  private void trace(CDOID id, OwnerInfo oldInfo, OwnerInfo newInfo)
+  {
+    if (DEBUG)
+    {
+      String message = "[" + session.getSessionID() + "] " + id + ": " + oldInfo + " --> " + newInfo;
+
+      if (DEBUG_STACK_TRACE)
+      {
+        message += "\n" + ReflectUtil.dumpThread();
+      }
+
+      IOUtil.OUT().println(message);
+    }
+  }
+
   /**
    * @author Eike Stepper
    */
@@ -688,19 +829,78 @@
 
     public abstract OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
 
-    public abstract OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
+    public abstract OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
 
-    public abstract OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
+    public abstract OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
 
-    public abstract OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
+    public abstract OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
 
-    public abstract void remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner);
+    public abstract OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner);
+
+    public abstract OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner, Consumer<LockType> consumer);
+
+    public abstract boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner);
+
+    public final OwnerInfo applyDelta(CDOLockStateCacheImpl cache, CDOLockDelta delta)
+    {
+      OwnerInfo info = this;
+
+      CDOLockOwner oldOwner = delta.getOldOwner();
+      CDOLockOwner newOwner = delta.getNewOwner();
+
+      switch (delta.getType())
+      {
+      case READ:
+        if (oldOwner != null)
+        {
+          info = info.removeReadLockOwner(cache, oldOwner);
+        }
+
+        if (newOwner != null)
+        {
+          info = info.addReadLockOwner(cache, newOwner);
+        }
+
+        break;
+
+      case OPTION:
+        if (oldOwner != null)
+        {
+          info = info.removeWriteOptionOwner(cache, oldOwner);
+        }
+
+        if (newOwner != null)
+        {
+          info = info.addWriteOptionOwner(cache, newOwner);
+        }
+
+        break;
+
+      case WRITE:
+        if (oldOwner != null)
+        {
+          info = info.removeWriteLockOwner(cache, oldOwner);
+        }
+
+        if (newOwner != null)
+        {
+          info = info.addWriteLockOwner(cache, newOwner);
+        }
+
+        break;
+
+      default:
+        throw new AssertionError();
+      }
+
+      return info;
+    }
 
     @Override
     public String toString()
     {
       String name = getClass().getName();
-      name = StringUtil.replace(name, CDOLockStateCache.class.getName() + "$", "");
+      name = name.replace(CDOLockStateCacheImpl.class.getName() + "$", "");
       name = name.replace('$', '.');
       return name;
     }
@@ -738,19 +938,22 @@
     @Override
     public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
-      CheckUtil.checkArg(owner, "owner");
+      if (owner == null)
+      {
+        return this;
+      }
+
       return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLock.TYPE);
     }
 
     @Override
     public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
-      CheckUtil.checkArg(owner, "owner");
       return this;
     }
 
     @Override
-    public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
       if (owner == null)
       {
@@ -761,7 +964,13 @@
     }
 
     @Override
-    public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    {
+      return this;
+    }
+
+    @Override
+    public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
       if (owner == null)
       {
@@ -772,15 +981,22 @@
     }
 
     @Override
-    public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
       return this;
     }
 
     @Override
-    public void remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner, Consumer<LockType> consumer)
+    {
+      return this;
+    }
+
+    @Override
+    public boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
     {
       // Do nothing.
+      return false;
     }
   }
 
@@ -815,10 +1031,11 @@
     }
 
     @Override
-    public final OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    public final OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner, Consumer<LockType> consumer)
     {
       if (owner == this.owner)
       {
+        notifyLockTypes(consumer);
         return NoOwnerInfo.INSTANCE;
       }
 
@@ -826,12 +1043,15 @@
     }
 
     @Override
-    public final void remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    public final boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
     {
       if (owner == oldOwner)
       {
         owner = newOwner;
+        return true;
       }
+
+      return false;
     }
 
     @Override
@@ -840,6 +1060,21 @@
       return super.toString() + "[" + owner + "]";
     }
 
+    protected final ObjectAlreadyLockedException failure(CDOLockOwner owner, LockType lockType)
+    {
+      StringJoiner joiner = new StringJoiner(" and the ", //
+          owner + " could not acquire the " + lockType + " lock because the ", //
+          " is already acquired by " + this.owner);
+
+      notifyLockTypes(type -> {
+        joiner.add(type + " lock");
+      });
+
+      return new ObjectAlreadyLockedException(joiner.toString());
+    }
+
+    protected abstract void notifyLockTypes(Consumer<LockType> consumer);
+
     public static SingleOwnerInfo create(CDOLockOwner owner, int type)
     {
       switch (type)
@@ -885,7 +1120,10 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
+        if (owner == null)
+        {
+          return this;
+        }
 
         if (owner == this.owner)
         {
@@ -898,12 +1136,11 @@
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
-        return removeOwner(cache, owner);
+        return removeOwner(cache, owner, null);
       }
 
       @Override
-      public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
@@ -915,11 +1152,17 @@
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLockAndWriteLock.TYPE);
         }
 
-        throw new IllegalStateException("Read lock already obtained");
+        throw failure(owner, LockType.WRITE);
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
@@ -933,6 +1176,21 @@
 
         return new MultiOwnerInfo.ReadLockAndWriteOption(owner, this.owner);
       }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      protected void notifyLockTypes(Consumer<LockType> consumer)
+      {
+        if (consumer != null)
+        {
+          consumer.accept(LockType.READ);
+        }
+      }
     }
 
     /**
@@ -962,19 +1220,22 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
+        if (owner == null)
+        {
+          return this;
+        }
+
         if (owner == this.owner)
         {
           return this;
         }
 
-        throw new IllegalStateException("Write lock already obtained");
+        throw failure(owner, LockType.READ);
       }
 
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
         if (owner == this.owner)
         {
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.WriteLock.TYPE);
@@ -984,11 +1245,11 @@
       }
 
       @Override
-      public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
-          return cache.getSingleOwnerInfo(this.owner, SingleOwnerInfo.ReadLock.TYPE);
+          return this;
         }
 
         if (owner == this.owner)
@@ -996,18 +1257,42 @@
           return this;
         }
 
-        throw new IllegalStateException("Read lock already obtained");
+        throw failure(owner, LockType.WRITE);
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        if (owner == this.owner)
+        {
+          return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLock.TYPE);
+        }
+
+        return this;
+      }
+
+      @Override
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
           return this;
         }
 
-        throw new IllegalStateException("Write lock already obtained");
+        throw failure(owner, LockType.OPTION);
+      }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      protected void notifyLockTypes(Consumer<LockType> consumer)
+      {
+        consumer.accept(LockType.READ);
+        consumer.accept(LockType.WRITE);
       }
     }
 
@@ -1038,7 +1323,11 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
+        if (owner == null)
+        {
+          return this;
+        }
+
         if (owner == this.owner)
         {
           return this;
@@ -1050,7 +1339,6 @@
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
         if (owner == this.owner)
         {
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.WriteOption.TYPE);
@@ -1060,7 +1348,7 @@
       }
 
       @Override
-      public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
@@ -1072,15 +1360,21 @@
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.WriteLock.TYPE);
         }
 
-        throw new IllegalStateException("Read lock already obtained");
+        throw failure(owner, LockType.WRITE);
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
-          return cache.getSingleOwnerInfo(this.owner, SingleOwnerInfo.ReadLock.TYPE);
+          return this;
         }
 
         if (owner == this.owner)
@@ -1088,7 +1382,25 @@
           return this;
         }
 
-        throw new IllegalStateException("Write option already obtained");
+        throw failure(owner, LockType.OPTION);
+      }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        if (owner == this.owner)
+        {
+          return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLock.TYPE);
+        }
+
+        return this;
+      }
+
+      @Override
+      protected void notifyLockTypes(Consumer<LockType> consumer)
+      {
+        consumer.accept(LockType.READ);
+        consumer.accept(LockType.OPTION);
       }
     }
 
@@ -1113,23 +1425,31 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
-        throw new IllegalStateException("Write lock already obtained");
+        if (owner == null)
+        {
+          return this;
+        }
+
+        if (owner == this.owner)
+        {
+          return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLockAndWriteLock.TYPE);
+        }
+
+        throw failure(owner, LockType.READ);
       }
 
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
         return this;
       }
 
       @Override
-      public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
-          return NoOwnerInfo.INSTANCE;
+          return this;
         }
 
         if (owner == this.owner)
@@ -1137,18 +1457,41 @@
           return this;
         }
 
-        throw new IllegalStateException("Write lock already obtained");
+        throw failure(owner, LockType.WRITE);
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        if (owner == this.owner)
+        {
+          return NoOwnerInfo.INSTANCE;
+        }
+
+        return this;
+      }
+
+      @Override
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
           return this;
         }
 
-        throw new IllegalStateException("Write lock already obtained");
+        throw failure(owner, LockType.OPTION);
+      }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      protected void notifyLockTypes(Consumer<LockType> consumer)
+      {
+        consumer.accept(LockType.WRITE);
       }
     }
 
@@ -1173,7 +1516,11 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
+        if (owner == null)
+        {
+          return this;
+        }
+
         if (owner == this.owner)
         {
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.ReadLockAndWriteOption.TYPE);
@@ -1185,12 +1532,11 @@
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
         return this;
       }
 
       @Override
-      public OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
@@ -1202,15 +1548,21 @@
           return cache.getSingleOwnerInfo(owner, SingleOwnerInfo.WriteLock.TYPE);
         }
 
-        throw new IllegalStateException("Write option already obtained");
+        throw failure(owner, LockType.WRITE);
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        return this;
+      }
+
+      @Override
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
-          return NoOwnerInfo.INSTANCE;
+          return this;
         }
 
         if (owner == this.owner)
@@ -1218,7 +1570,24 @@
           return this;
         }
 
-        throw new IllegalStateException("Write option already obtained");
+        throw failure(owner, LockType.OPTION);
+      }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        if (owner == this.owner)
+        {
+          return NoOwnerInfo.INSTANCE;
+        }
+
+        return this;
+      }
+
+      @Override
+      protected void notifyLockTypes(Consumer<LockType> consumer)
+      {
+        consumer.accept(LockType.OPTION);
       }
     }
   }
@@ -1239,14 +1608,33 @@
     }
 
     @Override
-    public final OwnerInfo setWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    public final OwnerInfo addWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
     {
       if (owner == null)
       {
         return this;
       }
 
-      throw new IllegalStateException();
+      throw failure(owner, LockType.WRITE);
+    }
+
+    @Override
+    public final OwnerInfo removeWriteLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+    {
+      return this;
+    }
+
+    protected final ObjectAlreadyLockedException failure(CDOLockOwner owner, LockType lockType)
+    {
+      String message = owner + " could not acquire the " + lockType + " lock because the READ lock is already acquired by " + getReadLockOwners();
+
+      CDOLockOwner writeOptionOwner = getWriteOptionOwner();
+      if (writeOptionOwner != null)
+      {
+        message += " and the OPTION lock is already acquired by " + writeOptionOwner;
+      }
+
+      return new ObjectAlreadyLockedException(message);
     }
 
     /**
@@ -1271,7 +1659,10 @@
       @Override
       public OwnerInfo addReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
+        if (owner == null)
+        {
+          return this;
+        }
 
         CDOLockOwner[] owners = readLockOwners;
         if (CDOLockUtil.indexOf(owners, owner) == -1)
@@ -1289,8 +1680,6 @@
       @Override
       public OwnerInfo removeReadLockOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        CheckUtil.checkArg(owner, "owner");
-
         CDOLockOwner[] owners = readLockOwners;
         int index = CDOLockUtil.indexOf(owners, owner);
         if (index != -1)
@@ -1343,7 +1732,7 @@
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
@@ -1354,20 +1743,42 @@
       }
 
       @Override
-      public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
-        return removeReadLockOwner(cache, owner);
+        return this;
       }
 
       @Override
-      public void remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+      public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner, Consumer<LockType> consumer)
+      {
+        OwnerInfo newInfo = removeReadLockOwner(cache, owner);
+
+        if (newInfo != this)
+        {
+          consumer.accept(LockType.READ);
+        }
+
+        return newInfo;
+      }
+
+      @Override
+      public boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
       {
         CDOLockOwner[] owners = readLockOwners;
         int index = CDOLockUtil.indexOf(owners, oldOwner);
         if (index != -1)
         {
           readLockOwners[index] = newOwner;
+          return true;
         }
+
+        return false;
+      }
+
+      @Override
+      public String toString()
+      {
+        return super.toString() + "[readLockOwners=" + Arrays.asList(readLockOwners) + "]";
       }
 
       private static MultiOwnerInfo createMultiOwnerInfo(CDOLockOwner writeOptionOwner, CDOLockOwner... readLockOwners)
@@ -1401,43 +1812,64 @@
       }
 
       @Override
-      public OwnerInfo setWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo addWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
       {
         if (owner == null)
         {
-          return new MultiOwnerInfo.ReadLock(readLockOwners);
+          return this;
         }
 
-        if (writeOptionOwner != owner)
+        if (owner == writeOptionOwner)
         {
-          throw new IllegalStateException();
+          return this;
+        }
+
+        throw failure(owner, LockType.OPTION);
+      }
+
+      @Override
+      public OwnerInfo removeWriteOptionOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      {
+        if (owner == writeOptionOwner)
+        {
+          return new MultiOwnerInfo.ReadLock(readLockOwners);
         }
 
         return this;
       }
 
       @Override
-      public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner)
+      public OwnerInfo removeOwner(CDOLockStateCacheImpl cache, CDOLockOwner owner, Consumer<LockType> consumer)
       {
-        OwnerInfo newInfo = super.removeOwner(cache, owner);
+        OwnerInfo newInfo = super.removeOwner(cache, owner, consumer);
 
         if (owner == newInfo.getWriteOptionOwner())
         {
-          return newInfo.setWriteOptionOwner(cache, null);
+          consumer.accept(LockType.OPTION);
+          return newInfo.removeWriteOptionOwner(cache, owner);
         }
 
         return newInfo;
       }
 
       @Override
-      public void remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+      public boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
       {
-        super.remapOwner(oldOwner, newOwner);
+        boolean remapped = super.remapOwner(oldOwner, newOwner);
 
         if (writeOptionOwner == oldOwner)
         {
           writeOptionOwner = newOwner;
+          remapped = true;
         }
+
+        return remapped;
+      }
+
+      @Override
+      public String toString()
+      {
+        return super.toString() + "[readLockOwners=" + Arrays.asList(readLockOwners) + ", writeOptionOwner=" + writeOptionOwner + "]";
       }
     }
   }
@@ -1445,21 +1877,16 @@
   /**
    * @author Eike Stepper
    */
-  private static abstract class LockState extends AbstractCDOLockState
+  private static final class LockState extends AbstractCDOLockState
   {
     private static final Set<CDOLockOwner> NO_LOCK_OWNERS = Collections.emptySet();
 
-    protected final Object key;
+    private final CDOLockStateCacheImpl cache;
 
-    public LockState(Object key)
+    public LockState(Object lockedObject, CDOLockStateCacheImpl cache)
     {
-      this.key = key;
-    }
-
-    @Override
-    public final Object getLockedObject()
-    {
-      return key;
+      super(lockedObject);
+      this.cache = cache;
     }
 
     @Override
@@ -1509,50 +1936,75 @@
       return info.getWriteOptionOwner();
     }
 
-    protected abstract OwnerInfo getInfo();
-  }
-
-  /**
-   * @author Eike Stepper
-   */
-  private static final class MutableLockState extends LockState implements InternalCDOLockState
-  {
-    private final CDOLockStateCacheImpl cache;
-
-    public MutableLockState(Object key, CDOLockStateCacheImpl cache)
+    @Override
+    protected CDOLockDelta addReadOwner(CDOLockOwner owner)
     {
-      super(key);
-      this.cache = cache;
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.addReadLockOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, null, owner);
+      }
+
+      return null;
     }
 
     @Override
-    public void addReadLockOwner(CDOLockOwner owner)
+    protected CDOLockDelta addWriteOwner(CDOLockOwner owner)
     {
-      cache.changeOwnerInfo(key, owner, (i, o) -> i.addReadLockOwner(cache, o));
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.addWriteLockOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.WRITE, null, owner);
+      }
+
+      return null;
     }
 
     @Override
-    public boolean removeReadLockOwner(CDOLockOwner owner)
+    protected CDOLockDelta addOptionOwner(CDOLockOwner owner)
     {
-      return cache.changeOwnerInfo(key, owner, (i, o) -> i.removeReadLockOwner(cache, o));
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.addWriteOptionOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.OPTION, null, owner);
+      }
+
+      return null;
     }
 
     @Override
-    public void setWriteLockOwner(CDOLockOwner owner)
+    protected CDOLockDelta removeReadOwner(CDOLockOwner owner)
     {
-      cache.changeOwnerInfo(key, owner, (i, o) -> i.setWriteLockOwner(cache, o));
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.removeReadLockOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.READ, owner, null);
+      }
+
+      return null;
     }
 
     @Override
-    public void setWriteOptionOwner(CDOLockOwner owner)
+    protected CDOLockDelta removeWriteOwner(CDOLockOwner owner)
     {
-      cache.changeOwnerInfo(key, owner, (i, o) -> i.setWriteOptionOwner(cache, o));
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.removeWriteLockOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.WRITE, owner, null);
+      }
+
+      return null;
     }
 
     @Override
-    public boolean removeOwner(CDOLockOwner owner)
+    protected CDOLockDelta removeOptionOwner(CDOLockOwner owner)
     {
-      return cache.changeOwnerInfo(key, owner, (i, o) -> i.removeOwner(cache, o));
+      if (cache.changeOwnerInfo(lockedObject, owner, (i, o) -> i.removeWriteOptionOwner(cache, o)))
+      {
+        return CDOLockUtil.createLockDelta(lockedObject, LockType.OPTION, owner, null);
+      }
+
+      return null;
+    }
+
+    private OwnerInfo getInfo()
+    {
+      return cache.getOwnerInfo(lockedObject);
     }
 
     /**
@@ -1560,59 +2012,9 @@
      */
     @Deprecated
     @Override
-    public boolean remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
+    public CDOLockDelta[] remapOwner(CDOLockOwner oldOwner, CDOLockOwner newOwner)
     {
       throw new UnsupportedOperationException();
     }
-
-    @Deprecated
-    @Override
-    public void updateFrom(Object object, CDOLockState source)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @deprecated Only used for new objects and those are local to a transaction, i.e., not cached.
-     */
-    @Deprecated
-    @Override
-    public void updateFrom(CDOLockState source)
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    public void dispose()
-    {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    protected OwnerInfo getInfo()
-    {
-      return cache.getOwnerInfo(key);
-    }
-  }
-
-  /**
-   * @author Eike Stepper
-   */
-  private static final class ImmutableLockState extends LockState
-  {
-    private final OwnerInfo info;
-
-    public ImmutableLockState(Object key, OwnerInfo info)
-    {
-      super(key);
-      this.info = info;
-    }
-
-    @Override
-    protected OwnerInfo getInfo()
-    {
-      return info;
-    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
index a389bc3..65759c4 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
@@ -34,6 +34,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLobInfo;
 import org.eclipse.emf.cdo.common.lob.CDOLobStore;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
@@ -95,13 +96,12 @@
 import org.eclipse.emf.internal.cdo.messages.Messages;
 import org.eclipse.emf.internal.cdo.object.CDOFactoryImpl;
 import org.eclipse.emf.internal.cdo.session.remote.CDORemoteSessionManagerImpl;
-import org.eclipse.emf.internal.cdo.util.DefaultLocksChangedEvent;
+import org.eclipse.emf.internal.cdo.util.AbstractLocksChangedEvent;
 
 import org.eclipse.net4j.util.AdapterUtil;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
-import org.eclipse.net4j.util.concurrent.IRWLockManager;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 import org.eclipse.net4j.util.concurrent.IRWOLockManager;
 import org.eclipse.net4j.util.concurrent.RWOLockManager;
@@ -153,6 +153,7 @@
 import java.io.Reader;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -687,7 +688,7 @@
   {
     try
     {
-      lockManager.lock(LockType.WRITE, key, this, IRWLockManager.WAIT);
+      lockManager.lock(key, singletonCollection, LockType.WRITE, 1, IRWOLockManager.NO_TIMEOUT, null, null);
     }
     catch (InterruptedException ex)
     {
@@ -698,7 +699,7 @@
   @Override
   public void releaseAtomicRequestLock(Object key)
   {
-    lockManager.unlock(LockType.WRITE, key, singletonCollection);
+    lockManager.unlock(key, singletonCollection, LockType.WRITE, 1, null, null);
   }
 
   @Override
@@ -1172,8 +1173,9 @@
       }
       else
       {
-        List<CDOLockState> lockStates = lockChangeInfo.getNewLockStates();
-        lockStateCache.addLockStates(branch, lockStates, null);
+        CDOLockDelta[] lockDeltas = lockChangeInfo.getLockDeltas();
+        CDOLockState[] lockStates = lockChangeInfo.getLockStates();
+        lockStateCache.updateLockStates(branch, Arrays.asList(lockDeltas), Arrays.asList(lockStates), null);
       }
     }
 
@@ -2763,7 +2765,7 @@
    * @author Caspar De Groot
    * @since 4.1
    */
-  private final class SessionLocksChangedEvent extends DefaultLocksChangedEvent implements CDOSessionLocksChangedEvent
+  private final class SessionLocksChangedEvent extends AbstractLocksChangedEvent implements CDOSessionLocksChangedEvent
   {
     private static final long serialVersionUID = 1L;
 
@@ -2779,6 +2781,12 @@
     }
 
     @Override
+    protected InternalCDOSession getSession()
+    {
+      return (InternalCDOSession)getSource();
+    }
+
+    @Override
     protected String formatEventName()
     {
       return "CDOSessionLocksChangedEvent";
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
index 0492efa..807529e 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
@@ -28,6 +28,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lob.CDOLobInfo;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
@@ -77,6 +78,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -190,14 +192,14 @@
   }
 
   @Override
-  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID)
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID, BiConsumer<CDOID, LockGrade> consumer)
   {
     int attempt = 0;
     for (;;)
     {
       try
       {
-        return delegate.openView(viewID, readOnly, durableLockingID);
+        return delegate.openView(viewID, readOnly, durableLockingID, consumer);
       }
       catch (Exception ex)
       {
@@ -301,14 +303,6 @@
   }
 
   @Override
-  @Deprecated
-  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
-      Collection<CDOLob<?>> lobs, OMMonitor monitor)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public CommitTransactionResult commitTransaction(InternalCDOCommitContext context, OMMonitor monitor)
   {
     int attempt = 0;
@@ -326,14 +320,6 @@
   }
 
   @Override
-  @Deprecated
-  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
-      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public CommitTransactionResult commitDelegation(InternalCDOCommitContext context, OMMonitor monitor)
   {
     int attempt = 0;
@@ -471,21 +457,14 @@
   }
 
   @Override
-  @Deprecated
-  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth)
+  public List<CDOLockState> getLockStates2(int branchID, Collection<CDOID> ids, int depth)
   {
     int attempt = 0;
     for (;;)
     {
       try
       {
-        return delegate.getLockStates(branchID, ids, depth);
+        return delegate.getLockStates2(branchID, ids, depth);
       }
       catch (Exception ex)
       {
@@ -649,20 +628,6 @@
   }
 
   @Override
-  @Deprecated
-  public void deleteBranch(int branchID)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void renameBranch(int branchID, String newName)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void renameBranch(int branchID, String oldName, String newName)
   {
     int attempt = 0;
@@ -768,13 +733,6 @@
   }
 
   @Override
-  @Deprecated
-  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth,
       boolean prefetchLockStates)
   {
@@ -826,14 +784,6 @@
     }
   }
 
-  @Override
-  @Deprecated
-  public LockObjectsResult lockObjects(List<InternalCDORevision> viewedRevisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
-      throws InterruptedException
-  {
-    throw new UnsupportedOperationException();
-  }
-
   /**
    * @since 4.1
    */
@@ -982,13 +932,6 @@
   }
 
   @Override
-  @Deprecated
-  public void unlockObjects(CDOView view, Collection<CDOID> objectIDs, LockType lockType)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public UnlockObjectsResult unlockObjects2(CDOView view, Collection<CDOID> objectIDs, LockType lockType, boolean recursive)
   {
     int attempt = 0;
@@ -1110,14 +1053,6 @@
   }
 
   @Override
-  @Deprecated
-  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
-      CDORevisionAvailabilityInfo sourceBaseInfo)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public MergeDataResult loadMergeData2(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo,
       CDORevisionAvailabilityInfo targetBaseInfo, CDORevisionAvailabilityInfo sourceBaseInfo)
   {
@@ -1170,13 +1105,6 @@
   }
 
   @Override
-  @Deprecated
-  public void requestChangeCredentials()
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public void requestChangeServerPassword(AtomicReference<char[]> receiver)
   {
     int attempt = 0;
@@ -1266,4 +1194,92 @@
       throw WrappedException.wrap(ex);
     }
   }
+
+  @Override
+  @Deprecated
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
+      Collection<CDOLob<?>> lobs, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
+      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void deleteBranch(int branchID)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void renameBranch(int branchID, String newName)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<RevisionInfo> loadRevisions(List<RevisionInfo> infos, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public LockObjectsResult lockObjects(List<InternalCDORevision> viewedRevisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
+      throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlockObjects(CDOView view, Collection<CDOID> objectIDs, LockType lockType)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
+      CDORevisionAvailabilityInfo sourceBaseInfo)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void requestChangeCredentials()
+  {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
index 0b87b70..0981774 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
@@ -39,6 +39,7 @@
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lob.CDOLobStore;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
@@ -150,7 +151,7 @@
 import org.eclipse.net4j.util.collection.ConcurrentArray;
 import org.eclipse.net4j.util.collection.DelegatingCloseableIterator;
 import org.eclipse.net4j.util.collection.Pair;
-import org.eclipse.net4j.util.concurrent.IRWLockManager;
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
 import org.eclipse.net4j.util.event.IEvent;
 import org.eclipse.net4j.util.event.IListener;
@@ -224,6 +225,8 @@
 
   private static final boolean X_COMPRESSION = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.transaction.X_COMPRESSION");
 
+  private static final LockType[] ALL_LOCK_TYPES = LockType.values();
+
   private static final Method COPY_OBJECT_METHOD;
 
   static
@@ -4258,14 +4261,10 @@
   {
     InternalCDOObject object = super.remapObjectUnsynced(oldID);
 
-    InternalCDOLockState oldLockState = lockStatesOfNewObjects.remove(object);
-    if (oldLockState != null)
+    InternalCDOLockState lockState = lockStatesOfNewObjects.get(object);
+    if (lockState != null)
     {
-      Object lockedObject = getLockTarget(object); // CDOID or CDOIDAndBranch
-      InternalCDOLockState newLockState = (InternalCDOLockState)CDOLockUtil.createLockState(lockedObject);
-      newLockState.updateFrom(oldLockState);
-
-      lockStatesOfNewObjects.put(object, newLockState);
+      lockState.remapID(object.cdoID());
     }
 
     return object;
@@ -4342,61 +4341,35 @@
   }
 
   @Override
-  protected InternalCDOLockState createUpdatedLockStateForNewObject(CDOObject object, IRWLockManager.LockType lockType, boolean on)
+  protected boolean updateLockStateOfNewObject(CDOObject object, LockType lockType, boolean on, List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
     if (!FSMUtil.isNew(object))
     {
-      return null;
+      return false;
     }
 
-    InternalCDOLockState lockStateOfNewObject = getLockStateOfNewObject(object);
+    InternalCDOLockState lockState = getLockStateOfNewObject(object);
+    CDOLockOwner lockOwner = getLockOwner();
 
-    switch (lockType)
-    {
-    case READ:
-      if (on)
-      {
-        lockStateOfNewObject.addReadLockOwner(getLockOwner());
-      }
-      else
-      {
-        lockStateOfNewObject.removeReadLockOwner(getLockOwner());
-      }
-      break;
+    CDOLockDelta lockDelta = on ? lockState.addOwner(lockOwner, lockType) : lockState.removeOwner(lockOwner, lockType);
 
-    case WRITE:
-      lockStateOfNewObject.setWriteLockOwner(on ? getLockOwner() : null);
-      break;
-
-    case OPTION:
-      lockStateOfNewObject.setWriteOptionOwner(on ? getLockOwner() : null);
-      break;
-
-    default:
-      throw new IllegalArgumentException("Unknown lock type " + lockType);
-    }
-
-    return lockStateOfNewObject;
+    lockDeltas.add(lockDelta);
+    lockStates.add(lockState);
+    return true;
   }
 
   @Override
-  protected List<CDOLockState> createUnlockedLockStatesForAllNewObjects()
+  protected void unlockAllNewObjects(List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
-    Collection<CDOObject> newObjects = getNewObjects().values();
-    if (newObjects.isEmpty())
+    for (Map.Entry<CDOObject, InternalCDOLockState> entry : lockStatesOfNewObjects.entrySet())
     {
-      return null;
-    }
+      CDOObject object = entry.getKey();
 
-    List<CDOLockState> locksOnNewObjects = new ArrayList<>();
-    for (CDOObject object : newObjects)
-    {
-      Object lockTarget = getLockTarget(object);
-      CDOLockState lockState = CDOLockUtil.createLockState(lockTarget);
-      locksOnNewObjects.add(lockState);
+      for (LockType lockType : ALL_LOCK_TYPES)
+      {
+        updateLockStateOfNewObject(object, lockType, false, lockDeltas, lockStates);
+      }
     }
-
-    return locksOnNewObjects;
   }
 
   @Override
@@ -5073,9 +5046,11 @@
       CDOLockStateCache lockStateCache = getSession().getLockStateCache();
       CDOLockOwner lockOwner = getLockOwner();
 
-      List<CDOLockState> lockStatesToChangeInOldBranch = new ArrayList<>();
-      List<CDOLockState> lockStatesToChangeInNewBranch = new ArrayList<>();
-      List<CDOLockState> releasedLocks = new ArrayList<>();
+      List<CDOLockDelta> lockDeltasOldBranch = new ArrayList<>();
+      List<CDOLockState> lockStatesOldBranch = new ArrayList<>();
+
+      List<CDOLockDelta> lockDeltasNewBranch = new ArrayList<>();
+      List<CDOLockState> lockStatesNewBranch = new ArrayList<>();
 
       // 1. Process detached objects.
       lockStateCache.removeLockStates(oldBranch, detachedObjectIDs, null);
@@ -5087,6 +5062,11 @@
       for (Map.Entry<CDOObject, InternalCDOLockState> entry : lockStatesOfNewObjects.entrySet())
       {
         CDOObject object = entry.getKey();
+        if (FSMUtil.isTransient(object))
+        {
+          continue;
+        }
+
         InternalCDOLockState lockState = entry.getValue();
         CDOID id = lockState.getID();
 
@@ -5096,27 +5076,43 @@
           newID = id;
         }
 
-        // Remember to remap the lock target in old branch.
         if (newID != id || branchChanged)
         {
-          Object newLockTarget = getLockTarget(newID);
-          lockState = (InternalCDOLockState)CDOLockUtil.copyLockState(lockState, newLockTarget);
-          lockStatesToChangeInOldBranch.add(lockState);
+          lockState.remapID(newID);
         }
 
+        Object lockTarget = lockState.getLockedObject();
+        boolean deltaAdded = false;
+
         // Auto-release locks.
         if (options().isEffectiveAutoReleaseLock(object))
         {
-          if (lockState.removeOwner(lockOwner))
+          for (CDOLockDelta lockDelta : lockState.clearOwner(lockOwner))
           {
-            releasedLocks.add(lockState);
+            lockDeltasNewBranch.add(lockDelta);
+            deltaAdded = true;
           }
         }
         else
         {
           // Transfer lock state of the new object into the session-scoped lock state cache.
-          lockStatesToChangeInNewBranch.add(lockState);
+          for (LockType lockType : ALL_LOCK_TYPES)
+          {
+            if (lockState.isLocked(lockType, lockOwner, false))
+            {
+              lockDeltasNewBranch.add(CDOLockUtil.createLockDelta(lockTarget, lockType, null, lockOwner));
+              deltaAdded = true;
+            }
+          }
         }
+
+        if (!deltaAdded)
+        {
+          // Add a Null delta to make sure that lockStateCache.updateLockStates() does not ignore the lock state.
+          lockDeltasNewBranch.add(CDOLockUtil.createLockDelta(lockTarget));
+        }
+
+        lockStatesNewBranch.add(lockState);
       }
 
       // 3. Process all other locks.
@@ -5124,31 +5120,47 @@
         // Auto-release locks.
         if (options().isEffectiveAutoReleaseLock(object))
         {
-          if (((InternalCDOLockState)lockState).removeOwner(lockOwner))
+          for (CDOLockDelta lockDelta : ((InternalCDOLockState)lockState).clearOwner(lockOwner))
           {
-            releasedLocks.add(lockState);
+            lockDeltasOldBranch.add(lockDelta);
           }
+
+          lockStatesOldBranch.add(lockState);
         }
       });
 
       lockStatesOfNewObjects.clear();
 
-      if (!lockStatesToChangeInOldBranch.isEmpty())
+      if (!lockDeltasNewBranch.isEmpty())
       {
-        lockStateCache.addLockStates(oldBranch, lockStatesToChangeInOldBranch, null);
+        lockStateCache.updateLockStates(newBranch, lockDeltasNewBranch, lockStatesNewBranch, null);
+
+        // Remove the Null deltas before makeLockChangeInfo() is called.
+        for (Iterator<CDOLockDelta> it = lockDeltasNewBranch.iterator(); it.hasNext();)
+        {
+          CDOLockDelta delta = it.next();
+          if (delta.getType() == null)
+          {
+            it.remove();
+          }
+        }
       }
 
-      if (!lockStatesToChangeInNewBranch.isEmpty())
+      if (!lockDeltasOldBranch.isEmpty())
       {
-        lockStateCache.addLockStates(newBranch, lockStatesToChangeInNewBranch, null);
+        lockStateCache.updateLockStates(oldBranch, lockDeltasOldBranch, lockStatesOldBranch, null);
+
+        // Make sure that the changes on the old branch end up in the lockChangeInfo below.
+        lockDeltasNewBranch.addAll(lockDeltasOldBranch);
+        lockStatesNewBranch.addAll(lockStatesOldBranch);
       }
 
-      if (!releasedLocks.isEmpty())
+      if (lockDeltasNewBranch.isEmpty())
       {
-        return makeLockChangeInfo(CDOLockChangeInfo.Operation.UNLOCK, null, result.getTimeStamp(), releasedLocks);
+        return null;
       }
 
-      return null;
+      return makeLockChangeInfo(result.getTimeStamp(), lockDeltasNewBranch, lockStatesNewBranch);
     }
 
     private void collectLobs(InternalCDORevision revision, Map<ByteArrayWrapper, CDOLob<?>> lobs)
@@ -5372,6 +5384,8 @@
 
     private final Map<EObject, Boolean> autoReleaseLocksExemptions = new WeakHashMap<>();
 
+    private long optimisticLockingTimeout = DEFAULT_OPTIMISTIC_LOCKING_TIMEOUT;
+
     private long commitInfoTimeout = DEFAULT_COMMIT_INFO_TIMEOUT;
 
     private Map<CDOID, CDORevision> attachedRevisionsMap;
@@ -5819,6 +5833,40 @@
     }
 
     @Override
+    public long getOptimisticLockingTimeout()
+    {
+      return optimisticLockingTimeout;
+    }
+
+    @Override
+    public void setOptimisticLockingTimeout(long optimisticLockingTimeout)
+    {
+      checkActive();
+
+      IEvent event = null;
+      synchronized (getViewMonitor())
+      {
+        lockView();
+
+        try
+        {
+          if (this.optimisticLockingTimeout != optimisticLockingTimeout)
+          {
+            this.optimisticLockingTimeout = optimisticLockingTimeout;
+            event = new OptimisticLockingTimeoutImpl();
+          }
+        }
+        finally
+        {
+          unlockView();
+        }
+      }
+
+      fireEvent(event);
+
+    }
+
+    @Override
     public long getCommitInfoTimeout()
     {
       return commitInfoTimeout;
@@ -5932,6 +5980,19 @@
     /**
      * @author Eike Stepper
      */
+    private final class OptimisticLockingTimeoutImpl extends OptionsEvent implements CommitInfoTimeout
+    {
+      private static final long serialVersionUID = 1L;
+
+      public OptimisticLockingTimeoutImpl()
+      {
+        super(OptionsImpl.this);
+      }
+    }
+
+    /**
+     * @author Eike Stepper
+     */
     private final class CommitInfoTimeoutImpl extends OptionsEvent implements CommitInfoTimeout
     {
       private static final long serialVersionUID = 1L;
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/DefaultLocksChangedEvent.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/AbstractLocksChangedEvent.java
similarity index 65%
rename from plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/DefaultLocksChangedEvent.java
rename to plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/AbstractLocksChangedEvent.java
index f4d13d3..7f70a27 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/DefaultLocksChangedEvent.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/util/AbstractLocksChangedEvent.java
@@ -12,22 +12,21 @@
 
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.spi.common.lock.AbstractCDOLockChangeInfo;
 
-import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
-import org.eclipse.net4j.util.event.Event;
 import org.eclipse.net4j.util.event.INotifier;
 
+import org.eclipse.emf.spi.cdo.InternalCDOSession;
 import org.eclipse.emf.spi.cdo.InternalCDOView;
 
-import java.util.List;
-
 /**
  * @author Caspar De Groot
  * @since 4.1
  */
-public class DefaultLocksChangedEvent extends Event implements CDOLockChangeInfo
+public abstract class AbstractLocksChangedEvent extends AbstractCDOLockChangeInfo
 {
   private static final long serialVersionUID = 1L;
 
@@ -35,7 +34,7 @@
 
   private final CDOLockChangeInfo lockChangeInfo;
 
-  public DefaultLocksChangedEvent(INotifier notifier, InternalCDOView sender, CDOLockChangeInfo lockChangeInfo)
+  public AbstractLocksChangedEvent(INotifier notifier, InternalCDOView sender, CDOLockChangeInfo lockChangeInfo)
   {
     super(notifier);
     this.sender = sender;
@@ -60,34 +59,21 @@
   }
 
   @Override
-  public final Operation getOperation()
-  {
-    return lockChangeInfo.getOperation();
-  }
-
-  @Override
-  public final LockType getLockType()
-  {
-    return lockChangeInfo.getLockType();
-  }
-
-  @Override
-  public final CDOLockOwner getLockOwner()
+  public CDOLockOwner getLockOwner()
   {
     return lockChangeInfo.getLockOwner();
   }
 
-  @Deprecated
   @Override
-  public final CDOLockState[] getLockStates()
+  public final CDOLockDelta[] getLockDeltas()
   {
-    return lockChangeInfo.getLockStates();
+    return lockChangeInfo.getLockDeltas();
   }
 
   @Override
-  public List<CDOLockState> getNewLockStates()
+  public CDOLockState[] getLockStates()
   {
-    return lockChangeInfo.getNewLockStates();
+    return lockChangeInfo.getLockStates();
   }
 
   @Override
@@ -96,14 +82,11 @@
     return lockChangeInfo.isInvalidateAll();
   }
 
-  protected final CDOLockChangeInfo getLockChangeInfo()
-  {
-    return lockChangeInfo;
-  }
-
   @Override
   protected String formatAdditionalParameters()
   {
-    return "sender=" + getSender() + ", " + getLockChangeInfo();
+    return "sender=" + sender + ", " + lockChangeInfo;
   }
+
+  protected abstract InternalCDOSession getSession();
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewImpl.java
index 65bb255..9db7916 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewImpl.java
@@ -22,10 +22,11 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
-import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants.UnitOpcode;
 import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
@@ -72,7 +73,7 @@
 import org.eclipse.emf.internal.cdo.object.CDONotificationBuilder;
 import org.eclipse.emf.internal.cdo.object.CDOObjectWrapperBase;
 import org.eclipse.emf.internal.cdo.session.SessionUtil;
-import org.eclipse.emf.internal.cdo.util.DefaultLocksChangedEvent;
+import org.eclipse.emf.internal.cdo.util.AbstractLocksChangedEvent;
 
 import org.eclipse.net4j.util.CheckUtil;
 import org.eclipse.net4j.util.ObjectUtil;
@@ -131,7 +132,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -183,6 +183,8 @@
   @ExcludeFromDump
   private boolean inverseClosing;
 
+  private DurableLockProcessor durableLockProcessor;
+
   /**
    * @since 2.0
    */
@@ -384,8 +386,12 @@
     checkActive();
     checkState(!isHistorical(), "Locking not supported for historial views");
 
-    List<CDOLockState> newLockStates = null;
-    List<CDOLockState> locksOnNewObjects = new ArrayList<>();
+    List<CDOLockDelta> newObjectLockDeltas = new ArrayList<>();
+    List<CDOLockState> newObjectLockStates = new ArrayList<>();
+
+    List<CDOLockDelta> sessionLockDeltas = null;
+    List<CDOLockState> sessionLockStates = null;
+
     long timeStamp = CDOBranchPoint.UNSPECIFIED_DATE;
 
     synchronized (getViewMonitor())
@@ -399,21 +405,14 @@
         // TODO For recursive locking consider local tree changes (NEW, DIRTY).
         for (CDOObject object : CollectionUtil.setOf(objects))
         {
-          CDOLockState lockState = createUpdatedLockStateForNewObject(object, lockType, true);
-          if (lockState != null)
+          if (updateLockStateOfNewObject(object, lockType, true, newObjectLockDeltas, newObjectLockStates))
           {
-            locksOnNewObjects.add(lockState);
-
             if (recursive)
             {
               for (TreeIterator<EObject> it = object.eAllContents(); it.hasNext();)
               {
                 CDOObject child = CDOUtil.getCDOObject(it.next());
-                lockState = createUpdatedLockStateForNewObject(child, lockType, true);
-                if (lockState != null)
-                {
-                  locksOnNewObjects.add(lockState);
-                }
+                updateLockStateOfNewObject(child, lockType, true, newObjectLockDeltas, newObjectLockStates);
               }
             }
           }
@@ -484,10 +483,14 @@
 
         if (result != null)
         {
-          newLockStates = Arrays.asList(result.getNewLockStates());
           timeStamp = result.getTimestamp();
 
-          updateLockStates(newLockStates, false, null);
+          sessionLockDeltas = result.getLockDeltas();
+          if (!ObjectUtil.isEmpty(sessionLockDeltas))
+          {
+            sessionLockStates = result.getLockStates();
+            updateLockStates(sessionLockDeltas, sessionLockStates);
+          }
         }
       }
       finally
@@ -496,57 +499,43 @@
       }
     }
 
-    if (!locksOnNewObjects.isEmpty())
+    if (!newObjectLockDeltas.isEmpty())
     {
-      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(Operation.LOCK, lockType, getTimeStamp(), locksOnNewObjects);
+      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(getTimeStamp(), newObjectLockDeltas, newObjectLockStates);
       fireLocksChangedEvent(this, lockChangeInfo);
     }
 
-    if (newLockStates != null)
+    if (!ObjectUtil.isEmpty(sessionLockDeltas))
     {
-      notifyLockChanges(Operation.LOCK, lockType, timeStamp, newLockStates);
+      notifyLockChanges(timeStamp, sessionLockDeltas, sessionLockStates);
     }
   }
 
-  @Deprecated
-  @Override
-  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  /**
-   * Updates the lock states of objects held in this view.
-   * <p>
-   * The caller must hold the view lock!
-   *
-   * @param newLockStates new {@link CDOLockState lockStates} to integrate in cache
-   * @param loadObjectsOnDemand true to load corresponding {@link CDOObject} if not already loaded to be able to store lockState in cache, false otherwise
-   * @since 4.5
-   */
-  @Override
-  public final void updateLockStates(Collection<? extends CDOLockState> newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
+  private void updateLockStates(List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
     CDOBranch branch = getBranch();
     CDOLockStateCache lockStateCache = session.getLockStateCache();
-    lockStateCache.addLockStates(branch, newLockStates, consumer);
+    lockStateCache.updateLockStates(branch, lockDeltas, lockStates, null);
   }
 
-  protected final void notifyLockChanges(Operation op, LockType type, long timestamp, Collection<? extends CDOLockState> lockStates)
+  protected final void notifyLockChanges(long timestamp, Collection<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
-    if (isActive() && !lockStates.isEmpty())
+    if (isActive() && !lockDeltas.isEmpty())
     {
-      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(op, type, timestamp, lockStates);
+      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(timestamp, lockDeltas, lockStates);
       notifyLockChanges(lockChangeInfo);
     }
   }
 
+  /**
+   * Notifies the session (including other views) and this view.
+   */
   protected final void notifyLockChanges(CDOLockChangeInfo lockChangeInfo)
   {
     if (isActive() && lockChangeInfo != null)
     {
-      List<CDOLockState> lockStates = lockChangeInfo.getNewLockStates();
-      if (!ObjectUtil.isEmpty(lockStates) || lockChangeInfo.isInvalidateAll())
+      CDOLockDelta[] lockDeltas = lockChangeInfo.getLockDeltas();
+      if (!ObjectUtil.isEmpty(lockDeltas) || lockChangeInfo.isInvalidateAll())
       {
         // Do not call out from the current thread to other views while this view is holding its view lock!
         session.handleLockNotification(lockChangeInfo, this, true);
@@ -555,10 +544,11 @@
     }
   }
 
-  protected final CDOLockChangeInfo makeLockChangeInfo(Operation op, LockType type, long timestamp, Collection<? extends CDOLockState> newLockStates)
+  protected final CDOLockChangeInfo makeLockChangeInfo(long timestamp, Collection<CDOLockDelta> lockDeltas, Collection<CDOLockState> lockStates)
   {
     CDOBranchPoint branchPoint = getBranch().getPoint(timestamp);
-    return CDOLockUtil.createLockChangeInfo(branchPoint, getLockOwner(), op, type, newLockStates);
+    CDOLockOwner lockOwner = getLockOwner();
+    return CDOLockUtil.createLockChangeInfo(branchPoint, lockOwner, lockDeltas, lockStates);
   }
 
   @Override
@@ -622,8 +612,12 @@
   {
     checkActive();
 
-    Collection<? extends CDOLockState> newLockStates = null;
-    List<CDOLockState> locksOnNewObjects = new LinkedList<>();
+    List<CDOLockDelta> newObjectLockDeltas = new ArrayList<>();
+    List<CDOLockState> newObjectLockStates = new ArrayList<>();
+
+    List<CDOLockDelta> sessionLockDeltas = null;
+    List<CDOLockState> sessionLockStates = null;
+
     long timeStamp = CDOBranchPoint.UNSPECIFIED_DATE;
 
     synchronized (getViewMonitor())
@@ -640,18 +634,15 @@
 
           for (CDOObject object : CollectionUtil.setOf(objects))
           {
-            CDOLockState lockState = createUpdatedLockStateForNewObject(object, lockType, false);
-            if (lockState != null)
+            updateLockStateOfNewObject(object, lockType, false, newObjectLockDeltas, newObjectLockStates);
+            if (updateLockStateOfNewObject(object, lockType, false, newObjectLockDeltas, newObjectLockStates))
             {
-              locksOnNewObjects.add(lockState);
-
               if (recursive)
               {
                 for (TreeIterator<EObject> it = object.eAllContents(); it.hasNext();)
                 {
                   CDOObject child = CDOUtil.getCDOObject(it.next());
-                  lockState = createUpdatedLockStateForNewObject(child, lockType, false);
-                  locksOnNewObjects.add(lockState);
+                  updateLockStateOfNewObject(child, lockType, false, newObjectLockDeltas, newObjectLockStates);
                 }
               }
             }
@@ -671,11 +662,7 @@
         }
         else
         {
-          Collection<CDOLockState> lockStates = createUnlockedLockStatesForAllNewObjects();
-          if (!ObjectUtil.isEmpty(lockStates))
-          {
-            locksOnNewObjects.addAll(lockStates);
-          }
+          unlockAllNewObjects(newObjectLockDeltas, newObjectLockStates);
         }
 
         UnlockObjectsResult result = null;
@@ -687,10 +674,13 @@
 
         if (result != null)
         {
-          newLockStates = Arrays.asList(result.getNewLockStates());
           timeStamp = result.getTimestamp();
-
-          updateLockStates(newLockStates, false, null);
+          sessionLockDeltas = result.getLockDeltas();
+          if (!ObjectUtil.isEmpty(sessionLockDeltas))
+          {
+            sessionLockStates = result.getLockStates();
+            updateLockStates(sessionLockDeltas, sessionLockStates);
+          }
         }
       }
       finally
@@ -699,28 +689,27 @@
       }
     }
 
-    if (!locksOnNewObjects.isEmpty())
+    if (!newObjectLockDeltas.isEmpty())
     {
-      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(Operation.UNLOCK, lockType, getTimeStamp(), locksOnNewObjects);
+      CDOLockChangeInfo lockChangeInfo = makeLockChangeInfo(getTimeStamp(), newObjectLockDeltas, newObjectLockStates);
       fireLocksChangedEvent(this, lockChangeInfo);
     }
 
-    if (newLockStates != null)
+    if (!ObjectUtil.isEmpty(sessionLockDeltas))
     {
-      notifyLockChanges(Operation.UNLOCK, lockType, timeStamp, newLockStates);
+      notifyLockChanges(timeStamp, sessionLockDeltas, sessionLockStates);
     }
   }
 
-  protected InternalCDOLockState createUpdatedLockStateForNewObject(CDOObject object, LockType lockType, boolean on)
+  protected boolean updateLockStateOfNewObject(CDOObject object, LockType lockType, boolean on, List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
     // We have no new objects, CDOTransactionImpl overrides.
-    return null;
+    return false;
   }
 
-  protected Collection<CDOLockState> createUnlockedLockStatesForAllNewObjects()
+  protected void unlockAllNewObjects(List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
   {
     // We have no new objects, CDOTransactionImpl overrides.
-    return null;
   }
 
   /**
@@ -792,13 +781,6 @@
   }
 
   @Override
-  @Deprecated
-  public String enableDurableLocking(boolean enable)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
   public String enableDurableLocking()
   {
     String oldID = null;
@@ -892,50 +874,6 @@
    * @since 2.0
    */
   @Override
-  @Deprecated
-  public CDOFeatureAnalyzer getFeatureAnalyzer()
-  {
-    synchronized (getViewMonitor())
-    {
-      lockView();
-
-      try
-      {
-        return options().getFeatureAnalyzer();
-      }
-      finally
-      {
-        unlockView();
-      }
-    }
-  }
-
-  /**
-   * @since 2.0
-   */
-  @Override
-  @Deprecated
-  public void setFeatureAnalyzer(CDOFeatureAnalyzer featureAnalyzer)
-  {
-    synchronized (getViewMonitor())
-    {
-      lockView();
-
-      try
-      {
-        options.setFeatureAnalyzer(featureAnalyzer);
-      }
-      finally
-      {
-        unlockView();
-      }
-    }
-  }
-
-  /**
-   * @since 2.0
-   */
-  @Override
   public InternalCDOTransaction toTransaction()
   {
     checkActive();
@@ -1001,11 +939,11 @@
         if (!ids.isEmpty())
         {
           CDOSessionProtocol sessionProtocol = session.getSessionProtocol();
-          CDOLockState[] loadedLockStates = sessionProtocol.getLockStates(viewID, ids, CDOLockState.DEPTH_NONE);
+          List<CDOLockState> loadedLockStates = sessionProtocol.getLockStates2(viewID, ids, CDOLockState.DEPTH_NONE);
 
           if (!ObjectUtil.isEmpty(loadedLockStates))
           {
-            lockStateCache.addLockStates(branch, Arrays.asList(loadedLockStates), null);
+            lockStateCache.addLockStates(branch, loadedLockStates, null);
           }
         }
       }
@@ -1118,22 +1056,6 @@
     revisionManager.getRevision(id, this, initialChunkSize, depth, true);
   }
 
-  @Override
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async)
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache)
-  {
-    throw new UnsupportedOperationException();
-  }
-
   /*
    * Must not by synchronized on the view!
    */
@@ -1241,7 +1163,9 @@
         CDOLockChangeInfo lockChangeInfo = invalidationData.getLockChangeInfo();
         if (lockChangeInfo != null)
         {
-          updateLockStates(lockChangeInfo.getNewLockStates(), false, null);
+          List<CDOLockDelta> lockDeltas = Arrays.asList(lockChangeInfo.getLockDeltas());
+          List<CDOLockState> lockStates = Arrays.asList(lockChangeInfo.getLockStates());
+          updateLockStates(lockDeltas, lockStates);
           fireLocksChangedEvent(null, lockChangeInfo);
         }
       }
@@ -1266,13 +1190,6 @@
   }
 
   @Override
-  @Deprecated
-  public boolean isInvalidationRunnerActive()
-  {
-    return isInvalidating();
-  }
-
-  @Override
   public boolean isInvalidating()
   {
     return invalidating;
@@ -1591,10 +1508,13 @@
   {
     super.doActivate();
 
+    lockOwner = CDOLockUtil.createLockOwner(this);
     CDOSessionProtocol sessionProtocol = session.getSessionProtocol();
+
     if (durableLockingID != null)
     {
-      CDOBranchPoint branchPoint = sessionProtocol.openView(viewID, isReadOnly(), durableLockingID);
+      durableLockProcessor = createDurableLockProcessor();
+      CDOBranchPoint branchPoint = sessionProtocol.openView(viewID, isReadOnly(), durableLockingID, durableLockProcessor);
       basicSetBranchPoint(branchPoint);
     }
     else
@@ -1610,8 +1530,6 @@
       runnable.run();
     }
 
-    lockOwner = CDOLockUtil.createLockOwner(this);
-
     if (viewLock != null && Boolean.getBoolean("org.eclipse.emf.cdo.sync.tester"))
     {
       new SyncTester().start();
@@ -1625,6 +1543,12 @@
   {
     super.doAfterActivate();
 
+    if (durableLockProcessor != null)
+    {
+      durableLockProcessor.run();
+      durableLockProcessor = null;
+    }
+
     ExecutorService executorService = getExecutorService();
     invalidator.setDelegate(executorService);
 
@@ -1694,6 +1618,9 @@
     }
     finally
     {
+      List<CDOLockDelta> lockDeltas = null;
+      List<CDOLockState> lockStates = new ArrayList<>();
+
       synchronized (getViewMonitor())
       {
         lockView();
@@ -1702,23 +1629,21 @@
         {
           if (session.isActive())
           {
-            List<CDOLockState> result = new ArrayList<>();
-
-            CDOLockStateCache lockStateCache = session.getLockStateCache();
             CDOBranch branch = getBranch();
-            lockStateCache.removeOwner(branch, lockOwner, result::add);
-
-            if (!result.isEmpty())
-            {
-              long timeStamp = session.getLastUpdateTime();
-              notifyLockChanges(Operation.UNLOCK, null, timeStamp, result);
-            }
+            CDOLockStateCache lockStateCache = session.getLockStateCache();
+            lockDeltas = lockStateCache.removeOwner(branch, lockOwner, lockStates::add);
           }
         }
         finally
         {
           unlockView();
         }
+
+        if (!ObjectUtil.isEmpty(lockDeltas))
+        {
+          long timeStamp = session.getLastUpdateTime();
+          notifyLockChanges(timeStamp, lockDeltas, lockStates);
+        }
       }
     }
 
@@ -1967,6 +1892,68 @@
     return session.getLockStateCache().createKey(getBranch(), id);
   }
 
+  public final DurableLockProcessor createDurableLockProcessor()
+  {
+    return new DurableLockProcessor();
+  }
+
+  @Override
+  @Deprecated
+  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public String enableDurableLocking(boolean enable)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @since 2.0
+   */
+  @Override
+  @Deprecated
+  public CDOFeatureAnalyzer getFeatureAnalyzer()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * @since 2.0
+   */
+  @Override
+  @Deprecated
+  public void setFeatureAnalyzer(CDOFeatureAnalyzer featureAnalyzer)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public boolean isInvalidationRunnerActive()
+  {
+    throw new UnsupportedOperationException();
+  }
+
   public static Object getLockTarget(CDOView view, CDOID id)
   {
     return ((CDOViewImpl)view).getLockTarget(id);
@@ -2889,7 +2876,7 @@
    * @author Caspar De Groot
    * @since 4.1
    */
-  private final class ViewLocksChangedEvent extends DefaultLocksChangedEvent implements CDOViewLocksChangedEvent
+  private final class ViewLocksChangedEvent extends AbstractLocksChangedEvent implements CDOViewLocksChangedEvent
   {
     private static final long serialVersionUID = 1L;
 
@@ -2910,9 +2897,9 @@
       List<EObject> objects = new ArrayList<>();
       CDOView view = getSource();
 
-      for (CDOLockState lockState : getNewLockStates())
+      for (CDOLockDelta delta : getLockDeltas())
       {
-        Object lockedObject = lockState.getLockedObject();
+        Object lockedObject = delta.getTarget();
 
         CDOID id = null;
         if (lockedObject instanceof CDOIDAndBranch)
@@ -2942,6 +2929,12 @@
     }
 
     @Override
+    protected InternalCDOSession getSession()
+    {
+      return getSource().getSession();
+    }
+
+    @Override
     protected String formatEventName()
     {
       return "CDOViewLocksChangedEvent";
@@ -3104,6 +3097,54 @@
 
   /**
    * @author Eike Stepper
+   */
+  public final class DurableLockProcessor implements BiConsumer<CDOID, LockGrade>, Runnable
+  {
+    private final Map<CDOID, LockGrade> lockGrades = CDOIDUtil.createMap();
+
+    private DurableLockProcessor()
+    {
+    }
+
+    @Override
+    public void accept(CDOID id, LockGrade lockGrade)
+    {
+      lockGrades.put(id, lockGrade);
+    }
+
+    @Override
+    public void run()
+    {
+      List<CDOLockDelta> lockDeltas = new ArrayList<>();
+      List<CDOLockState> lockStates = new ArrayList<>();
+
+      for (Map.Entry<CDOID, LockGrade> entry : lockGrades.entrySet())
+      {
+        CDOID id = entry.getKey();
+        LockGrade lockGrade = entry.getValue();
+
+        Object target = getLockTarget(id);
+        InternalCDOLockState lockState = (InternalCDOLockState)CDOLockUtil.createLockState(target);
+
+        lockGrade.forEachLockType(lockType -> {
+          lockDeltas.add(CDOLockUtil.createLockDelta(target, lockType, null, lockOwner));
+          lockState.addOwner(lockOwner, lockType);
+        });
+
+        lockStates.add(lockState);
+      }
+
+      if (!lockDeltas.isEmpty())
+      {
+        CDOBranch branch = getBranch();
+        CDOLockStateCache lockStateCache = session.getLockStateCache();
+        lockStateCache.updateLockStates(branch, lockDeltas, lockStates, null);
+      }
+    }
+  }
+
+  /**
+   * @author Eike Stepper
    * @since 2.0
    */
   public class OptionsImpl extends Notifier implements Options
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOLockStateCache.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOLockStateCache.java
index b7d88e1..1ab629a 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOLockStateCache.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOLockStateCache.java
@@ -12,12 +12,14 @@
 
 import org.eclipse.emf.cdo.common.branch.CDOBranch;
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 
 import org.eclipse.net4j.util.lifecycle.ILifecycle;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -40,11 +42,36 @@
 
   public void addLockStates(CDOBranch branch, Collection<? extends CDOLockState> newLockStates, Consumer<CDOLockState> consumer);
 
-  public void removeOwner(CDOBranch branch, CDOLockOwner owner, Consumer<CDOLockState> changeConsumer);
-
-  public void remapOwner(CDOBranch branch, CDOLockOwner oldOwner, CDOLockOwner newOwner);
+  public void updateLockStates(CDOBranch branch, Collection<CDOLockDelta> lockDeltas, Collection<CDOLockState> lockStates, Consumer<CDOLockState> consumer);
 
   public void removeLockStates(CDOBranch branch, Collection<CDOID> ids, Consumer<CDOLockState> consumer);
 
   public void removeLockStates(CDOBranch branch);
+
+  public List<CDOLockDelta> removeOwner(CDOBranch branch, CDOLockOwner owner, Consumer<CDOLockState> consumer);
+
+  public void remapOwner(CDOBranch branch, CDOLockOwner oldOwner, CDOLockOwner newOwner);
+
+  /**
+   * @author Eike Stepper
+   */
+  public static final class ObjectAlreadyLockedException extends IllegalStateException
+  {
+    private static final long serialVersionUID = 1L;
+
+    public ObjectAlreadyLockedException(String message, Throwable cause)
+    {
+      super(message, cause);
+    }
+
+    public ObjectAlreadyLockedException(String message)
+    {
+      super(message);
+    }
+
+    public ObjectAlreadyLockedException(CDOID id, CDOBranch branch, Throwable cause)
+    {
+      super(id + " on " + branch.getPathName() + " branch: " + cause.getMessage(), cause);
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
index c180bed..bb86ca8 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
@@ -27,7 +27,9 @@
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.lob.CDOLob;
 import org.eclipse.emf.cdo.common.lob.CDOLobInfo;
+import org.eclipse.emf.cdo.common.lock.CDOLockDelta;
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
+import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.protocol.CDODataInput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocol;
@@ -52,7 +54,6 @@
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager.RevisionLoader3;
 import org.eclipse.emf.cdo.view.CDOView;
 
-import org.eclipse.net4j.util.CheckUtil;
 import org.eclipse.net4j.util.collection.UnionSet;
 import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
@@ -78,6 +79,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 
 /**
  * If the meaning of this type isn't clear, there really should be more of a description here...
@@ -135,9 +137,9 @@
   public void openView(int viewID, boolean readOnly, CDOBranchPoint branchPoint);
 
   /**
-   * @since 4.0
+   * @since 4.15
    */
-  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID);
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID, BiConsumer<CDOID, LockGrade> consumer);
 
   /**
    * @since 4.0
@@ -157,14 +159,6 @@
   public boolean cancelQuery(int queryId);
 
   /**
-   * @since 4.0
-   * @deprecated Not called anymore. Use {@link #lockObjects2(List, int, CDOBranch, LockType, boolean, long)} instead.
-   */
-  @Deprecated
-  public LockObjectsResult lockObjects(List<InternalCDORevision> viewedRevisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
-      throws InterruptedException;
-
-  /**
    * @since 4.1
    */
   public LockObjectsResult lockObjects2(List<CDORevisionKey> revisionKeys, int viewID, CDOBranch viewedBranch, LockType lockType, boolean recursive,
@@ -177,13 +171,6 @@
       boolean recursive, long timeout) throws InterruptedException;
 
   /**
-   * @since 3.0
-   * @deprecated Not called anymore. Use {@link #unlockObjects2(CDOView, Collection, LockType, boolean)} instead.
-   */
-  @Deprecated
-  public void unlockObjects(CDOView view, Collection<CDOID> objectIDs, LockType lockType);
-
-  /**
    * @since 4.1
    */
   public UnlockObjectsResult unlockObjects2(CDOView view, Collection<CDOID> objectIDs, LockType lockType, boolean recursive);
@@ -214,27 +201,11 @@
   public void loadLob(CDOLobInfo info, Object outputStreamOrWriter) throws IOException;
 
   /**
-   * @since 4.0
-   * @deprecated Not called anymore. Use {@link #commitTransaction(InternalCDOCommitContext, OMMonitor)} instead.
-   */
-  @Deprecated
-  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
-      Collection<CDOLob<?>> lobs, OMMonitor monitor);
-
-  /**
    * @since 4.1
    */
   public CommitTransactionResult commitTransaction(InternalCDOCommitContext context, OMMonitor monitor);
 
   /**
-   * @since 4.0
-   * @deprecated Not called anymore. Use {@link #commitDelegation(InternalCDOCommitContext, OMMonitor)} instead.
-   */
-  @Deprecated
-  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
-      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor);
-
-  /**
    * @since 4.1
    */
   public CommitTransactionResult commitDelegation(InternalCDOCommitContext context, OMMonitor monitor);
@@ -292,30 +263,15 @@
   public CDOChangeSetData[] loadChangeSets(CDOBranchPointRange... ranges);
 
   /**
-   * @since 4.0
-   * @deprecated As of 4.6 use {@link #loadMergeData2(CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo, CDORevisionAvailabilityInfo)}.
-   */
-  @Deprecated
-  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
-      CDORevisionAvailabilityInfo sourceBaseInfo);
-
-  /**
    * @since 4.6
    */
   public MergeDataResult loadMergeData2(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo,
       CDORevisionAvailabilityInfo targetBaseInfo, CDORevisionAvailabilityInfo sourceBaseInfo);
 
   /**
-   * @since 4.1
-   * @deprecated Not called anymore. Use {@link #getLockStates(int, Collection, int)} instead.
+   * @since 4.15
    */
-  @Deprecated
-  public CDOLockState[] getLockStates(int viewID, Collection<CDOID> ids);
-
-  /**
-   * @since 4.4
-   */
-  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth);
+  public List<CDOLockState> getLockStates2(int branchID, Collection<CDOID> ids, int depth);
 
   /**
    * @since 4.1
@@ -336,19 +292,6 @@
    * Requests that the server initiate the change-credentials protocol.
    * This is an optional session protocol operation.
    *
-   * @since 4.3
-   * @deprecated As of 4.13 use {@link #requestChangeServerPassword(AtomicReference)}.
-   *
-   * @throws UnsupportedOperationException if the session protocol implementation does
-   *         not support requesting change of credentials
-   */
-  @Deprecated
-  public void requestChangeCredentials();
-
-  /**
-   * Requests that the server initiate the change-credentials protocol.
-   * This is an optional session protocol operation.
-   *
    * @since 4.13
    *
    * @throws UnsupportedOperationException if the session protocol implementation does
@@ -1094,7 +1037,9 @@
 
     private CDOReferenceAdjuster referenceAdjuster;
 
-    private CDOLockState[] newLockStates;
+    private List<CDOLockDelta> lockDeltas;
+
+    private List<CDOLockState> lockStates;
 
     private boolean clearResourcePathCache;
 
@@ -1320,20 +1265,35 @@
     }
 
     /**
-     * @since 4.1
+     * @since 4.15
      */
-    public CDOLockState[] getNewLockStates()
+    public List<CDOLockDelta> getLockDeltas()
     {
-      return newLockStates;
+      return lockDeltas;
     }
 
     /**
-     * @since 4.1
+     * @since 4.15
      */
-    public void setNewLockStates(CDOLockState[] newLockStates)
+    public void setLockDeltas(List<CDOLockDelta> lockDeltas)
     {
-      CheckUtil.checkArg(newLockStates, "newLockStates");
-      this.newLockStates = newLockStates;
+      this.lockDeltas = lockDeltas;
+    }
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockState> getLockStates()
+    {
+      return lockStates;
+    }
+
+    /**
+     * @since 4.15
+     */
+    public void setLockStates(List<CDOLockState> lockStates)
+    {
+      this.lockStates = lockStates;
     }
 
     /**
@@ -1379,6 +1339,26 @@
     }
 
     /**
+     * @since 4.1
+     * @deprecated As of 4.15 use {@link #getLockDeltas()}.
+     */
+    @Deprecated
+    public CDOLockState[] getNewLockStates()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @since 4.1
+     * @deprecated As of 4.15 use {@link #setLockStates(List)}.
+     */
+    @Deprecated
+    public void setNewLockStates(CDOLockState[] newLockStates)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
      * If the meaning of this type isn't clear, there really should be more of a description here...
      *
      * @author Simon McDuff
@@ -1423,38 +1403,35 @@
    */
   public static final class LockObjectsResult
   {
-    private boolean successful;
+    private final boolean successful;
 
-    private boolean timedOut;
+    private final boolean timedOut;
 
-    private boolean waitForUpdate;
+    private final boolean waitForUpdate;
 
-    private long requiredTimestamp;
+    private final long requiredTimestamp;
 
-    private long timestamp;
+    private final long timestamp;
 
-    private CDORevisionKey[] staleRevisions;
+    private final CDORevisionKey[] staleRevisions;
 
-    private CDOLockState[] newLockStates;
+    private final List<CDOLockDelta> lockDeltas;
 
-    @Deprecated
-    public LockObjectsResult(boolean successful, boolean timedOut, boolean waitForUpdate, long requiredTimestamp, CDORevisionKey[] staleRevisions)
-    {
-      throw new AssertionError("Deprecated");
-    }
+    private final List<CDOLockState> lockStates;
 
     /**
-     * @since 4.1
+     * @since 4.15
      */
     public LockObjectsResult(boolean successful, boolean timedOut, boolean waitForUpdate, long requiredTimestamp, CDORevisionKey[] staleRevisions,
-        CDOLockState[] newLockStates, long timestamp)
+        List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates, long timestamp)
     {
       this.successful = successful;
       this.timedOut = timedOut;
       this.waitForUpdate = waitForUpdate;
       this.requiredTimestamp = requiredTimestamp;
       this.staleRevisions = staleRevisions;
-      this.newLockStates = newLockStates;
+      this.lockDeltas = lockDeltas;
+      this.lockStates = lockStates;
       this.timestamp = timestamp;
     }
 
@@ -1478,19 +1455,6 @@
       return requiredTimestamp;
     }
 
-    public CDORevisionKey[] getStaleRevisions()
-    {
-      return staleRevisions;
-    }
-
-    /**
-     * @since 4.1
-     */
-    public CDOLockState[] getNewLockStates()
-    {
-      return newLockStates;
-    }
-
     /**
      * @since 4.1
      */
@@ -1498,6 +1462,53 @@
     {
       return timestamp;
     }
+
+    public CDORevisionKey[] getStaleRevisions()
+    {
+      return staleRevisions;
+    }
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockDelta> getLockDeltas()
+    {
+      return lockDeltas;
+    }
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockState> getLockStates()
+    {
+      return lockStates;
+    }
+
+    /**
+     * @since 4.1
+     * @deprecated As of 4.15 use {@link #getLockStates()}.
+     */
+    @Deprecated
+    public CDOLockState[] getNewLockStates()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Deprecated
+    public LockObjectsResult(boolean successful, boolean timedOut, boolean waitForUpdate, long requiredTimestamp, CDORevisionKey[] staleRevisions)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @since 4.1
+     */
+    @Deprecated
+    public LockObjectsResult(boolean successful, boolean timedOut, boolean waitForUpdate, long requiredTimestamp, CDORevisionKey[] staleRevisions,
+        CDOLockState[] newLockStates, long timestamp)
+    {
+      throw new UnsupportedOperationException();
+    }
   }
 
   /**
@@ -1507,23 +1518,87 @@
    */
   public static final class UnlockObjectsResult
   {
-    private CDOLockState[] newLockStates;
+    private final long timestamp;
 
-    private long timestamp;
+    private final List<CDOLockDelta> lockDeltas;
 
-    public UnlockObjectsResult(CDOLockState[] newLockStates, long timestamp)
+    private final List<CDOLockState> lockStates;
+
+    /**
+     * @since 4.15
+     */
+    public UnlockObjectsResult(long timestamp, List<CDOLockDelta> lockDeltas, List<CDOLockState> lockStates)
     {
-      this.newLockStates = newLockStates;
-    }
-
-    public CDOLockState[] getNewLockStates()
-    {
-      return newLockStates;
+      this.timestamp = timestamp;
+      this.lockDeltas = lockDeltas;
+      this.lockStates = lockStates;
     }
 
     public long getTimestamp()
     {
       return timestamp;
     }
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockDelta> getLockDeltas()
+    {
+      return lockDeltas;
+    }
+
+    /**
+     * @since 4.15
+     */
+    public List<CDOLockState> getLockStates()
+    {
+      return lockStates;
+    }
+
+    @Deprecated
+    public UnlockObjectsResult(CDOLockState[] newLockStates, long timestamp)
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @deprecated As of 4.15 use {@link #getLockStates()}.
+     */
+    @Deprecated
+    public CDOLockState[] getNewLockStates()
+    {
+      throw new UnsupportedOperationException();
+    }
   }
+
+  @Deprecated
+  public LockObjectsResult lockObjects(List<InternalCDORevision> viewedRevisions, int viewID, CDOBranch viewedBranch, LockType lockType, long timeout)
+      throws InterruptedException;
+
+  @Deprecated
+  public void unlockObjects(CDOView view, Collection<CDOID> objectIDs, LockType lockType);
+
+  @Deprecated
+  public CommitTransactionResult commitTransaction(int transactionID, String comment, boolean releaseLocks, CDOIDProvider idProvider, CDOCommitData commitData,
+      Collection<CDOLob<?>> lobs, OMMonitor monitor);
+
+  @Deprecated
+  public CommitTransactionResult commitDelegation(CDOBranch branch, String userID, String comment, CDOCommitData commitData,
+      Map<CDOID, EClass> detachedObjectTypes, Collection<CDOLob<?>> lobs, OMMonitor monitor);
+
+  @Deprecated
+  public Set<CDOID> loadMergeData(CDORevisionAvailabilityInfo targetInfo, CDORevisionAvailabilityInfo sourceInfo, CDORevisionAvailabilityInfo targetBaseInfo,
+      CDORevisionAvailabilityInfo sourceBaseInfo);
+
+  @Deprecated
+  public CDOLockState[] getLockStates(int viewID, Collection<CDOID> ids);
+
+  @Deprecated
+  public CDOLockState[] getLockStates(int branchID, Collection<CDOID> ids, int depth);
+
+  @Deprecated
+  public CDOBranchPoint openView(int viewID, boolean readOnly, String durableLockingID);
+
+  @Deprecated
+  public void requestChangeCredentials();
 }
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOView.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOView.java
index 9451340..6d706cf 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOView.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOView.java
@@ -77,12 +77,6 @@
 
   public void setViewSet(InternalCDOViewSet viewSet);
 
-  @Deprecated
-  public CDOFeatureAnalyzer getFeatureAnalyzer();
-
-  @Deprecated
-  public void setFeatureAnalyzer(CDOFeatureAnalyzer featureAnalyzer);
-
   /**
    * Returns an unmodifiable map of the objects managed by this view.
    *
@@ -115,21 +109,6 @@
   public void handleObjectStateChanged(InternalCDOObject object, CDOState oldState, CDOState newState);
 
   /**
-   * @deprecated As of 4.2. use {@link #invalidate(CDOBranch, long, List, List, Map, boolean, boolean)}
-   */
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async);
-
-  /**
-   * @since 4.2
-   * @deprecated As of 4.6. use {@link #invalidate(ViewInvalidationData)}
-   */
-  @Deprecated
-  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
-      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache);
-
-  /**
    * @since 4.6
    */
   public void invalidate(ViewInvalidationData invalidationData);
@@ -153,12 +132,6 @@
 
   public CDOID getResourceNodeID(String path);
 
-  /**
-   * @deprecated No longer supported.
-   */
-  @Deprecated
-  public void registerProxyResource(CDOResourceImpl resource);
-
   public void registerObject(InternalCDOObject object);
 
   public void deregisterObject(InternalCDOObject object);
@@ -203,18 +176,6 @@
   public boolean isObjectLocked(CDOObject object, LockType lockType, boolean byOthers);
 
   /**
-   * @since 4.12
-   * @deprecated As of 4.15 use {@link #updateLockStates(Collection, boolean, Consumer)}.
-   */
-  @Deprecated
-  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer);
-
-  /**
-   * @since 4.15
-   */
-  public void updateLockStates(Collection<? extends CDOLockState> newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer);
-
-  /**
    * @since 4.15
    */
   public CDOLockState[] getLockStates(Collection<CDOID> ids, boolean loadOnDemand);
@@ -254,6 +215,40 @@
    */
   public void inverseClose();
 
+  @Deprecated
+  public CDOFeatureAnalyzer getFeatureAnalyzer();
+
+  @Deprecated
+  public void setFeatureAnalyzer(CDOFeatureAnalyzer featureAnalyzer);
+
+  /**
+   * @deprecated As of 4.2. use {@link #invalidate(CDOBranch, long, List, List, Map, boolean, boolean)}
+   */
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async);
+
+  /**
+   * @since 4.2
+   * @deprecated As of 4.6. use {@link #invalidate(ViewInvalidationData)}
+   */
+  @Deprecated
+  public void invalidate(CDOBranch branch, long lastUpdateTime, List<CDORevisionKey> allChangedObjects, List<CDOIDAndVersion> allDetachedObjects,
+      Map<CDOID, InternalCDORevision> oldRevisions, boolean async, boolean clearResourcePathCache);
+
+  /**
+   * @deprecated No longer supported.
+   */
+  @Deprecated
+  public void registerProxyResource(CDOResourceImpl resource);
+
+  /**
+   * @since 4.12
+   * @deprecated As of 4.15 use {@link CDOLockStateCache#updateLockStates(CDOBranch, Collection, Collection, Consumer)}.
+   */
+  @Deprecated
+  public void updateLockStates(CDOLockState[] newLockStates, boolean loadObjectsOnDemand, Consumer<CDOLockState> consumer);
+
   /**
    * Optimizes the storage of {@link CDOObject#cdoView()} and {@link CDOObject#cdoState()}. All objects of a view
    * share a small number of {@link CDOState} literals, so they are moved into a final AbstractCDOView.viewAndStates array.
diff --git a/plugins/org.eclipse.net4j.tests/.classpath b/plugins/org.eclipse.net4j.tests/.classpath
index 3e5654f..a42a828 100644
--- a/plugins/org.eclipse.net4j.tests/.classpath
+++ b/plugins/org.eclipse.net4j.tests/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src">
 		<attributes>
diff --git a/plugins/org.eclipse.net4j.tests/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.net4j.tests/.settings/org.eclipse.jdt.core.prefs
index d01a602..dbac0b5 100644
--- a/plugins/org.eclipse.net4j.tests/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/org.eclipse.net4j.tests/.settings/org.eclipse.jdt.core.prefs
@@ -15,9 +15,9 @@
 org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=11
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -32,6 +32,7 @@
 org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
 org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
 org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
@@ -86,6 +87,7 @@
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
 org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
 org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
 org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
 org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
 org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
@@ -115,7 +117,8 @@
 org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
 org.eclipse.jdt.core.compiler.taskCaseSensitive=enabled
 org.eclipse.jdt.core.compiler.taskPriorities=NORMAL,HIGH,HIGH,LOW,LOW,LOW,LOW,LOW
 org.eclipse.jdt.core.compiler.taskTags=TODO,FIXME,XXX,PERF,MEM,POLISH,@generated NOT,@ADDED
diff --git a/plugins/org.eclipse.net4j.tests/META-INF/MANIFEST.MF b/plugins/org.eclipse.net4j.tests/META-INF/MANIFEST.MF
index fa19b34..503b440 100644
--- a/plugins/org.eclipse.net4j.tests/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.net4j.tests/META-INF/MANIFEST.MF
@@ -7,7 +7,7 @@
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.net4j.tests.bundle.OM$Activator
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-RequiredExecutionEnvironment: JavaSE-11
 Bundle-ClassPath: .
 Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)";visibility:=reexport,
  org.eclipse.net4j.ws;bundle-version="[1.0.0,2.0.0)";visibility:=reexport,
@@ -26,9 +26,13 @@
  org.eclipse.jetty.websocket.servlet;bundle-version="[10.0.0,11.0.0)",
  org.eclipse.jetty.websocket.server;bundle-version="[10.0.0,11.0.0)",
  org.eclipse.jetty.websocket.client;bundle-version="[10.0.0,11.0.0)",
- org.eclipse.jetty.websocket.api;bundle-version="[10.0.0,11.0.0)"
+ org.eclipse.jetty.websocket.api;bundle-version="[10.0.0,11.0.0)",
+ org.eclipse.jetty.websocket.core.client;bundle-version="[10.0.0,11.0.0)",
+ org.eclipse.jetty.websocket.core.common;bundle-version="[10.0.0,11.0.0)",
+ org.eclipse.jetty.websocket.core.server;bundle-version="[10.0.0,11.0.0)"
 Import-Package: javax.servlet;version="[2.3.0,5.0.0)",
- javax.servlet.http;version="[2.3.0,5.0.0)"
+ javax.servlet.http;version="[2.3.0,5.0.0)",
+ org.slf4j;version="[1.0.0,2.0.0)"
 Export-Package: org.eclipse.net4j.tests;version="4.2.6",
  org.eclipse.net4j.tests.apps;version="4.2.6",
  org.eclipse.net4j.tests.bugzilla;version="4.2.6",
diff --git a/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/tests/AllTests.java b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/tests/AllTests.java
index c552103..10379cb 100644
--- a/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/tests/AllTests.java
+++ b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/tests/AllTests.java
@@ -22,6 +22,7 @@
 import org.eclipse.net4j.util.tests.ExpectedIOTest;
 import org.eclipse.net4j.util.tests.ExtendedIOTest;
 import org.eclipse.net4j.util.tests.MultiMapTest;
+import org.eclipse.net4j.util.tests.RWOLockManagerTest;
 import org.eclipse.net4j.util.tests.RollingLogTest;
 import org.eclipse.net4j.util.tests.RoundRobinBlockingQueueTest;
 import org.eclipse.net4j.util.tests.SecurityTest;
@@ -60,6 +61,7 @@
     suite.addTestSuite(SecurityTest.class);
     suite.addTestSuite(ExecutorWorkSerializerTest.class);
     suite.addTestSuite(RoundRobinBlockingQueueTest.class);
+    suite.addTestSuite(RWOLockManagerTest.class);
     suite.addTestSuite(ExpectedIOTest.class);
     suite.addTestSuite(RollingLogTest.class);
     suite.addTestSuite(Bugzilla_262875_Test.class);
diff --git a/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/RWOLockManagerTest.java b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/RWOLockManagerTest.java
new file mode 100644
index 0000000..bfbb07d
--- /dev/null
+++ b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/RWOLockManagerTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2014, 2018 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.tests;
+
+import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
+import org.eclipse.net4j.util.concurrent.RWOLockManager;
+import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
+import org.eclipse.net4j.util.io.IOUtil;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Eike Stepper
+ */
+public class RWOLockManagerTest extends AbstractOMTest
+{
+  private static final int USERS = 10;
+
+  private static final int ALLOCATIONS = 10;
+
+  private static final int RETRIES = 5;
+
+  private static final Set<Object> EXCLUSIVE_RESOURCE = Collections.singleton(new Object()
+  {
+    @Override
+    public String toString()
+    {
+      return "EXCLUSIVE_RESOURCE";
+    }
+  });
+
+  public void testRWOLockManager() throws Exception
+  {
+    AtomicInteger resource = new AtomicInteger(-1);
+    RWOLockManager<Object, User> lockManager = new RWOLockManager<>();
+
+    User[] users = new User[USERS];
+    User[] allocators = new User[USERS * ALLOCATIONS];
+
+    CountDownLatch started = new CountDownLatch(1);
+    CountDownLatch finished = new CountDownLatch(USERS);
+
+    for (int userID = 0; userID < USERS; userID++)
+    {
+      users[userID] = new User(userID, started, finished, allocators, lockManager, resource);
+    }
+
+    for (int userID = 0; userID < USERS; userID++)
+    {
+      users[userID].start();
+    }
+
+    started.countDown();
+    await(finished);
+    IOUtil.OUT().println("FINISHED");
+
+    Exception exception = null;
+    for (int userID = 0; userID < USERS; userID++)
+    {
+      Exception ex = users[userID].getException();
+      if (ex != null)
+      {
+        exception = ex;
+        ex.printStackTrace();
+      }
+    }
+
+    if (exception != null)
+    {
+      throw exception;
+    }
+
+    IOUtil.OUT().println("SUCCESS");
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class User extends Thread
+  {
+    private final CountDownLatch started;
+
+    private final CountDownLatch finished;
+
+    private final User[] allocators;
+
+    private final RWOLockManager<Object, User> lockManager;
+
+    private final AtomicInteger resource;
+
+    private Exception exception;
+
+    public User(int userID, CountDownLatch started, CountDownLatch finished, User[] allocators, RWOLockManager<Object, User> lockManager,
+        AtomicInteger resource)
+    {
+      super("User-" + userID);
+      this.started = started;
+      this.finished = finished;
+      this.allocators = allocators;
+      this.lockManager = lockManager;
+      this.resource = resource;
+    }
+
+    public Exception getException()
+    {
+      return exception;
+    }
+
+    @Override
+    public void run()
+    {
+      await(started);
+
+      try
+      {
+        for (int allocation = 0; allocation < ALLOCATIONS; allocation++)
+        {
+          for (int retry = RETRIES; retry >= 0; --retry)
+          {
+            try
+            {
+              lockManager.lock(this, EXCLUSIVE_RESOURCE, LockType.WRITE, 1, 10000000, null, null);
+              break;
+            }
+            catch (TimeoutRuntimeException ex)
+            {
+              if (retry == 0)
+              {
+                exception = ex;
+                return;
+              }
+
+              msg("Lock timed out. Trying again...");
+            }
+            catch (InterruptedException ex)
+            {
+              exception = ex;
+              return;
+            }
+          }
+
+          try
+          {
+            int id = resource.get() + 1;
+            if (allocators[id] != null)
+            {
+              throw new IllegalStateException(id + " already allocated by " + allocators[id]);
+            }
+
+            allocators[id] = this;
+            resource.set(id);
+            msg("ALLOCATED " + id);
+          }
+          catch (Exception ex)
+          {
+            exception = ex;
+            return;
+          }
+          finally
+          {
+            lockManager.unlock(this, EXCLUSIVE_RESOURCE, LockType.WRITE, 1, null, null);
+          }
+        }
+      }
+      finally
+      {
+        finished.countDown();
+      }
+    }
+
+    @Override
+    public String toString()
+    {
+      return getName();
+    }
+
+    private void msg(String string)
+    {
+      IOUtil.OUT().println(getName() + ": " + string);
+    }
+  }
+}
diff --git a/plugins/org.eclipse.net4j.util/.settings/.api_filters b/plugins/org.eclipse.net4j.util/.settings/.api_filters
index b34473e..8d9b14c 100644
--- a/plugins/org.eclipse.net4j.util/.settings/.api_filters
+++ b/plugins/org.eclipse.net4j.util/.settings/.api_filters
@@ -89,7 +89,25 @@
         <filter id="336658481">
             <message_arguments>
                 <message_argument value="org.eclipse.net4j.util.concurrent.RWOLockManager"/>
-                <message_argument value="ALL_LOCK_TYPES"/>
+                <message_argument value="read"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.net4j.util.concurrent.RWOLockManager"/>
+                <message_argument value="rwAccess"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.net4j.util.concurrent.RWOLockManager"/>
+                <message_argument value="write"/>
+            </message_arguments>
+        </filter>
+        <filter id="576725006">
+            <message_arguments>
+                <message_argument value="IRWOLockManager&lt;OBJECT, CONTEXT&gt;"/>
+                <message_argument value="RWOLockManager&lt;OBJECT, CONTEXT&gt;"/>
             </message_arguments>
         </filter>
     </resource>
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/CollectionUtil.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/CollectionUtil.java
index b918849..b66aad3 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/CollectionUtil.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/CollectionUtil.java
@@ -74,6 +74,15 @@
   /**
    * @since 3.16
    */
+  public static <T> T first(Collection<? extends T> c)
+  {
+    Iterator<? extends T> it = c.iterator();
+    return it.hasNext() ? it.next() : null;
+  }
+
+  /**
+   * @since 3.16
+   */
   public static <K, V> List<K> removeAll(Map<K, V> map, BiPredicate<K, V> predicate)
   {
     List<K> keys = new ArrayList<>();
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Access.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Access.java
new file mode 100644
index 0000000..1b06c6e
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Access.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.concurrent;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * @author Eike Stepper
+ * @since 3.16
+ */
+public final class Access implements AutoCloseable
+{
+  private final Lock lock;
+
+  public Access(Lock lock)
+  {
+    this.lock = lock;
+  }
+
+  public Lock getLock()
+  {
+    return lock;
+  }
+
+  public Condition newCondition()
+  {
+    return lock.newCondition();
+  }
+
+  public Access access()
+  {
+    lock.lock();
+    return this;
+  }
+
+  @Override
+  public void close()
+  {
+    lock.unlock();
+  }
+}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Holder.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Holder.java
new file mode 100644
index 0000000..0e54179
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/Holder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.concurrent;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * @author Eike Stepper
+ * @since 3.16
+ */
+public final class Holder<T> implements Consumer<T>, Supplier<T>
+{
+  private T element;
+
+  public Holder(T element)
+  {
+    set(element);
+  }
+
+  public Holder()
+  {
+  }
+
+  @Override
+  public T get()
+  {
+    return element;
+  }
+
+  public T set(T element)
+  {
+    T oldElement = this.element;
+    this.element = element;
+    return oldElement;
+  }
+
+  @Override
+  public void accept(T element)
+  {
+    set(element);
+  }
+}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java
index c7cd4af..4e8a500 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java
@@ -22,8 +22,10 @@
  */
 public interface IRWLockManager<OBJECT, CONTEXT>
 {
+  @Deprecated
   public static final int WAIT = 0;
 
+  @Deprecated
   public static final int NO_WAIT = 1;
 
   public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException;
@@ -31,7 +33,7 @@
   public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException;
 
   /**
-   * Attempts to release for a given locktype, context and objects.
+   * Attempts to release for a given lock type, context and objects.
    *
    * @throws IllegalMonitorStateException
    *           Unlocking objects without lock.
@@ -57,6 +59,6 @@
     /**
      * @since 3.2
      */
-    OPTION
+    OPTION;
   }
 }
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWOLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWOLockManager.java
index 63ae0e8..f1bc175 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWOLockManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWOLockManager.java
@@ -14,6 +14,7 @@
 
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * A {@link IRWLockManager read/write lock manager} that supports {@link IRWLockManager.LockType#OPTION write option}
@@ -21,27 +22,110 @@
  *
  * @author Caspar De Groot
  * @since 3.2
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
  */
 public interface IRWOLockManager<OBJECT, CONTEXT> extends IRWLockManager<OBJECT, CONTEXT>
 {
   /**
-   * Adds locks of the given type, owned by the given context on the given objects.
+   * @since 3.16
    */
-  public List<LockState<OBJECT, CONTEXT>> lock2(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
+  public static final Collection<?> ALL_OBJECTS = null;
+
+  /**
+   * @since 3.16
+   */
+  public static final LockType ALL_LOCK_TYPES = null;
+
+  /**
+   * @since 3.16
+   */
+  public static final int ALL_LOCKS = -1;
+
+  /**
+   * @since 3.16
+   */
+  public static final long NO_TIMEOUT = -1;
+
+  /**
+   * @since 3.16
+   */
+  public long getModCount();
+
+  /**
+   * Adds locks of the given lockType, owned by the given context to the given objects.
+   *
+   * @param context The lock context to add from the <code>objects</code>. Must not be <code>null</code>.
+   * @param objects The objects to lock. Must not be <code>null</code>.
+   * @param lockType The type of lock to add to the <code>objects</code>. Must not be <code>null</code>.
+   * @param count The number of locks to add to each of the <code>objects</code>.
+   * @param timeout The period in milliseconds after that a {@link TimeoutRuntimeException} is thrown if some or all of the
+   *        <code>objects</code> could not be locked,  or {@link #NO_TIMEOUT} to attempt forever to acquire the requested locks.
+   * @param deltaHandler A handler that is notified with each delta in a {@link LockState lock state}, or <code>null</code> if no such notification is needed.
+   *        The handler is notified at most once per delta, but it can happen that the handler is notified before the lock operation finally fails
+   *        with one of the specified exceptions. The notification handling should be fast because notifications occur while the calling thread is synchronized on this lock manager.
+   * @param stateHandler A handler that is notified with each new {@link LockState lock state}, or <code>null</code> if no such notification is needed..
+   *        The handler is notified at most once per lock state, but it can happen that the handler is notified before the lock operation finally fails
+   *        with one of the specified exceptions. The notification handling should be fast because notifications occur while the calling thread is synchronized on this lock manager.
+   * @return The new {@link #getModCount() modification count}.
+   * @throws InterruptedException If the calling thread is interrupted.
+   * @throws TimeoutRuntimeException If the timeout period has expired and some or all of the <code>objects</code> could not be locked.
+   * @since 3.16
+   */
+  public long lock(CONTEXT context, Collection<? extends OBJECT> objects, LockType lockType, int count, long timeout, //
+      LockDeltaHandler<OBJECT, CONTEXT> deltaHandler, Consumer<LockState<OBJECT, CONTEXT>> stateHandler) //
+      throws InterruptedException, TimeoutRuntimeException;
+
+  /**
+   * Removes locks of the given lockType, owned by the given context from the given objects.
+   *
+   * @param context The lock context to remove from the <code>objects</code>. Must not be <code>null</code>.
+   * @param objects The objects to unlock, or {@link #ALL_OBJECTS} to unlock all objects of the <code>context</code>.
+   * @param lockType The type of lock to remove from the <code>objects</code>, or {@link #ALL_LOCK_TYPES} to remove the locks of all types.
+   * @param count The number of locks to remove from each of the <code>objects</code>, or {@link #ALL_LOCKS} to remove all locks.
+   * @param deltaHandler A handler that is notified with each delta in a {@link LockState}, or <code>null</code> if no such notification is needed.
+   * @param stateHandler A handler that is notified with each new {@link LockState}, or <code>null</code> if no such notification is needed.
+   * @since 3.16
+   */
+  public long unlock(CONTEXT context, Collection<? extends OBJECT> objects, LockType lockType, int count, //
+      LockDeltaHandler<OBJECT, CONTEXT> deltaHandler, Consumer<LockState<OBJECT, CONTEXT>> stateHandler);
+
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> lock2(LockType lockType, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
       throws InterruptedException;
 
-  /**
-   * Removes all locks of the given type, owned by the given context on the given objects.
-   */
-  public List<LockState<OBJECT, CONTEXT>> unlock2(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock);
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> unlock2(LockType lockType, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock);
 
-  /**
-   * Removes all locks owned by the given context on any objects.
-   */
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context, Collection<? extends OBJECT> objectsToUnlock);
+
+  @Deprecated
   public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context);
 
+  @Override
+  @Deprecated
+  public void lock(LockType lockType, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException;
+
+  @Override
+  @Deprecated
+  public void lock(LockType lockType, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException;
+
+  @Override
+  @Deprecated
+  public void unlock(LockType lockType, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock);
+
+  @Override
+  @Deprecated
+  public void unlock(CONTEXT context);
+
   /**
-   * Removes all locks owned by the given context.
+   * @author Eike Stepper
+   * @since 3.16
    */
-  public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context, Collection<? extends OBJECT> objectsToUnlock);
+  @FunctionalInterface
+  public interface LockDeltaHandler<OBJECT, CONTEXT>
+  {
+    public void handleLockDelta(CONTEXT context, OBJECT object, LockType lockType, int oldCount, int newCount);
+  }
 }
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
index 7c57e26..ee49e90 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
@@ -7,15 +7,14 @@
  *
  * Contributors:
  *    Caspar De Groot - initial API and implementation
+ *    Eike Stepper - major reimplementation
  */
 package org.eclipse.net4j.util.concurrent;
 
-import org.eclipse.net4j.internal.util.bundle.OM;
 import org.eclipse.net4j.util.CheckUtil;
 import org.eclipse.net4j.util.ObjectUtil;
 import org.eclipse.net4j.util.collection.HashBag;
 import org.eclipse.net4j.util.lifecycle.Lifecycle;
-import org.eclipse.net4j.util.om.trace.ContextTracer;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -23,11 +22,14 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * Keeps track of locks on objects. Locks are owned by contexts. A particular combination of locks and their owners, for
@@ -35,17 +37,31 @@
  * deciding whether or not a new lock can be granted, based on the locks already present.
  *
  * @author Caspar De Groot
+ * @author Eike Stepper
  * @since 3.2
  */
 public class RWOLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWOLockManager<OBJECT, CONTEXT>
 {
-  private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_CONCURRENCY, RWOLockManager.class);
+  private static final LockType[][] LOCK_TYPE_ARRAYS = { { LockType.values()[0] }, { LockType.values()[1] }, { LockType.values()[2] } };
 
-  private static final ThreadLocal<Boolean> UNLOCK_ALL = new ThreadLocal<>();
+  private static final LockType[] ALL_LOCK_TYPES_ARRAY = LockType.values();
 
-  private static final LockType[] ALL_LOCK_TYPES = LockType.values();
+  /**
+   * @since 3.16
+   */
+  protected final ReentrantReadWriteAccess rwAccess = new ReentrantReadWriteAccess(true);
 
-  private final List<LockState<OBJECT, CONTEXT>> emptyResult = Collections.emptyList();
+  /**
+   * @since 3.16
+   */
+  protected final Access read = rwAccess.readAccess();
+
+  /**
+   * @since 3.16
+   */
+  protected final Access write = rwAccess.writeAccess();
+
+  private final Condition unlocked = rwAccess.newCondition();
 
   private final Map<OBJECT, LockState<OBJECT, CONTEXT>> objectToLockStateMap = createObjectToLocksMap();
 
@@ -58,228 +74,343 @@
    */
   private final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> contextToLockStates = createContextToLocksMap();
 
-  @Override
-  public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException
+  private volatile long modCount;
+
+  public RWOLockManager()
   {
-    lock2(type, context, objectsToLock, timeout);
   }
 
+  /**
+   * @category Read Access
+   */
   @Override
-  public List<LockState<OBJECT, CONTEXT>> lock2(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
-      throws InterruptedException
+  public long getModCount()
   {
-    int count = objectsToLock.size();
-    if (count == 0)
+    try (Access access = read.access())
     {
-      return emptyResult;
+      return modCount;
     }
+  }
 
-    if (TRACER.isEnabled())
+  /**
+   * @since 3.16
+   * @category Write Access
+   */
+  @Override
+  public long lock(CONTEXT context, Collection<? extends OBJECT> objects, LockType lockType, int count, long timeout, //
+      LockDeltaHandler<OBJECT, CONTEXT> deltaHandler, Consumer<LockState<OBJECT, CONTEXT>> stateHandler) //
+      throws InterruptedException, TimeoutRuntimeException
+  {
+    CheckUtil.checkArg(context, "context");
+    CheckUtil.checkArg(objects, "objects");
+    CheckUtil.checkArg(lockType, "lockType");
+    CheckUtil.checkArg(count >= 0, "count >= 0");
+
+    long deadline = timeout == NO_TIMEOUT ? Long.MAX_VALUE : currentTimeMillis() + timeout;
+
+    try (Access access = write.access())
     {
-      TRACER.format("Lock: {0} --> {1}", context, objectsToLock); //$NON-NLS-1$
-    }
+      if (ObjectUtil.isEmpty(objects) || count == 0)
+      {
+        // Nothing to do.
+        return modCount;
+      }
 
-    // Must come before the synchronized block!
-    long startTime = timeout == WAIT ? 0L : currentTimeMillis();
+      // Populate a mutable list of objects to lock. This list will shrink while objects are successfully locked below.
+      List<OBJECT> objectsToLock = new ArrayList<>(objects);
 
-    // Do not synchronize the entire method as it would corrupt the timeout!
-    synchronized (this)
-    {
+      // Remember the locked objects for the case that an exception occurs, so that their locks can be removed again.
+      List<OBJECT> lockedObjects = new ArrayList<>(objectsToLock.size());
+
       for (;;)
       {
-        ArrayList<LockState<OBJECT, CONTEXT>> lockStates = getLockStatesForContext(type, context, objectsToLock);
-        if (lockStates != null)
+        for (Iterator<OBJECT> it = objectsToLock.iterator(); it.hasNext();)
         {
-          for (int i = 0; i < count; i++)
+          OBJECT lockedObject = it.next();
+          LockState<OBJECT, CONTEXT> lockState = getOrCreateLockState(lockedObject);
+
+          if (lockState.canLock(lockType, context))
           {
-            LockState<OBJECT, CONTEXT> lockState = lockStates.get(i);
-            lockState.lock(type, context);
-            addContextToLockStateMapping(context, lockState);
-          }
+            int oldCount = lockState.getLockCount(lockType, context);
+            int newCount = lockState.lock(lockType, context, count);
 
-          return lockStates;
-        }
-
-        wait(startTime, timeout);
-      }
-    }
-  }
-
-  @Override
-  public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException
-  {
-    // Do not synchronize the entire method as it would corrupt the timeout!
-    lock(type, context, Collections.singleton(objectToLock), timeout);
-  }
-
-  @Override
-  public void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
-  {
-    unlock2(type, context, objectsToUnlock);
-  }
-
-  @Override
-  public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
-  {
-    return unlock2(ALL_LOCK_TYPES, context, objectsToUnlock);
-  }
-
-  @Override
-  public List<LockState<OBJECT, CONTEXT>> unlock2(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
-  {
-    return unlock2(new LockType[] { type }, context, objectsToUnlock);
-  }
-
-  private List<LockState<OBJECT, CONTEXT>> unlock2(LockType[] types, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
-  {
-    if (objectsToUnlock.isEmpty())
-    {
-      return emptyResult;
-    }
-
-    if (TRACER.isEnabled())
-    {
-      TRACER.format("Unlock: {0} --> {1}", context, objectsToUnlock); //$NON-NLS-1$
-    }
-
-    Set<LockState<OBJECT, CONTEXT>> result = new HashSet<>();
-    boolean unlockAll = UNLOCK_ALL.get() == Boolean.TRUE;
-
-    synchronized (this)
-    {
-      for (OBJECT o : objectsToUnlock)
-      {
-        LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
-        if (lockState != null)
-        {
-          for (LockType type : types)
-          {
-            while (lockState.canUnlock(type, context))
+            if (newCount != oldCount)
             {
-              lockState.unlock(type, context);
-              result.add(lockState);
+              addContextToLockStateMapping(context, lockState);
 
-              if (!unlockAll)
+              if (deltaHandler != null)
               {
-                break;
+                deltaHandler.handleLockDelta(context, lockedObject, lockType, oldCount, newCount);
               }
             }
+
+            if (stateHandler != null)
+            {
+              stateHandler.accept(lockState);
+            }
+
+            lockedObjects.add(lockedObject);
+            it.remove();
+          }
+        }
+
+        if (objectsToLock.isEmpty())
+        {
+          return ++modCount;
+        }
+
+        try
+        {
+          long waitTime = deadline - currentTimeMillis();
+          if (waitTime <= 0)
+          {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Could not lock objects within ");
+            builder.append(timeout);
+            builder.append(" milliseconds: ");
+
+            StringJoiner joiner = new StringJoiner(", ", "Could not lock objects within " + timeout + " milliseconds: ", "");
+            for (OBJECT objectToLock : objectsToLock)
+            {
+              LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
+              if (lockState != null)
+              {
+                joiner.add(lockState.toString());
+              }
+            }
+
+            throw new TimeoutRuntimeException(builder.toString());
+          }
+
+          // Give others a chance to unlock objects.
+          unlocked.await(waitTime, TimeUnit.MILLISECONDS);
+        }
+        catch (InterruptedException | TimeoutRuntimeException ex)
+        {
+          unlock(context, lockedObjects, lockType, count, null, null);
+
+          --modCount; // Fix the modCount that was increased by unlock();
+          throw ex;
+        }
+      }
+    }
+  }
+
+  private LockState<OBJECT, CONTEXT> getOrCreateLockState(OBJECT object)
+  {
+    return objectToLockStateMap.computeIfAbsent(object, o -> new LockState<>(o));
+  }
+
+  private void addContextToLockStateMapping(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
+  {
+    contextToLockStates.computeIfAbsent(context, c -> new HashSet<>()).add(lockState);
+  }
+
+  /**
+   * @category Write Access
+   */
+  @Override
+  public long unlock(CONTEXT context, Collection<? extends OBJECT> objects, LockType lockType, int count, //
+      LockDeltaHandler<OBJECT, CONTEXT> deltaHandler, Consumer<LockState<OBJECT, CONTEXT>> stateHandler)
+  {
+    CheckUtil.checkArg(context, "context");
+    CheckUtil.checkArg(count >= -1, "count >= -1");
+
+    LockType[] lockTypes = lockType == null ? ALL_LOCK_TYPES_ARRAY : LOCK_TYPE_ARRAYS[lockType.ordinal()];
+    List<LockState<OBJECT, CONTEXT>> modifiedLockStates = new ArrayList<>();
+
+    try (Access access = write.access())
+    {
+      if (objects == null)
+      {
+        if (count == 0)
+        {
+          // Nothing to do.
+          return modCount;
+        }
+
+        Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
+        if (lockStates == null)
+        {
+          // We have no locks, nothing to do.
+          return modCount;
+        }
+
+        for (LockState<OBJECT, CONTEXT> lockState : lockStates)
+        {
+          OBJECT object = lockState.getLockedObject();
+          doUnlock(context, object, lockTypes, count, deltaHandler, stateHandler, lockState, modifiedLockStates);
+        }
+      }
+      else
+      {
+        if (objects.isEmpty())
+        {
+          return modCount;
+        }
+
+        for (OBJECT object : objects)
+        {
+          LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(object);
+          if (lockState != null)
+          {
+            doUnlock(context, object, lockTypes, count, deltaHandler, stateHandler, lockState, modifiedLockStates);
           }
         }
       }
 
-      for (LockState<OBJECT, CONTEXT> lockState : result)
+      removeLockStates(context, modifiedLockStates);
+
+      // Wake up blocked lockers.
+      unlocked.signalAll();
+
+      return ++modCount;
+    }
+  }
+
+  private void doUnlock(CONTEXT context, OBJECT object, LockType[] lockTypes, int count, //
+      LockDeltaHandler<OBJECT, CONTEXT> deltaHandler, Consumer<LockState<OBJECT, CONTEXT>> stateHandler, //
+      LockState<OBJECT, CONTEXT> lockState, List<LockState<OBJECT, CONTEXT>> modifiedLockStates)
+  {
+    for (LockType lockType : lockTypes)
+    {
+      if (lockState.canUnlock(lockType, context))
       {
-        if (!lockState.hasLocks(context))
+        int oldCount = lockState.getLockCount(lockType, context);
+        int newCount = lockState.unlock(lockType, context, count);
+
+        if (newCount != oldCount)
         {
-          removeLockStateForContext(context, lockState);
+          modifiedLockStates.add(lockState);
+
+          if (deltaHandler != null)
+          {
+            deltaHandler.handleLockDelta(context, lockState.getLockedObject(), lockType, oldCount, newCount);
+          }
         }
 
-        if (lockState.hasNoLocks())
+        if (stateHandler != null)
         {
-          objectToLockStateMap.remove(lockState.getLockedObject());
+          stateHandler.accept(lockState);
         }
       }
-
-      notifyAll();
     }
-
-    return new LinkedList<>(result);
   }
 
-  @Override
-  public synchronized void unlock(CONTEXT context)
+  private void removeLockStates(CONTEXT context, List<LockState<OBJECT, CONTEXT>> modifiedLockStates)
   {
-    unlock2(context);
-  }
-
-  @Override
-  public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context)
-  {
-    Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
-    if (lockStates == null)
+    for (LockState<OBJECT, CONTEXT> lockState : modifiedLockStates)
     {
-      return emptyResult;
-    }
-
-    if (TRACER.isEnabled())
-    {
-      TRACER.format("Unlock: {0} --> {1}", context, lockStates); //$NON-NLS-1$
-    }
-
-    List<OBJECT> objectsWithoutLocks = new LinkedList<>();
-
-    for (LockState<OBJECT, CONTEXT> lockState : lockStates)
-    {
-      for (LockType type : ALL_LOCK_TYPES)
+      if (!lockState.hasLocks(context))
       {
-        while (lockState.hasLock(type, context, false))
-        {
-          lockState.unlock(type, context);
-        }
+        removeLockStateForContext(context, lockState);
       }
 
       if (lockState.hasNoLocks())
       {
-        OBJECT o = lockState.getLockedObject();
-        objectsWithoutLocks.add(o);
+        objectToLockStateMap.remove(lockState.getLockedObject());
       }
     }
-
-    contextToLockStates.remove(context);
-
-    // This must be done outside the above iteration, in order to avoid ConcurrentModEx
-    for (OBJECT o : objectsWithoutLocks)
-    {
-      objectToLockStateMap.remove(o);
-    }
-
-    notifyAll();
-
-    return toList(lockStates);
   }
 
-  @Override
-  public synchronized boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock)
+  /**
+   * Removes a lockState from the set of all lockStates that the given context is involved in. If the lockState being
+   * removed is the last one for the given context, then the set becomes empty, and is therefore removed from the
+   * contextToLockStates map.
+   */
+  private void removeLockStateForContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
   {
-    LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
-    return lockState != null && lockState.hasLock(type, context, false);
-  }
-
-  @Override
-  public synchronized boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock)
-  {
-    LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
-    return lockState != null && lockState.hasLock(type, context, true);
-  }
-
-  protected synchronized void changeContext(CONTEXT oldContext, CONTEXT newContext)
-  {
-    for (LockState<OBJECT, CONTEXT> lockState : objectToLockStateMap.values())
-    {
-      lockState.replaceContext(oldContext, newContext);
-    }
-
-    Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.remove(oldContext);
+    Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
     if (lockStates != null)
     {
-      contextToLockStates.put(newContext, lockStates);
+      lockStates.remove(lockState);
+
+      if (lockStates.isEmpty())
+      {
+        contextToLockStates.remove(context);
+      }
     }
   }
 
-  protected long currentTimeMillis()
+  /**
+   * @category Read Access
+   */
+  @Override
+  public boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock)
   {
-    return System.currentTimeMillis();
+    try (Access access = read.access())
+    {
+      LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
+      return lockState != null && lockState.hasLock(type, context, false);
+    }
   }
 
-  protected Map<OBJECT, LockState<OBJECT, CONTEXT>> createObjectToLocksMap()
+  /**
+   * @category Read Access
+   */
+  @Override
+  public boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock)
   {
-    return new HashMap<>();
+    try (Access access = read.access())
+    {
+      LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
+      return lockState != null && lockState.hasLock(type, context, true);
+    }
   }
 
-  protected Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> createContextToLocksMap()
+  /**
+   * @category Read Access
+   */
+  public LockState<OBJECT, CONTEXT> getLockState(OBJECT key)
   {
-    return new HashMap<>();
+    try (Access access = read.access())
+    {
+      return objectToLockStateMap.get(key);
+    }
+  }
+
+  /**
+   * @since 3.16
+   * @category Read Access
+   */
+  public void getLockStates(Collection<OBJECT> keys, BiConsumer<OBJECT, LockState<OBJECT, CONTEXT>> consumer)
+  {
+    try (Access access = read.access())
+    {
+      keys.forEach(key -> {
+        LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(key);
+        consumer.accept(key, lockState);
+      });
+    }
+  }
+
+  /**
+   * @since 3.16
+   * @category Read Access
+   */
+  public void getLockStates(Consumer<LockState<OBJECT, CONTEXT>> consumer)
+  {
+    try (Access access = read.access())
+    {
+      objectToLockStateMap.values().forEach(consumer);
+    }
+  }
+
+  protected void changeContext(CONTEXT oldContext, CONTEXT newContext)
+  {
+    try (Access access = write.access())
+    {
+      for (LockState<OBJECT, CONTEXT> lockState : objectToLockStateMap.values())
+      {
+        lockState.replaceContext(oldContext, newContext);
+      }
+
+      Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.remove(oldContext);
+      if (lockStates != null)
+      {
+        contextToLockStates.put(newContext, lockStates);
+      }
+    }
   }
 
   /**
@@ -298,162 +429,117 @@
     return contextToLockStates;
   }
 
-  public synchronized LockState<OBJECT, CONTEXT> getLockState(OBJECT key)
+  protected Map<OBJECT, LockState<OBJECT, CONTEXT>> createObjectToLocksMap()
   {
-    return objectToLockStateMap.get(key);
+    return new HashMap<>();
+  }
+
+  protected Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> createContextToLocksMap()
+  {
+    return new HashMap<>();
+  }
+
+  protected long currentTimeMillis()
+  {
+    return System.currentTimeMillis();
+  }
+
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> getLockStates()
+  {
+    throw new UnsupportedOperationException();
   }
 
   /**
-   * @since 3.16
+   * @category Write Access
    */
-  public synchronized void getLockStates(Collection<OBJECT> keys, BiConsumer<OBJECT, LockState<OBJECT, CONTEXT>> consumer)
+  @Deprecated
+  public void setLockState(OBJECT key, LockState<OBJECT, CONTEXT> lockState)
   {
-    keys.forEach(key -> {
-      LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(key);
-      consumer.accept(key, lockState);
-    });
-  }
-
-  /**
-   * @since 3.5
-   */
-  public synchronized List<LockState<OBJECT, CONTEXT>> getLockStates()
-  {
-    return new ArrayList<>(objectToLockStateMap.values());
-  }
-
-  public synchronized void setLockState(OBJECT key, LockState<OBJECT, CONTEXT> lockState)
-  {
-    objectToLockStateMap.put(key, lockState);
-
-    for (CONTEXT readLockOwner : lockState.getReadLockOwners())
+    try (Access access = write.access())
     {
-      addContextToLockStateMapping(readLockOwner, lockState);
-    }
+      objectToLockStateMap.put(key, lockState);
 
-    CONTEXT writeLockOwner = lockState.getWriteLockOwner();
-    if (writeLockOwner != null)
-    {
-      addContextToLockStateMapping(writeLockOwner, lockState);
-    }
-
-    CONTEXT writeOptionOwner = lockState.getWriteOptionOwner();
-    if (writeOptionOwner != null)
-    {
-      addContextToLockStateMapping(writeOptionOwner, lockState);
-    }
-  }
-
-  private LockState<OBJECT, CONTEXT> getOrCreateLockState(OBJECT o)
-  {
-    LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
-    if (lockState == null)
-    {
-      lockState = new LockState<>(o);
-      objectToLockStateMap.put(o, lockState);
-    }
-
-    return lockState;
-  }
-
-  private ArrayList<LockState<OBJECT, CONTEXT>> getLockStatesForContext(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock)
-  {
-    int count = objectsToLock.size();
-    ArrayList<LockState<OBJECT, CONTEXT>> lockStates = new ArrayList<>(count);
-
-    Iterator<? extends OBJECT> it = objectsToLock.iterator();
-
-    for (int i = 0; i < count; i++)
-    {
-      OBJECT o = it.next();
-      LockState<OBJECT, CONTEXT> lockState = getOrCreateLockState(o);
-      if (!lockState.canLock(type, context))
+      for (CONTEXT readLockOwner : lockState.getReadLockOwners())
       {
-        return null;
+        addContextToLockStateMapping(readLockOwner, lockState);
       }
 
-      lockStates.add(lockState);
-    }
-
-    return lockStates;
-  }
-
-  private void addContextToLockStateMapping(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
-  {
-    Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
-    if (lockStates == null)
-    {
-      lockStates = new HashSet<>();
-      contextToLockStates.put(context, lockStates);
-    }
-
-    lockStates.add(lockState);
-  }
-
-  /**
-   * Removes a lockState from the set of all lockStates that the given context is involved in. If the lockState being
-   * removed is the last one for the given context, then the set becomes empty, and is therefore removed from the
-   * contextToLockStates map.
-   */
-  private void removeLockStateForContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
-  {
-    Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
-    lockStates.remove(lockState);
-    if (lockStates.isEmpty())
-    {
-      contextToLockStates.remove(context);
-    }
-  }
-
-  private void wait(long startTime, long timeout) throws InterruptedException
-  {
-    if (timeout == WAIT)
-    {
-      wait();
-    }
-    else
-    {
-      long elapsedTime = currentTimeMillis() - startTime;
-      long waitTime = timeout - elapsedTime;
-      if (waitTime < 1)
+      CONTEXT writeLockOwner = lockState.getWriteLockOwner();
+      if (writeLockOwner != null)
       {
-        throw new TimeoutRuntimeException("Could not lock objects within " + timeout + " milli seconds");
+        addContextToLockStateMapping(writeLockOwner, lockState);
       }
 
-      wait(waitTime);
+      CONTEXT writeOptionOwner = lockState.getWriteOptionOwner();
+      if (writeOptionOwner != null)
+      {
+        addContextToLockStateMapping(writeOptionOwner, lockState);
+      }
     }
   }
 
-  @SuppressWarnings("unchecked")
-  private static <OBJECT, CONTEXT> List<LockState<OBJECT, CONTEXT>> toList(Set<LockState<OBJECT, CONTEXT>> lockStates)
+  @Override
+  @Deprecated
+  public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException
   {
-    if (lockStates instanceof List)
-    {
-      return (List<LockState<OBJECT, CONTEXT>>)lockStates;
-    }
-
-    List<LockState<OBJECT, CONTEXT>> list = new LinkedList<>();
-    for (LockState<OBJECT, CONTEXT> lockState : lockStates)
-    {
-      list.add(lockState);
-    }
-
-    return list;
+    throw new UnsupportedOperationException();
   }
 
-  /**
-   * @since 3.7
-   */
+  @Override
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> lock2(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
+      throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public void unlock(CONTEXT context)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  @Deprecated
+  public List<LockState<OBJECT, CONTEXT>> unlock2(LockType lockType, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Deprecated
   public static void setUnlockAll(boolean on)
   {
-    if (on)
-    {
-      UNLOCK_ALL.set(Boolean.TRUE);
-    }
-    else
-    {
-      UNLOCK_ALL.remove();
-    }
+    throw new UnsupportedOperationException();
   }
 
   /**
@@ -477,7 +563,7 @@
   {
     private final OBJECT lockedObject;
 
-    private final HashBag<CONTEXT> readLockOwners = new HashBag<>();
+    private HashBag<CONTEXT> readLockOwners;
 
     private CONTEXT writeLockOwner;
 
@@ -496,38 +582,67 @@
       return lockedObject;
     }
 
-    public boolean hasLock(LockType type, CONTEXT view, boolean byOthers)
+    /**
+     * @since 3.16
+     */
+    public int getLockCount(LockType type, CONTEXT context)
     {
-      CheckUtil.checkArg(view, "view");
+      CheckUtil.checkArg(context, "context");
 
       switch (type)
       {
       case READ:
-        if (byOthers)
+        return readLockOwners == null ? 0 : readLockOwners.getCounterFor(context);
+
+      case WRITE:
+        return writeLockOwner == context ? writeLockCounter : 0;
+
+      case OPTION:
+        return writeOptionOwner == context ? 1 : 0;
+
+      default:
+        throw new AssertionError();
+      }
+    }
+
+    public boolean hasLock(LockType type, CONTEXT context, boolean byOthers)
+    {
+      CheckUtil.checkArg(context, "context");
+
+      switch (type)
+      {
+      case READ:
+        if (readLockOwners == null)
         {
-          return readLockOwners.size() > 1 || readLockOwners.size() == 1 && !readLockOwners.contains(view);
+          return false;
         }
 
-        return readLockOwners.contains(view);
+        if (byOthers)
+        {
+          return readLockOwners.size() > 1 || readLockOwners.size() == 1 && !readLockOwners.contains(context);
+        }
+
+        return readLockOwners.contains(context);
 
       case WRITE:
         if (byOthers)
         {
-          return writeLockOwner != null && writeLockOwner != view;
+          return writeLockOwner != null && writeLockOwner != context;
         }
 
-        return writeLockOwner == view;
+        return writeLockOwner == context;
 
       case OPTION:
         if (byOthers)
         {
-          return writeOptionOwner != null && writeOptionOwner != view;
+          return writeOptionOwner != null && writeOptionOwner != context;
         }
 
-        return writeOptionOwner == view;
-      }
+        return writeOptionOwner == context;
 
-      return false;
+      default:
+        throw new AssertionError();
+      }
     }
 
     public boolean hasLock(LockType type)
@@ -535,20 +650,26 @@
       switch (type)
       {
       case READ:
-        return readLockOwners.size() > 0;
+        return readLockOwners != null && readLockOwners.size() > 0;
 
       case WRITE:
         return writeLockOwner != null;
 
       case OPTION:
         return writeOptionOwner != null;
-      }
 
-      return false;
+      default:
+        throw new AssertionError();
+      }
     }
 
     public Set<CONTEXT> getReadLockOwners()
     {
+      if (readLockOwners == null)
+      {
+        return Collections.emptySet();
+      }
+
       return Collections.unmodifiableSet(readLockOwners);
     }
 
@@ -596,11 +717,11 @@
       StringBuilder builder = new StringBuilder("LockState[target=");
       builder.append(lockedObject);
 
-      if (readLockOwners.size() > 0)
+      if (readLockOwners != null && readLockOwners.size() > 0)
       {
         builder.append(", read=");
         boolean first = true;
-        for (CONTEXT view : readLockOwners)
+        for (CONTEXT context : readLockOwners)
         {
           if (first)
           {
@@ -611,7 +732,7 @@
             builder.append(", ");
           }
 
-          builder.append(view);
+          builder.append(context);
         }
 
         builder.deleteCharAt(builder.length() - 1);
@@ -633,43 +754,23 @@
       return builder.toString();
     }
 
-    void lock(LockType type, CONTEXT context)
-    {
-      CheckUtil.checkArg(context, "context");
-      switch (type)
-      {
-      case READ:
-        doReadLock(context);
-        return;
-
-      case WRITE:
-        doWriteLock(context);
-        return;
-
-      case OPTION:
-        doWriteOption(context);
-        return;
-      }
-
-      throw new AssertionError("Unknown lock type " + type);
-    }
-
     boolean canLock(LockType type, CONTEXT context)
     {
       CheckUtil.checkArg(context, "context");
       switch (type)
       {
       case READ:
-        return canReadLock(context);
+        return canLockRead(context);
 
       case WRITE:
-        return canWriteLock(context);
+        return canLockWrite(context);
 
       case OPTION:
-        return canWriteOption(context);
-      }
+        return canLockOption(context);
 
-      throw new AssertionError("Unknown lock type " + type);
+      default:
+        throw new AssertionError();
+      }
     }
 
     boolean canUnlock(LockType type, CONTEXT context)
@@ -678,48 +779,69 @@
       switch (type)
       {
       case READ:
-        return canReadUnlock(context);
+        return canUnlockRead(context);
 
       case WRITE:
-        return canWriteUnlock(context);
+        return canUnlockWrite(context);
 
       case OPTION:
-        return canWriteUnoption(context);
-      }
+        return canUnlockOption(context);
 
-      throw new AssertionError("Unknown lock type " + type);
+      default:
+        throw new AssertionError();
+      }
     }
 
-    void unlock(LockType type, CONTEXT context)
+    int lock(LockType type, CONTEXT context, int count)
     {
       CheckUtil.checkArg(context, "context");
       switch (type)
       {
       case READ:
-        doReadUnlock(context);
-        return;
+        return doLockRead(context, count);
 
       case WRITE:
-        doWriteUnlock(context);
-        return;
+        return doLockWrite(context, count);
 
       case OPTION:
-        doWriteUnoption(context);
-        return;
-      }
+        return doLockOption(context, count);
 
-      throw new AssertionError("Unknown lock type " + type);
+      default:
+        throw new AssertionError();
+      }
+    }
+
+    int unlock(LockType type, CONTEXT context, int count)
+    {
+      CheckUtil.checkArg(context, "context");
+      switch (type)
+      {
+      case READ:
+        return doUnlockRead(context, count);
+
+      case WRITE:
+        return doUnlockWrite(context, count);
+
+      case OPTION:
+        return doUnlockOption(context, count);
+
+      default:
+        throw new AssertionError();
+      }
     }
 
     void replaceContext(CONTEXT oldContext, CONTEXT newContext)
     {
-      int readLocksOwnedByOldView = readLockOwners.getCounterFor(oldContext);
-      if (readLocksOwnedByOldView > 0)
+      if (readLockOwners != null)
       {
-        for (int i = 0; i < readLocksOwnedByOldView; i++)
+        int readLocksOwnedByOldView = readLockOwners.getCounterFor(oldContext);
+        if (readLocksOwnedByOldView > 0)
         {
-          readLockOwners.remove(oldContext);
-          readLockOwners.add(newContext);
+          for (int i = 0; i < readLocksOwnedByOldView; i++)
+          {
+            readLockOwners.remove(oldContext);
+            readLockOwners.add(newContext);
+          }
         }
       }
 
@@ -736,15 +858,15 @@
 
     boolean hasNoLocks()
     {
-      return readLockOwners.isEmpty() && writeLockOwner == null && writeOptionOwner == null;
+      return ObjectUtil.isEmpty(readLockOwners) && writeLockOwner == null && writeOptionOwner == null;
     }
 
     boolean hasLocks(CONTEXT context)
     {
-      return readLockOwners.contains(context) || writeLockOwner == context || writeOptionOwner == context;
+      return readLockOwners != null && readLockOwners.contains(context) || writeLockOwner == context || writeOptionOwner == context;
     }
 
-    private boolean canReadLock(CONTEXT context)
+    private boolean canLockRead(CONTEXT context)
     {
       if (writeLockOwner != null && writeLockOwner != context)
       {
@@ -754,12 +876,7 @@
       return true;
     }
 
-    private void doReadLock(CONTEXT context)
-    {
-      readLockOwners.add(context);
-    }
-
-    private boolean canWriteLock(CONTEXT context)
+    private boolean canLockWrite(CONTEXT context)
     {
       // If another context owns a writeLock, we can't write-lock
       if (writeLockOwner != null && writeLockOwner != context)
@@ -774,29 +891,26 @@
       }
 
       // If another context owns a readLock, we can't write-lock
-      if (readLockOwners.size() > 1)
+      if (readLockOwners != null)
       {
-        return false;
-      }
-
-      if (readLockOwners.size() == 1)
-      {
-        if (!readLockOwners.contains(context))
+        if (readLockOwners.size() > 1)
         {
           return false;
         }
+
+        if (readLockOwners.size() == 1)
+        {
+          if (!readLockOwners.contains(context))
+          {
+            return false;
+          }
+        }
       }
 
       return true;
     }
 
-    private void doWriteLock(CONTEXT context)
-    {
-      writeLockOwner = context;
-      writeLockCounter++;
-    }
-
-    private boolean canWriteOption(CONTEXT context)
+    private boolean canLockOption(CONTEXT context)
     {
       if (writeOptionOwner != null && writeOptionOwner != context)
       {
@@ -811,14 +925,9 @@
       return true;
     }
 
-    private void doWriteOption(CONTEXT context)
+    private boolean canUnlockRead(CONTEXT context)
     {
-      writeOptionOwner = context;
-    }
-
-    private boolean canReadUnlock(CONTEXT context)
-    {
-      if (!readLockOwners.contains(context))
+      if (readLockOwners != null && !readLockOwners.contains(context))
       {
         return false;
       }
@@ -826,12 +935,7 @@
       return true;
     }
 
-    private void doReadUnlock(CONTEXT context)
-    {
-      readLockOwners.remove(context);
-    }
-
-    private boolean canWriteUnlock(CONTEXT context)
+    private boolean canUnlockWrite(CONTEXT context)
     {
       if (writeLockOwner != context)
       {
@@ -841,16 +945,7 @@
       return true;
     }
 
-    private void doWriteUnlock(CONTEXT context)
-    {
-      writeLockCounter--;
-      if (writeLockCounter == 0)
-      {
-        writeLockOwner = null;
-      }
-    }
-
-    private boolean canWriteUnoption(CONTEXT context)
+    private boolean canUnlockOption(CONTEXT context)
     {
       if (writeOptionOwner != context)
       {
@@ -860,9 +955,77 @@
       return true;
     }
 
-    private void doWriteUnoption(CONTEXT context)
+    private int doLockRead(CONTEXT context, int count)
+    {
+      if (readLockOwners == null)
+      {
+        readLockOwners = new HashBag<>();
+      }
+
+      return readLockOwners.addAndGet(context, count);
+    }
+
+    private int doLockWrite(CONTEXT context, int count)
+    {
+      writeLockOwner = context;
+      return writeLockCounter += count;
+    }
+
+    private int doLockOption(CONTEXT context, int count)
+    {
+      writeOptionOwner = context;
+      return 1;
+    }
+
+    private int doUnlockRead(CONTEXT context, int count)
+    {
+      if (readLockOwners == null)
+      {
+        return 0;
+      }
+
+      if (count == ALL_LOCKS)
+      {
+        readLockOwners.removeCounterFor(context);
+        if (readLockOwners.isEmpty())
+        {
+          readLockOwners = null;
+        }
+
+        return 0;
+      }
+
+      int newCount = readLockOwners.removeAndGet(context, count);
+      if (readLockOwners.isEmpty())
+      {
+        readLockOwners = null;
+      }
+
+      return newCount;
+    }
+
+    private int doUnlockWrite(CONTEXT context, int count)
+    {
+      if (count == ALL_LOCKS)
+      {
+        writeLockOwner = null;
+        writeLockCounter = 0;
+        return 0;
+      }
+
+      writeLockCounter -= count;
+      if (writeLockCounter == 0)
+      {
+        writeLockOwner = null;
+      }
+
+      return writeLockCounter;
+    }
+
+    private int doUnlockOption(CONTEXT context, int count)
     {
       writeOptionOwner = null;
+      return 0;
     }
   }
 }
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/ReentrantReadWriteAccess.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/ReentrantReadWriteAccess.java
new file mode 100644
index 0000000..b2e002a
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/ReentrantReadWriteAccess.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.concurrent;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * @author Eike Stepper
+ * @since 3.16
+ */
+public final class ReentrantReadWriteAccess
+{
+  private final ReentrantReadWriteLock lock;
+
+  private final Access readAccess;
+
+  private final Access writeAccess;
+
+  public ReentrantReadWriteAccess(boolean fair)
+  {
+    lock = new ReentrantReadWriteLock(fair);
+    readAccess = new Access(lock.readLock());
+    writeAccess = new Access(lock.writeLock());
+  }
+
+  public ReentrantReadWriteAccess()
+  {
+    this(false);
+  }
+
+  public ReentrantReadWriteLock getLock()
+  {
+    return lock;
+  }
+
+  public Access readAccess()
+  {
+    return readAccess;
+  }
+
+  public Access writeAccess()
+  {
+    return writeAccess;
+  }
+
+  public Condition newCondition()
+  {
+    return writeAccess.newCondition();
+  }
+}