@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>> get(K key) {
   checkNotNull(key, ERROR_NULL_KEY);
   return database
       .get(name, keyCache.getUnchecked(key))
       .thenApply(
           v ->
               v != null
                   ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime())
                   : null);
 }
 @Override
 public CompletableFuture<Collection<Versioned<V>>> values() {
   return database
       .values(name)
       .thenApply(
           c ->
               c.stream()
                   .map(
                       v ->
                           new Versioned<V>(
                               serializer.decode(v.value()), v.version(), v.creationTime()))
                   .collect(Collectors.toList()));
 }
 @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<Set<Entry<K, Versioned<V>>>> entrySet() {
   return database
       .entrySet(name)
       .thenApply(s -> s.stream().map(this::fromRawEntry).collect(Collectors.toSet()));
 }
 @Override
 public CompletableFuture<Set<K>> keySet() {
   return database
       .keySet(name)
       .thenApply(s -> s.stream().map(this::dK).collect(Collectors.toSet()));
 }
 @Override
 public CompletableFuture<Boolean> isEmpty() {
   return database.isEmpty(name);
 }
 @Override
 public CompletableFuture<Boolean> containsValue(V value) {
   checkNotNull(value, ERROR_NULL_VALUE);
   return database.containsValue(name, serializer.encode(value));
 }
 @Override
 public CompletableFuture<Boolean> containsKey(K key) {
   checkNotNull(key, ERROR_NULL_KEY);
   return database.containsKey(name, keyCache.getUnchecked(key));
 }
 @Override
 public CompletableFuture<Void> clear() {
   checkIfUnmodifiable();
   return database.clear(name).thenApply(this::unwrapResult);
 }
 @Override
 public CompletableFuture<Integer> size() {
   return database.size(name);
 }