diff --git a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java
index 7152b7e91..fa31b9e04 100644
--- a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java
@@ -15,11 +15,8 @@
  */
 package org.springframework.data.couchbase.core;
 
-import java.lang.reflect.InaccessibleObjectException;
-import java.util.Map;
-import java.util.Set;
-
 import com.couchbase.client.core.annotation.Stability;
+import com.couchbase.client.core.error.CouchbaseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.ApplicationContext;
@@ -29,7 +26,6 @@
 import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
 import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
 import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
-import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
 import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
 import org.springframework.data.couchbase.core.support.TemplateUtils;
 import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
@@ -39,13 +35,16 @@
 import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
 import org.springframework.util.ClassUtils;
 
-import com.couchbase.client.core.error.CouchbaseException;
+import java.lang.reflect.InaccessibleObjectException;
+import java.util.Map;
+import java.util.Set;
 
 
 /**
  * Base shared by Reactive and non-Reactive TemplateSupport
  *
  * @author Michael Reiche
+ * @author Mico Piira
  */
 @Stability.Internal
 public abstract class AbstractTemplateSupport {
@@ -68,7 +67,7 @@ public AbstractTemplateSupport(ReactiveCouchbaseTemplate template, CouchbaseConv
 	abstract ReactiveCouchbaseTemplate getReactiveTemplate();
 
 	public <T> T decodeEntityBase(Object id, String source, Long cas, Class<T> entityClass, String scope,
-			String collection, Object txResultHolder, CouchbaseResourceHolder holder) {
+			String collection, Object txResultHolder, CouchbaseResourceHolder holder, CouchbaseDocument converted) {
 
 		// this is the entity class defined for the repository. It may not be the class of the document that was read
 		// we will reset it after reading the document
@@ -88,7 +87,6 @@ public <T> T decodeEntityBase(Object id, String source, Long cas, Class<T> entit
 			// to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
 			// Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
 			// if this is a Collection or array, only the first element will be returned.
-			final CouchbaseDocument converted = new CouchbaseDocument(id);
 			Set<Map.Entry<String, Object>> set = ((CouchbaseDocument) translationService.decode(source, converted))
 					.getContent().entrySet();
 			return (T) set.iterator().next().getValue();
@@ -99,8 +97,6 @@ public <T> T decodeEntityBase(Object id, String source, Long cas, Class<T> entit
 					+ TemplateUtils.SELECT_ID);
 		}
 
-		final CouchbaseDocument converted = new CouchbaseDocument(id);
-
 		// if possible, set the version property in the source so that if the constructor has a long version argument,
 		// it will have a value and not fail (as null is not a valid argument for a long argument). This possible failure
 		// can be avoid by defining the argument as Long instead of long.
@@ -148,7 +144,7 @@ CouchbasePersistentEntity couldBePersistentEntity(Class<?> entityClass) {
 		return null;
 	}
 
-	public <T> T applyResultBase(T entity, CouchbaseDocument converted, Object id, long cas,
+	public <T> T applyResultBase(T entity, Object id, long cas,
 			Object txResultHolder, CouchbaseResourceHolder holder) {
 		ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
 
@@ -168,7 +164,6 @@ public <T> T applyResultBase(T entity, CouchbaseDocument converted, Object id, l
 		if (holder != null) {
 			holder.transactionResultHolder(txResultHolder, (T) accessor.getBean());
 		}
-		maybeEmitEvent(new AfterSaveEvent(accessor.getBean(), converted));
 		return (T) accessor.getBean();
 
 	}
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java
index 8714296df..18290da4e 100644
--- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java
@@ -22,10 +22,7 @@
 import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
 import org.springframework.data.couchbase.core.convert.translation.TranslationService;
 import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
-import org.springframework.data.couchbase.core.mapping.event.AfterConvertCallback;
-import org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback;
-import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
-import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
+import org.springframework.data.couchbase.core.mapping.event.*;
 import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
 import org.springframework.data.mapping.callback.EntityCallbacks;
 import org.springframework.util.Assert;
@@ -37,6 +34,7 @@
  * @author Michael Reiche
  * @author Jorge Rodriguez Martin
  * @author Carlos Espinaco
+ * @author Mico Piira
  * @since 3.0
  */
 class CouchbaseTemplateSupport extends AbstractTemplateSupport implements ApplicationContextAware, TemplateSupport {
@@ -51,26 +49,30 @@ public CouchbaseTemplateSupport(final CouchbaseTemplate template, final Couchbas
 	}
 
 	@Override
-	public CouchbaseDocument encodeEntity(final Object entityToEncode) {
+	public <T> EncodedEntity<T> encodeEntity(final T entityToEncode) {
 		maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode));
 		Object maybeNewEntity = maybeCallBeforeConvert(entityToEncode, "");
 		final CouchbaseDocument converted = new CouchbaseDocument();
 		converter.write(maybeNewEntity, converted);
-		maybeCallAfterConvert(entityToEncode, converted, "");
 		maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted));
-		return converted;
+		return new EncodedEntity<>(maybeCallBeforeSave(entityToEncode, converted, ""), converted);
 	}
 
 	@Override
 	public <T> T decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
 			Object txHolder, CouchbaseResourceHolder holder) {
-		return decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder);
+		CouchbaseDocument converted = new CouchbaseDocument(id);
+		T decoded = decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder, converted);
+		maybeEmitEvent(new AfterConvertEvent<>(decoded, converted));
+		return maybeCallAfterConvert(decoded, converted, "");
 	}
 
 	@Override
 	public <T> T applyResult(T entity, CouchbaseDocument converted, Object id, long cas,
 			Object txResultHolder, CouchbaseResourceHolder holder) {
-		return applyResultBase(entity, converted, id, cas, txResultHolder, holder);
+		T applied = applyResultBase(entity, id, cas, txResultHolder, holder);
+		maybeEmitEvent(new AfterSaveEvent<>(applied, converted));
+		return maybeCallAfterSave(applied, converted, "");
 	}
 
 	@Override
@@ -119,6 +121,24 @@ protected <T> T maybeCallAfterConvert(T object, CouchbaseDocument document, Stri
 		return object;
 	}
 
+	protected <T> T maybeCallAfterSave(T object, CouchbaseDocument document, String collection) {
+		if (null != entityCallbacks) {
+			return entityCallbacks.callback(AfterSaveCallback.class, object, document, collection);
+		} else {
+			LOG.info("maybeCallAfterSave called, but CouchbaseTemplate not initialized with applicationContext");
+		}
+		return object;
+	}
+
+	protected <T> T maybeCallBeforeSave(T object, CouchbaseDocument document, String collection) {
+		if (null != entityCallbacks) {
+			return entityCallbacks.callback(BeforeSaveCallback.class, object, document, collection);
+		} else {
+			LOG.info("maybeCallBeforeSave called, but CouchbaseTemplate not initialized with applicationContext");
+		}
+		return object;
+	}
+
 	@Override
 	ReactiveCouchbaseTemplate getReactiveTemplate() {
 		return template.reactive();
diff --git a/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java b/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java
new file mode 100644
index 000000000..09f888919
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012-2023 the original author or authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core;
+
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+
+/**
+ * @author Mico Piira
+ */
+public record EncodedEntity<T>(T entity, CouchbaseDocument document) {
+}
diff --git a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java
index 4d90f591a..b824d5a35 100644
--- a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java
+++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java
@@ -26,6 +26,7 @@
  *
  * @author Carlos Espinaco
  * @author Michael Reiche
+ * @author Mico Piira
  * @since 4.2
  */
 public class NonReactiveSupportWrapper implements ReactiveTemplateSupport {
@@ -37,7 +38,7 @@ public NonReactiveSupportWrapper(TemplateSupport support) {
 	}
 
 	@Override
-	public Mono<CouchbaseDocument> encodeEntity(Object entityToEncode) {
+	public <T> Mono<EncodedEntity<T>> encodeEntity(T entityToEncode) {
 		return Mono.fromSupplier(() -> support.encodeEntity(entityToEncode));
 	}
 
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java
index 5b9843536..72b86aacc 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java
@@ -16,28 +16,25 @@
 
 package org.springframework.data.couchbase.core;
 
-import reactor.core.publisher.Mono;
-
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
 import org.springframework.data.couchbase.core.convert.translation.TranslationService;
 import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
-import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
-import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
-import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback;
-import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback;
+import org.springframework.data.couchbase.core.mapping.event.*;
 import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
 import org.springframework.data.mapping.callback.EntityCallbacks;
 import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
 import org.springframework.util.Assert;
+import reactor.core.publisher.Mono;
 
 /**
  * Internal encode/decode support for {@link ReactiveCouchbaseTemplate}.
  *
  * @author Carlos Espinaco
  * @author Michael Reiche
+ * @author Mico Piira
  * @since 4.2
  */
 class ReactiveCouchbaseTemplateSupport extends AbstractTemplateSupport
@@ -53,14 +50,19 @@ public ReactiveCouchbaseTemplateSupport(final ReactiveCouchbaseTemplate template
 	}
 
 	@Override
-	public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
-		return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
-				.flatMap(entity -> maybeCallBeforeConvert(entity, "")).map(maybeNewEntity -> {
+	public <T> Mono<EncodedEntity<T>> encodeEntity(final T entityToEncode) {
+		maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode));
+		return maybeCallBeforeConvert(entityToEncode, "")
+				.map(maybeNewEntity -> {
 					final CouchbaseDocument converted = new CouchbaseDocument();
 					converter.write(maybeNewEntity, converted);
 					return converted;
-				}).flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted))
-				.doOnNext(converted -> maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)));
+				})
+				.flatMap(converted -> {
+					maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted));
+					return maybeCallBeforeSave(entityToEncode, converted, "")
+							.map(potentiallyModified -> new EncodedEntity<>(potentiallyModified, converted));
+				});
 	}
 
 	@Override
@@ -71,14 +73,23 @@ ReactiveCouchbaseTemplate getReactiveTemplate() {
 	@Override
 	public <T> Mono<T> decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope,
 			String collection, Object txResultHolder, CouchbaseResourceHolder holder) {
+		CouchbaseDocument converted = new CouchbaseDocument(id);
 		return Mono
-				.fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder));
+				.fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder, converted))
+				.flatMap(entity -> {
+					maybeEmitEvent(new AfterConvertEvent<>(entity, converted));
+					return maybeCallAfterConvert(entity, converted, "");
+				});
 	}
 
 	@Override
 	public <T> Mono<T> applyResult(T entity, CouchbaseDocument converted, Object id, Long cas,
 			Object txResultHolder, CouchbaseResourceHolder holder) {
-		return Mono.fromSupplier(() -> applyResultBase(entity, converted, id, cas, txResultHolder, holder));
+		return Mono.fromSupplier(() -> applyResultBase(entity, id, cas, txResultHolder, holder))
+				.flatMap(saved -> {
+					maybeEmitEvent(new AfterSaveEvent<>(saved, converted));
+					return maybeCallAfterSave(saved, converted, "");
+				});
 	}
 
 	@Override
@@ -104,6 +115,25 @@ public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCal
 		this.reactiveEntityCallbacks = reactiveEntityCallbacks;
 	}
 
+	protected <T> Mono<T> maybeCallBeforeSave(T object, CouchbaseDocument document, String collection) {
+		if (reactiveEntityCallbacks != null) {
+			return reactiveEntityCallbacks.callback(ReactiveBeforeSaveCallback.class, object, document, collection);
+		} else {
+			LOG.info("maybeCallBeforeSave called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
+		}
+		return Mono.just(object);
+	}
+
+	protected <T> Mono<T> maybeCallAfterSave(T object, CouchbaseDocument document, String collection) {
+		if (reactiveEntityCallbacks != null) {
+			return reactiveEntityCallbacks.callback(ReactiveAfterSaveCallback.class, object, document, collection);
+		} else {
+			LOG.info("maybeCallAfterSave called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
+		}
+		return Mono.just(object);
+	}
+
+
 	protected <T> Mono<T> maybeCallBeforeConvert(T object, String collection) {
 		if (reactiveEntityCallbacks != null) {
 			return reactiveEntityCallbacks.callback(ReactiveBeforeConvertCallback.class, object, collection);
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java
index c6dfb5924..bd0f868d3 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java
@@ -46,6 +46,7 @@
  *
  * @author Michael Reiche
  * @author Tigran Babloyan
+ * @author Mico Piira
  */
 public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
 
@@ -102,12 +103,15 @@ public Mono<T> one(T object) {
 			return Mono
 					.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()))
 					.flatMap(collection -> support.encodeEntity(object)
-							.flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
+							.flatMap(encodedEntity -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
+								T potentiallyModified = encodedEntity.entity();
+								CouchbaseDocument converted = encodedEntity.document();
+
 								if (!ctxOpt.isPresent()) {
 									return collection.reactive()
 											.insert(converted.getId().toString(), converted.export(),
 													buildOptions(pArgs.getOptions(), converted))
-											.flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(),
+											.flatMap(result -> this.support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(),
 													null, null));
 								} else {
 									rejectInvalidTransactionalOptions();
@@ -120,7 +124,7 @@ public Mono<T> one(T object) {
 													template.getCouchbaseClientFactory().getCluster().environment().transcoder()
 															.encode(converted.export()).encoded(),
 													new SpanWrapper(span))
-											.flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(),
+											.flatMap(result -> this.support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(),
 													null, null));
 								}
 							})).onErrorMap(throwable -> {
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java
index 4d09819d7..54de9a5f3 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java
@@ -23,7 +23,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
-import org.springframework.data.couchbase.core.mapping.CouchbaseList;
 import org.springframework.data.couchbase.core.query.OptionsBuilder;
 import org.springframework.data.couchbase.core.support.PseudoArgs;
 import org.springframework.util.Assert;
@@ -37,6 +36,7 @@
  * {@link ReactiveMutateInByIdOperation} implementations for Couchbase.
  *
  * @author Tigran Babloyan
+ * @author Mico Piira
  */
 public class ReactiveMutateInByIdOperationSupport implements ReactiveMutateInByIdOperation {
 
@@ -105,14 +105,16 @@ public Mono<T> one(T object) {
 			}
 			
 			Mono<T> reactiveEntity = TransactionalSupport.verifyNotInTransaction("mutateInById")
-					.then(support.encodeEntity(object)).flatMap(converted -> {
+					.then(support.encodeEntity(object)).flatMap(encodedEntity -> {
+						T potentiallyModified = encodedEntity.entity();
+						CouchbaseDocument converted = encodedEntity.document();
 						return Mono
 								.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope())
 										.getCollection(pArgs.getCollection()))
 								.flatMap(collection -> collection.reactive()
-										.mutateIn(converted.getId().toString(), getMutations(converted), buildMutateInOptions(pArgs.getOptions(), object, converted))
+										.mutateIn(converted.getId().toString(), getMutations(converted), buildMutateInOptions(pArgs.getOptions(), potentiallyModified, converted))
 										.flatMap(
-												result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null)));
+												result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)));
 					});
 
 			return reactiveEntity.onErrorMap(throwable -> {
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java
index fa176074c..0c2f10195 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java
@@ -49,6 +49,7 @@
  *
  * @author Michael Reiche
  * @author Tigran Babloyan
+ * @author Mico Piira
  */
 public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
 
@@ -105,20 +106,22 @@ public Mono<T> one(T object) {
 			return Mono
 					.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()))
 					.flatMap(collection -> support.encodeEntity(object)
-							.flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
+							.flatMap(encodedEntity -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
+								T potentiallyModified = encodedEntity.entity();
+								CouchbaseDocument converted = encodedEntity.document();
 								if (!ctxOpt.isPresent()) {
 									return collection.reactive()
 											.replace(converted.getId().toString(), converted.export(),
-													buildReplaceOptions(pArgs.getOptions(), object, converted))
-											.flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null,
+													buildReplaceOptions(pArgs.getOptions(), potentiallyModified, converted))
+											.flatMap(result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null,
 													null));
 								} else {
 									rejectInvalidTransactionalOptions();
 
-									Long cas = support.getCas(object);
+									Long cas = support.getCas(potentiallyModified);
 									if (cas == null || cas == 0) {
 										throw new IllegalArgumentException(
-												"cas must be supplied in object for tx replace. object=" + object);
+												"cas must be supplied in object for tx replace. object=" + potentiallyModified);
 									}
 
 									CollectionIdentifier collId = makeCollectionIdentifier(collection.async());
@@ -138,7 +141,7 @@ public Mono<T> one(T object) {
 										return ctx.replace(getResult, template.getCouchbaseClientFactory().getCluster().environment()
 												.transcoder().encode(converted.export()).encoded(), new SpanWrapper(span));
 									}).flatMap(
-											result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null));
+											result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null));
 								}
 							})).onErrorMap(throwable -> {
 								if (throwable instanceof RuntimeException) {
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java
index a128ac09f..294aa3fb5 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java
@@ -15,20 +15,20 @@
  */
 package org.springframework.data.couchbase.core;
 
-import reactor.core.publisher.Mono;
-
 import org.springframework.data.couchbase.core.convert.translation.TranslationService;
 import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
 import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
+import reactor.core.publisher.Mono;
 
 /**
  * ReactiveTemplateSupport
  *
  * @author Michael Reiche
+ * @author Mico Piira
  */
 public interface ReactiveTemplateSupport {
 
-	Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
+	<T> Mono<EncodedEntity<T>> encodeEntity(T entityToEncode);
 
 	<T> Mono<T> decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
 			Object txResultHolder, CouchbaseResourceHolder holder);
diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java
index ea0c4e62e..61e6b2f64 100644
--- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java
@@ -38,6 +38,7 @@
  *
  * @author Michael Reiche
  * @author Tigran Babloyan
+ * @author Mico Piira
  */
 public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
 
@@ -92,14 +93,16 @@ public Mono<T> one(T object) {
 				LOG.debug("upsertById object={} {}", object, pArgs);
 			}
 			Mono<T> reactiveEntity = TransactionalSupport.verifyNotInTransaction("upsertById")
-					.then(support.encodeEntity(object)).flatMap(converted -> {
+					.then(support.encodeEntity(object)).flatMap(encodedEntity -> {
+						T potentiallyModified = encodedEntity.entity();
+						CouchbaseDocument converted = encodedEntity.document();
 						return Mono
 								.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope())
 										.getCollection(pArgs.getCollection()))
 								.flatMap(collection -> collection.reactive()
 										.upsert(converted.getId().toString(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
 										.flatMap(
-												result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null)));
+												result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)));
 					});
 
 			return reactiveEntity.onErrorMap(throwable -> {
diff --git a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java
index 8e176a815..b3b3a5c2f 100644
--- a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java
+++ b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java
@@ -21,10 +21,11 @@
 
 /**
  * @author Michael Reiche
+ * @author Mico Piira
  */
 public interface TemplateSupport {
 
-	CouchbaseDocument encodeEntity(Object entityToEncode);
+	<T> EncodedEntity<T> encodeEntity(T entityToEncode);
 
 	<T> T decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
 			Object txResultHolder, CouchbaseResourceHolder holder);
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java
index a923790e4..7bc66fa0d 100644
--- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java
@@ -19,23 +19,22 @@
 import org.springframework.data.mapping.callback.EntityCallback;
 
 /**
- * Callback being invoked after a domain object is materialized from a {@link CouchbaseDocument} when reading results.
+ * Callback being invoked after a domain object is materialized from a document when reading results.
  *
  * @author Michael Reiche
+ * @author Mico Piira
  * @see org.springframework.data.mapping.callback.EntityCallbacks
  * @since 4.2
  */
 @FunctionalInterface
 public interface AfterConvertCallback<T> extends EntityCallback<T> {
-
 	/**
-	 * Entity callback method invoked after a domain object is materialized from a {@link CouchbaseDocument}. Can return
-	 * either the same or a modified instance of the domain object.
+	 * Entity callback method invoked after a domain object is materialized from a document.
 	 *
 	 * @param entity the domain object (the result of the conversion).
-	 * @param document must not be {@literal null}.
-	 * @param collection name of the collection.
-	 * @return the domain object that is the result of reading it from the {@link CouchbaseDocument}.
+	 * @param document must not be null.
+	 * @param collection name of the document.
+	 * @return the domain object that is the result of reading it from the document.
 	 */
 	T onAfterConvert(T entity, CouchbaseDocument document, String collection);
 }
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java
new file mode 100644
index 000000000..41fc9566c
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core.mapping.event;
+
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+
+/**
+ * @author Mico Piira
+ */
+public class AfterConvertEvent<E> extends CouchbaseMappingEvent<E> {
+    public AfterConvertEvent(E source, CouchbaseDocument document) {
+        super(source, document);
+    }
+}
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java
new file mode 100644
index 000000000..33a341e2e
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core.mapping.event;
+
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+import org.springframework.data.mapping.callback.EntityCallback;
+
+/**
+ * Entity callback triggered after save of a CouchbaseDocument.
+ *
+ * @author Mico Piira
+ */
+@FunctionalInterface
+public interface AfterSaveCallback<T> extends EntityCallback<T> {
+    /**
+     * Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of the domain object.
+     *
+     * @param entity the domain object that was saved.
+     * @param document CouchbaseDocument representing the entity.
+     * @param collection name of the collection.
+     * @return the domain object that was persisted.
+     */
+    T onAfterSave(T entity, CouchbaseDocument document, String collection);
+}
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java
deleted file mode 100644
index 5c82c0e95..000000000
--- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2012-2024 the original author or authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.data.couchbase.core.mapping.event;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.ObjectFactory;
-import org.springframework.core.Ordered;
-import org.springframework.data.auditing.AuditingHandler;
-import org.springframework.data.auditing.IsNewAwareAuditingHandler;
-import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
-import org.springframework.data.mapping.callback.EntityCallback;
-import org.springframework.data.mapping.context.MappingContext;
-import org.springframework.util.Assert;
-
-/**
- * {@link EntityCallback} to populate auditing related fields on an entity about to be saved.
- *
- * @author Jorge Rodríguez Martín
- * @since 4.2
- */
-public class AuditingEntityCallback implements BeforeConvertCallback<Object>, AfterConvertCallback<Object>, Ordered {
-
-	private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
-	private static final Logger LOG = LoggerFactory.getLogger(AuditingEntityCallback.class);
-
-	/**
-	 * Creates a new {@link AuditingEntityCallback} using the given {@link MappingContext} and {@link AuditingHandler}
-	 * provided by the given {@link ObjectFactory}.
-	 *
-	 * @param auditingHandlerFactory must not be {@literal null}.
-	 */
-	public AuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory) {
-		Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
-		this.auditingHandlerFactory = auditingHandlerFactory;
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * @see org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback#onBeforeConvert(java.lang.Object, java.lang.String)
-	 */
-	@Override
-	public Object onBeforeConvert(Object entity, String collection) {
-		// LOG.trace("onBeforeConvert " + entity);
-		return entity; // markAudited called in AuditingEventListener.onApplicationEvent()
-										// auditingHandlerFactory.getObject().markAudited(entity);
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * @see org.springframework.data.couchbase.core.mapping.event.AfterConvertCallback#onAfterConvert(java.lang.Object, CouchbaseDocument, java.lang.String)
-	 */
-	@Override
-	public Object onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
-		// LOG.trace("onAfterConvert " + document);
-		return entity;
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * @see org.springframework.core.Ordered#getOrder()
-	 */
-	@Override
-	public int getOrder() {
-		return 100;
-	}
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java
index f8cd1423a..3efa2a052 100644
--- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java
@@ -22,15 +22,14 @@
  *
  * @author Mark Paluch
  * @author Michael Reiche
+ * @author Mico Piira
  * @see org.springframework.data.mapping.callback.EntityCallbacks
  * @since 2.2
  */
 @FunctionalInterface
 public interface BeforeConvertCallback<T> extends EntityCallback<T> {
-
 	/**
-	 * Entity callback method invoked before a domain object is converted to be persisted. Can return either the same or a
-	 * modified instance of the domain object.
+	 * Entity callback method invoked before a domain object is converted to be persisted.
 	 *
 	 * @param entity the domain object to save.
 	 * @param collection name of the collection.
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java
new file mode 100644
index 000000000..162e023be
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core.mapping.event;
+
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+import org.springframework.data.mapping.callback.EntityCallback;
+
+/**
+ * Entity callback triggered before save of a CouchbaseDocument.
+ *
+ * @author Mico Piira
+ */
+@FunctionalInterface
+public interface BeforeSaveCallback<T> extends EntityCallback<T> {
+    /**
+     * Entity callback method invoked before a domain object is saved.
+     * Can return either the same or a modified instance of the domain object and can modify CouchbaseDocument contents.
+     * This method is called after converting the entity to a CouchbaseDocument so effectively the document is used as
+     * outcome of invoking this callback.
+     * Changes to the domain object are not taken into account for saving, only changes to the document.
+     * Only transient fields of the entity should be changed in this callback.
+     * To change persistent the entity before being converted, use the {@link BeforeConvertCallback}.
+     *
+     * @param entity the domain object to save.
+     * @param document CouchbaseDocument representing the entity.
+     * @param collection name of the collection.
+     * @return the domain object to be persisted.
+     */
+    T onBeforeSave(T entity, CouchbaseDocument document, String collection);
+}
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java
index cacb59991..e90322e86 100644
--- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java
@@ -20,9 +20,10 @@
 import org.springframework.data.mapping.callback.EntityCallback;
 
 /**
- * Callback being invoked after a domain object is materialized from a {@link org.springframework.data.couchbase.core.mapping.CouchbaseDocument} when reading results.
+ * Callback being invoked after a domain object is materialized from a document when reading results.
  *
  * @author Jorge Rodríguez Martín
+ * @author Mico Piira
  * @see org.springframework.data.mapping.callback.EntityCallbacks
  * @since 4.2
  */
@@ -30,11 +31,12 @@
 public interface ReactiveAfterConvertCallback<T> extends EntityCallback<T> {
 
 	/**
-	 * Entity callback method invoked after a domain object is converted to be persisted. Can return
-	 * either the same of a modified instance of the domain object.
-	 * @param entity the domain object to save.
-	 * @param collection name of the collection.
-	 * @return a {@link Publisher} emitting the domain object to be persisted.
+	 * Entity callback method invoked after a domain object is materialized from a document.
+	 *
+	 * @param entity the domain object (the result of the conversion).
+	 * @param document must not be null.
+	 * @param collection name of the document.
+	 * @return the domain object that is the result of reading it from the document.
 	 */
 	Publisher<T> onAfterConvert(T entity, CouchbaseDocument document, String collection);
 }
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java
new file mode 100644
index 000000000..68138170a
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core.mapping.event;
+
+import org.reactivestreams.Publisher;
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+import org.springframework.data.mapping.callback.EntityCallback;
+
+/**
+ * Entity callback triggered after save of a CouchbaseDocument.
+ *
+ * @author Mico Piira
+ */
+@FunctionalInterface
+public interface ReactiveAfterSaveCallback<T> extends EntityCallback<T> {
+    /**
+     * Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of the domain object.
+     *
+     * @param entity the domain object that was saved.
+     * @param document CouchbaseDocument representing the entity.
+     * @param collection name of the collection.
+     * @return the domain object that was persisted.
+     */
+    Publisher<T> onAfterSave(T entity, CouchbaseDocument document, String collection);
+}
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java
index ca6b61408..081ede271 100644
--- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java
@@ -22,20 +22,18 @@
  * Callback being invoked before a domain object is converted to be persisted.
  *
  * @author Jorge Rodríguez Martín
+ * @author Mico Piira
  * @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
  * @since 4.2
  */
 @FunctionalInterface
 public interface ReactiveBeforeConvertCallback<T> extends EntityCallback<T> {
-
 	/**
-	 * Entity callback method invoked before a domain object is converted to be persisted. Can return
-	 * either the same of a modified instance of the domain object.
+	 * Entity callback method invoked before a domain object is converted to be persisted.
 	 *
 	 * @param entity the domain object to save.
 	 * @param collection name of the collection.
-	 * @return a {@link Publisher} emitting the domain object to be persisted.
+	 * @return the domain object to be persisted.
 	 */
 	Publisher<T> onBeforeConvert(T entity, String collection);
-
 }
diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java
new file mode 100644
index 000000000..0ff1c73bc
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core.mapping.event;
+
+import org.reactivestreams.Publisher;
+import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
+import org.springframework.data.mapping.callback.EntityCallback;
+
+/**
+ * Entity callback triggered before save of a CouchbaseDocument.
+ *
+ * @author Mico Piira
+ */
+@FunctionalInterface
+public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
+    /**
+     * Entity callback method invoked before a domain object is saved.
+     * Can return either the same or a modified instance of the domain object and can modify CouchbaseDocument contents.
+     * This method is called after converting the entity to a CouchbaseDocument so effectively the document is used as
+     * outcome of invoking this callback.
+     * Changes to the domain object are not taken into account for saving, only changes to the document.
+     * Only transient fields of the entity should be changed in this callback.
+     * To change persistent the entity before being converted, use the {@link ReactiveBeforeConvertCallback}.
+     *
+     * @param entity the domain object to save.
+     * @param document CouchbaseDocument representing the entity.
+     * @param collection name of the collection.
+     * @return the domain object to be persisted.
+     */
+    Publisher<T> onBeforeSave(T entity, CouchbaseDocument document, String collection);
+}
diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java
index 5e770f948..2a9877d8d 100644
--- a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java
+++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java
@@ -31,7 +31,6 @@
 import org.springframework.data.config.ParsingUtils;
 import org.springframework.data.couchbase.config.BeanNames;
 import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
-import org.springframework.data.couchbase.core.mapping.event.AuditingEntityCallback;
 import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener;
 import org.springframework.util.Assert;
 
@@ -85,16 +84,6 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle
 		Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
 		Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
 
-		// Register the AuditEntityCallback
-
-		BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
-				.rootBeanDefinition(AuditingEntityCallback.class);
-		listenerBeanDefinitionBuilder
-				.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
-
-		registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
-				AuditingEntityCallback.class.getName(), registry);
-
 		// Register the AuditingEventListener
 
 		BeanDefinitionBuilder listenerBeanDefinitionBuilder2 = BeanDefinitionBuilder
diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java
new file mode 100644
index 000000000..4b42a240a
--- /dev/null
+++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012-2023 the original author or authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.couchbase.core;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.couchbase.core.mapping.event.*;
+import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
+import org.springframework.data.couchbase.domain.Config;
+import org.springframework.data.couchbase.util.ClusterType;
+import org.springframework.data.couchbase.util.IgnoreWhen;
+import org.springframework.data.couchbase.util.JavaIntegrationTests;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import reactor.core.publisher.Mono;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Mico Piira
+ */
+@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
+@SpringJUnitConfig({Config.class, CouchbaseTemplateCallbackIntegrationTests.Callbacks.class})
+@DirtiesContext
+class CouchbaseTemplateCallbackIntegrationTests extends JavaIntegrationTests {
+
+    @Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
+    @Autowired CouchbaseTemplate couchbaseTemplate;
+
+    static class Callbacks {
+
+        @Bean
+        ReactiveBeforeConvertCallback<CallbacksTestEntity> reactiveBeforeConvertCallback() {
+            return (entity, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_beforeconvert"));
+        }
+
+        @Bean
+        ReactiveAfterSaveCallback<CallbacksTestEntity> reactiveAfterSaveCallback() {
+            return (entity, document, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_aftersave"));
+        }
+
+        @Bean
+        ReactiveBeforeSaveCallback<CallbacksTestEntity> reactiveBeforeSaveCallback() {
+            return (entity, document, collection) ->
+                    Mono.fromCallable(() -> document.put("name", document.get("name") + "_beforesave"))
+                            .thenReturn(new CallbacksTestEntity(entity.id(), entity.name() + "_beforesave2"));
+        }
+
+        @Bean
+        ReactiveAfterConvertCallback<CallbacksTestEntity> reactiveAfterConvertCallback() {
+            return (entity, document, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_afterconvert"));
+        }
+
+        @Bean
+        BeforeConvertCallback<CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity> BeforeConvertCallback() {
+            return (entity, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_beforeconvert");
+        }
+
+        @Bean
+        AfterSaveCallback<CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity> AfterSaveCallback() {
+            return (entity, document, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_aftersave");
+        }
+
+        @Bean
+        BeforeSaveCallback<CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity> BeforeSaveCallback() {
+            return (entity, document, collection) -> {
+                document.put("name", document.get("name") + "_beforesave");
+                return new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_beforesave2");
+            };
+        }
+
+        @Bean
+        AfterConvertCallback<CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity> AfterConvertCallback() {
+            return (entity, document, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_afterconvert");
+        }
+
+    }
+
+    public record CallbacksTestEntity(@Id @GeneratedValue String id, String name) { }
+
+    @Test
+    void testReactiveCallbacks() {
+        CallbacksTestEntity entity = new CallbacksTestEntity(UUID.randomUUID().toString(), "a");
+        CallbacksTestEntity saved = reactiveCouchbaseTemplate.insertById(CallbacksTestEntity.class)
+                .one(entity)
+                .block();
+        assertEquals("a_beforesave2_aftersave", saved.name());
+        CallbacksTestEntity block = reactiveCouchbaseTemplate.findById(CallbacksTestEntity.class).one(entity.id()).block();
+        assertEquals("a_beforeconvert_beforesave_afterconvert", block.name());
+    }
+
+    @Test
+    void testBlockingCallbacks() {
+        CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity entity = new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(UUID.randomUUID().toString(), "a");
+        CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity saved = couchbaseTemplate.insertById(CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity.class)
+                .one(entity);
+        assertEquals("a_beforesave2_aftersave", saved.name());
+        CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity block = couchbaseTemplate.findById(CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity.class).one(entity.id());
+        assertEquals("a_beforeconvert_beforesave_afterconvert", block.name());
+    }
+
+}