protected <T> ICompletableFuture<T> replaceInternal(
      K key,
      V oldValue,
      V newValue,
      ExpiryPolicy expiryPolicy,
      boolean hasOldValue,
      boolean withCompletionEvent,
      boolean async) {
    final long start = System.nanoTime();
    ensureOpen();
    if (hasOldValue) {
      validateNotNull(key, oldValue, newValue);
      CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, oldValue, newValue);
    } else {
      validateNotNull(key, newValue);
      CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, newValue);
    }

    final Data keyData = toData(key);
    final Data oldValueData = toData(oldValue);
    final Data newValueData = toData(newValue);
    final Data expiryPolicyData = toData(expiryPolicy);
    final int completionId = withCompletionEvent ? nextCompletionId() : -1;
    ClientMessage request =
        CacheReplaceCodec.encodeRequest(
            nameWithPrefix, keyData, oldValueData, newValueData, expiryPolicyData, completionId);
    ClientInvocationFuture future;
    try {
      future = invoke(request, keyData, completionId);
      invalidateNearCache(keyData);
    } catch (Exception e) {
      throw ExceptionUtil.rethrow(e);
    }

    ClientDelegatingFuture delegatingFuture =
        new ClientDelegatingFuture<T>(
            future, clientContext.getSerializationService(), replaceResponseDecoder);
    if (async && statisticsEnabled) {
      delegatingFuture.andThen(
          new ExecutionCallback<Object>() {
            public void onResponse(Object responseData) {
              Object response = clientContext.getSerializationService().toObject(responseData);
              handleStatisticsOnReplace(false, start, response);
            }

            public void onFailure(Throwable t) {}
          });
    }
    return delegatingFuture;
  }
 @Override
 public <T> T decodeClientMessage(ClientMessage clientMessage) {
   return (T) CacheReplaceCodec.decodeResponse(clientMessage).response;
 }