@Override
 public CompletableFuture<Boolean> remove(K key, V value) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(value, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   return database
       .remove(name, keyCache.getUnchecked(key), serializer.encode(value))
       .thenApply(this::unwrapResult);
 }
 @Override
 public CompletableFuture<Boolean> replace(K key, V oldValue, V newValue) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(newValue, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   byte[] existing = oldValue != null ? serializer.encode(oldValue) : null;
   return database
       .replace(name, keyCache.getUnchecked(key), existing, serializer.encode(newValue))
       .thenApply(this::unwrapResult);
 }
 @Override
 public CompletableFuture<Versioned<V>> put(K key, V value) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(value, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   return database
       .put(name, keyCache.getUnchecked(key), serializer.encode(value))
       .thenApply(this::unwrapResult)
       .thenApply(
           v ->
               v != null
                   ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime())
                   : null);
 }
 @Override
 public CompletableFuture<Versioned<V>> remove(K key) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkIfUnmodifiable();
   return database
       .remove(name, keyCache.getUnchecked(key))
       .thenApply(this::unwrapResult)
       .thenApply(v -> v != null ? v.<V>map(serializer::decode) : null)
       .whenComplete(
           (r, e) -> {
             if (r != null) {
               notifyListeners(new MapEvent<>(name, MapEvent.Type.REMOVE, key, r));
             }
           });
 }
 @Override
 public CompletableFuture<Versioned<V>> putAndGet(K key, V value) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(value, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   return database
       .putAndGet(name, keyCache.getUnchecked(key), serializer.encode(value))
       .thenApply(this::unwrapResult)
       .thenApply(
           v -> {
             Versioned<byte[]> rawNewValue = v.newValue();
             return new Versioned<>(
                 serializer.decode(rawNewValue.value()),
                 rawNewValue.version(),
                 rawNewValue.creationTime());
           });
 }
 @Override
 public CompletableFuture<Versioned<V>> putIfAbsent(K key, V value) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(value, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   AtomicReference<MapEvent<K, V>> event = new AtomicReference<>();
   return database
       .putIfAbsentAndGet(name, keyCache.getUnchecked(key), serializer.encode(value))
       .thenApply(this::unwrapResult)
       .whenComplete(
           (r, e) -> {
             if (r != null && r.updated()) {
               event.set(
                   new MapEvent<K, V>(
                       name, MapEvent.Type.INSERT, key, r.newValue().<V>map(serializer::decode)));
             }
           })
       .thenApply(v -> v.updated() ? null : v.oldValue().<V>map(serializer::decode))
       .whenComplete((r, e) -> notifyListeners(event.get()));
 }
 @Override
 public CompletableFuture<Optional<Versioned<V>>> replaceAndGet(
     K key, long oldVersion, V newValue) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkNotNull(newValue, ERROR_NULL_VALUE);
   checkIfUnmodifiable();
   return database
       .replaceAndGet(name, keyCache.getUnchecked(key), oldVersion, serializer.encode(newValue))
       .thenApply(this::unwrapResult)
       .thenApply(
           v -> {
             if (v.updated()) {
               Versioned<byte[]> rawNewValue = v.newValue();
               return Optional.of(
                   new Versioned<>(
                       serializer.decode(rawNewValue.value()),
                       rawNewValue.version(),
                       rawNewValue.creationTime()));
             } else {
               return Optional.empty();
             }
           });
 }
 @Override
 public CompletableFuture<Boolean> remove(K key, long version) {
   checkNotNull(key, ERROR_NULL_KEY);
   checkIfUnmodifiable();
   return database.remove(name, keyCache.getUnchecked(key), version).thenApply(this::unwrapResult);
 }
 @Override
 public CompletableFuture<Void> clear() {
   checkIfUnmodifiable();
   return database.clear(name).thenApply(this::unwrapResult);
 }