protected TXEntryState txReadEntry(KeyInfo keyInfo, InternalRegion internalRegion,
      boolean rememberRead, Object expectedOldValue, boolean createIfAbsent)
      throws EntryNotFoundException {
    InternalRegion dataReg = internalRegion.getDataRegionForWrite(keyInfo);
    TXRegionState txr = txReadRegion(dataReg);
    TXEntryState result = null;
    if (txr != null) {
      result = txr.readEntry(keyInfo.getKey());
    }
    if (result == null && rememberRead) {
      // to support repeatable read create an tx entry that reflects current committed state
      if (txr == null) {
        txr = txWriteRegion(internalRegion, keyInfo);
      }
      result = dataReg.createReadEntry(txr, keyInfo, createIfAbsent);
      if (result == null) {
        // createReadEntry will only returns null if createIfAbsent is false.
        // CreateIfAbsent will only be false when this method is called by set operations.
        // In that case we do not want the TXState to have a TXEntryState.
        assert !createIfAbsent;
        return result;
      }
    }

    if (result != null) {
      if (expectedOldValue != null) {
        Object val = result.getNearSidePendingValue();
        if (!AbstractRegionEntry.checkExpectedOldValue(expectedOldValue, val, internalRegion)) {
          throw new EntryNotFoundException(
              "The current value was not equal to expected value.");
        }
      }
    } else {
      /*
       * This means it isn't in the cache and rememberReads = false. This should only happen from
       * test hooks at this point.
       *
       */
      if (txr != null) {
        txr.cleanupNonDirtyEntries(dataReg);
      }
      if (expectedOldValue == null) {
        /*
         * They were expecting non-existence.
         */
        return result;
      } else {
        /*
         * If they pass in null to expectedOldValue, we will have it as Token.INVALID here
         */
        if (!Token.isInvalid(expectedOldValue)) {
          throw new EntryNotFoundException(
              "The current value was not equal to expected value.");
        }
      }
    }
    return result;
  }
This form of txReadEntry takes a concurrent-map argument, expectedOldValue. If this parameter
is not null it must match the current value of the entry or an EntryNotFoundException is
thrown.
geode-core/src/main/java/org/apache/geode/internal/cache/TXState.java