diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java
index 0a9d4f463..f1ba459a6 100644
--- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java
+++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java
@@ -252,4 +252,11 @@
 	 * @since 5.4
 	 */
 	String mappedTypeName() default "";
+
+	/**
+	 * Maps your data beyond the dynamic field mapping rules.
+	 * 
+	 * @since 5.4
+	 */
+	boolean dynamicTemplate() default false;
 }
diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java
index c46f1da0e..4b20ef1f9 100644
--- a/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java
+++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java
@@ -80,7 +80,6 @@ public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate cluste
 		this.elasticsearchConverter = elasticsearchConverter;
 		this.boundClass = boundClass;
 		this.boundIndex = null;
-
 	}
 
 	public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate clusterTemplate,
@@ -95,7 +94,6 @@ public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate cluste
 		this.elasticsearchConverter = elasticsearchConverter;
 		this.boundClass = null;
 		this.boundIndex = boundIndex;
-
 	}
 
 	protected Class<?> checkForBoundClass() {
@@ -145,6 +143,8 @@ protected boolean doCreate(IndexCoordinates indexCoordinates, Map<String, Object
 
 		CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
 		CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest));
+		// refresh cached mappings
+		refreshMapping();
 		return Boolean.TRUE.equals(createIndexResponse.acknowledged());
 	}
 
@@ -241,6 +241,28 @@ public Map<String, Object> getMapping() {
 		return responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates);
 	}
 
+	/**
+	 * Refreshes the mapping for the current entity.
+	 * <p>
+	 * This method is responsible for retrieving and updating the metadata related to the current entity.
+	 */
+	private void refreshMapping() {
+		if (boundClass == null) {
+			return;
+		}
+
+		ElasticsearchPersistentEntity<?> entity = this.elasticsearchConverter.getMappingContext()
+				.getPersistentEntity(boundClass);
+		if (entity == null) {
+			return;
+		}
+
+		Object dynamicTemplates = getMapping().get("dynamic_templates");
+		if (dynamicTemplates instanceof List<?> value) {
+			entity.buildDynamicTemplates(value);
+		}
+	}
+
 	@Override
 	public Settings createSettings() {
 		return createSettings(checkForBoundClass());
diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java
index d4c7a6e1b..2bb2e8583 100644
--- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java
+++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java
@@ -25,6 +25,7 @@
 import reactor.core.publisher.Mono;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -142,7 +143,12 @@ private Mono<Boolean> doCreate(IndexCoordinates indexCoordinates, Map<String, Ob
 
 		CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
 		Mono<CreateIndexResponse> createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest)));
-		return createIndexResponse.map(CreateIndexResponse::acknowledged);
+		return createIndexResponse
+				.doOnNext((result) -> {
+					// refresh cached mappings
+					refreshMapping();
+				})
+				.map(CreateIndexResponse::acknowledged);
 	}
 
 	@Override
@@ -218,6 +224,30 @@ public Mono<Document> getMapping() {
 		return getMappingResponse.map(response -> responseConverter.indicesGetMapping(response, indexCoordinates));
 	}
 
+	/**
+	 * Refreshes the mapping for the current entity.
+	 * <p>
+	 * This method is responsible for retrieving and updating the metadata related to the current entity.
+	 */
+	private void refreshMapping() {
+		if (boundClass == null) {
+			return;
+		}
+
+		ElasticsearchPersistentEntity<?> entity = this.elasticsearchConverter.getMappingContext()
+				.getPersistentEntity(boundClass);
+		if (entity == null) {
+			return;
+		}
+
+		getMapping().subscribe((mappings) -> {
+			Object dynamicTemplates = mappings.get("dynamic_templates");
+			if (dynamicTemplates instanceof List<?> value) {
+				entity.buildDynamicTemplates(value);
+			}
+		});
+	}
+
 	@Override
 	public Mono<Settings> createSettings() {
 		return createSettings(checkForBoundClass());
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java
index ccac971fd..d59069e6f 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java
@@ -15,6 +15,8 @@
  */
 package org.springframework.data.elasticsearch.core.convert;
 
+import static org.springframework.util.PatternMatchUtils.simpleMatch;
+
 import java.time.temporal.TemporalAccessor;
 import java.util.*;
 import java.util.Map.Entry;
@@ -42,6 +44,7 @@
 import org.springframework.data.elasticsearch.annotations.ScriptedField;
 import org.springframework.data.elasticsearch.core.document.Document;
 import org.springframework.data.elasticsearch.core.document.SearchDocument;
+import org.springframework.data.elasticsearch.core.mapping.DynamicTemplate;
 import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
 import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
 import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
@@ -388,6 +391,10 @@ private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Ob
 							targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
 						}
 					}
+
+					if (targetEntity.hasDynamicTemplates()) {
+						populateFieldsUsingDynamicTemplates(targetEntity, result, document);
+					}
 				}
 
 				if (source instanceof SearchDocument searchDocument) {
@@ -664,6 +671,28 @@ private <T> void populateScriptFields(ElasticsearchPersistentEntity<?> entity, T
 			});
 		}
 
+		private <R> void populateFieldsUsingDynamicTemplates(ElasticsearchPersistentEntity<?> targetEntity, R result,
+				Document document) {
+			for (Entry<String, DynamicTemplate> templateEntry : targetEntity.getDynamicTemplates().entrySet()) {
+				ElasticsearchPersistentProperty property = targetEntity
+						.getPersistentPropertyWithFieldName(templateEntry.getKey());
+				if (property != null && property.isDynamicFieldMapping()) {
+					// prepare value
+					Map<String, Object> values = new HashMap<>();
+					// TODO: Path match and unmatched
+					document.entrySet().stream()
+							.filter(fieldKey -> templateEntry.getValue().getMatch().stream()
+									.anyMatch(regex -> simpleMatch(regex, fieldKey.getKey()))
+									&& templateEntry.getValue().getUnmatch().stream()
+											.noneMatch(regex -> simpleMatch(regex, fieldKey.getKey())))
+							.forEach(entry -> values.put(entry.getKey(), entry.getValue()));
+
+					// set property
+					targetEntity.getPropertyAccessor(result).setProperty(property, read(property.getType(), Document.from(values)));
+				}
+			}
+		}
+
 		/**
 		 * Compute the type to use by checking the given entity against the store type;
 		 */
@@ -1035,7 +1064,14 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va
 
 			if (valueType.isMap()) {
 				Map<String, Object> mapDbObj = createMap((Map<?, ?>) value, property);
-				sink.set(property, mapDbObj);
+				if (property.isDynamicFieldMapping()) {
+					for (Entry<String, Object> entry : mapDbObj.entrySet()) {
+						sink.set(entry.getKey(), entry.getValue());
+					}
+				} else {
+					sink.set(property, mapDbObj);
+				}
+				
 				return;
 			}
 
@@ -1058,7 +1094,14 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va
 
 			addCustomTypeKeyIfNecessary(value, document, TypeInformation.of(property.getRawType()));
 			writeInternal(value, document, entity);
-			sink.set(property, document);
+			if (property.isDynamicFieldMapping()) {
+				// flatten
+				for (Entry<String, Object> entry : document.entrySet()) {
+					sink.set(entry.getKey(), entry.getValue());
+				}
+			} else {
+				sink.set(property, document);
+			}
 		}
 
 		/**
@@ -1499,7 +1542,11 @@ public void set(ElasticsearchPersistentProperty property, @Nullable Object value
 				}
 			}
 
-			target.put(property.getFieldName(), value);
+			set(property.getFieldName(), value);
+		}
+
+		public void set(String key, @Nullable Object value) {
+			target.put(key, value);
 		}
 
 		private Map<String, Object> getAsMap(Object result) {
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/DynamicTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/DynamicTemplate.java
new file mode 100644
index 000000000..0ee59fc72
--- /dev/null
+++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/DynamicTemplate.java
@@ -0,0 +1,190 @@
+package org.springframework.data.elasticsearch.core.mapping;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Immutable Value object encapsulating dynamic template(s).
+ * {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html">Elastic
+ * docs</a>}
+ *
+ * @author Youssef Aouichaoui
+ * @since 5.4
+ */
+public class DynamicTemplate {
+	/**
+	 * Patterns to match on the field name.
+	 */
+	private final List<String> match;
+
+	/**
+	 * Path patterns for a nested type to match the field name.
+	 */
+	private final List<String> pathMatch;
+
+	/**
+	 * Patterns that do not match the field name.
+	 */
+	private final List<String> unmatch;
+
+	/**
+	 * Path patterns for a nested type that do not match the field name.
+	 */
+	private final List<String> pathUnmatch;
+
+	/**
+	 * Data types that correspond to the field.
+	 */
+	private final List<String> matchMappingType;
+
+	/**
+	 * Data types that do not match to the field.
+	 */
+	private final List<String> unmatchMappingType;
+
+	private DynamicTemplate(Builder builder) {
+		this.match = builder.match;
+		this.pathMatch = builder.pathMatch;
+
+		this.unmatch = builder.unmatch;
+		this.pathUnmatch = builder.pathUnmatch;
+
+		this.matchMappingType = builder.matchMappingType;
+		this.unmatchMappingType = builder.unmatchMappingType;
+	}
+
+	public List<String> getMatch() {
+		return match;
+	}
+
+	public List<String> getPathMatch() {
+		return pathMatch;
+	}
+
+	public List<String> getUnmatch() {
+		return unmatch;
+	}
+
+	public List<String> getPathUnmatch() {
+		return pathUnmatch;
+	}
+
+	public List<String> getMatchMappingType() {
+		return matchMappingType;
+	}
+
+	public List<String> getUnmatchMappingType() {
+		return unmatchMappingType;
+	}
+
+	public boolean isRegexMatching() {
+		return false;
+	}
+
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	public static class Builder {
+		private final List<String> match = new ArrayList<>();
+		private final List<String> pathMatch = new ArrayList<>();
+
+		private final List<String> unmatch = new ArrayList<>();
+		private final List<String> pathUnmatch = new ArrayList<>();
+
+		private final List<String> matchMappingType = new ArrayList<>();
+		private final List<String> unmatchMappingType = new ArrayList<>();
+
+		private Builder() {}
+
+		/**
+		 * Patterns to match on the field name.
+		 */
+		public Builder withMatch(String... match) {
+			for (String value : match) {
+				if (value != null) {
+					parseValues(value, this.match);
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Path patterns for a nested type to match the field name.
+		 */
+		public Builder withPathMatch(String... pathMatch) {
+			for (String value : pathMatch) {
+				if (value != null) {
+					parseValues(value, this.pathMatch);
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Patterns that do not match the field name.
+		 */
+		public Builder withUnmatch(String... unmatch) {
+			for (String value : unmatch) {
+				if (value != null) {
+					parseValues(value, this.unmatch);
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Path patterns for a nested type that do not match the field name.
+		 */
+		public Builder withPathUnmatch(String... pathUnmatch) {
+			for (String value : pathUnmatch) {
+				if (value != null) {
+					parseValues(value, this.pathUnmatch);
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Data types that correspond to the field.
+		 */
+		public Builder withMatchMappingType(String... matchMappingType) {
+			for (String value : matchMappingType) {
+				if (value != null) {
+					parseValues(value, this.matchMappingType);
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Data types that do not match to the field.
+		 */
+		public Builder withUnmatchMappingType(String... unmatchMappingType) {
+			for (String value : unmatchMappingType) {
+				if (value != null) {
+					parseValues(value, this.unmatchMappingType);
+				}
+			}
+
+			return this;
+		}
+
+		private void parseValues(String source, List<String> target) {
+			if (source.startsWith("[")) {
+				target.addAll(List.of(source.replace("[", "").replace("]", "").split(",", -1)));
+			} else {
+				target.add(source);
+			}
+		}
+
+		public DynamicTemplate build() {
+			return new DynamicTemplate(this);
+		}
+	}
+}
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java
index 252376bed..bea0e8f39 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java
@@ -15,6 +15,8 @@
  */
 package org.springframework.data.elasticsearch.core.mapping;
 
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.springframework.data.elasticsearch.annotations.Document;
@@ -203,4 +205,25 @@ default ElasticsearchPersistentProperty getRequiredSeqNoPrimaryTermProperty() {
 	 * @since 5.2
 	 */
 	boolean isAlwaysWriteMapping();
+
+	/**
+	 * Retrieves the dynamic templates defined for the current document.
+	 *
+	 * @since 5.4
+	 */
+	Map<String, DynamicTemplate> getDynamicTemplates();
+
+	/**
+	 * if the mapping should be written to the index on repository bootstrap even if the index already exists.
+	 *
+	 * @since 5.4
+	 */
+	boolean hasDynamicTemplates();
+
+	/**
+	 * Building the dynamic templates for the current document.
+	 *
+	 * @since 5.4
+	 */
+	void buildDynamicTemplates(List<?> dynamicTemplates);
 }
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java
index 2b9d6232e..7575b8864 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java
@@ -110,6 +110,13 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
 	 */
 	boolean isIndexedIndexNameProperty();
 
+	/**
+	 * Maps your data beyond the dynamic field mapping rules.
+	 *
+	 * @since 5.4
+	 */
+	boolean isDynamicFieldMapping();
+
 	/**
 	 * calls {@link #getActualType()} but returns null when an exception is thrown
 	 *
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java
index fb2a12412..380e263fb 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java
@@ -15,7 +15,10 @@
  */
 package org.springframework.data.elasticsearch.core.mapping;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -51,6 +54,8 @@
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
 
+import static java.util.Collections.unmodifiableMap;
+
 /**
  * Elasticsearch specific {@link org.springframework.data.mapping.PersistentEntity} implementation holding
  *
@@ -85,6 +90,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
 	private @Nullable String routing;
 	private final ContextConfiguration contextConfiguration;
 	private final Set<Alias> aliases = new HashSet<>();
+	private final Map<String, DynamicTemplate> dynamicTemplates = new HashMap<>();
 
 	private final ConcurrentHashMap<String, Expression> indexNameExpressions = new ConcurrentHashMap<>();
 	private final Lazy<EvaluationContext> indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext);
@@ -627,6 +633,48 @@ public Dynamic dynamic() {
 		return dynamic;
 	}
 
+	@Override
+	public Map<String, DynamicTemplate> getDynamicTemplates() {
+		return unmodifiableMap(dynamicTemplates);
+	}
+
+	@Override
+	public boolean hasDynamicTemplates() {
+		return !dynamicTemplates.isEmpty();
+	}
+
+	@Override
+	public void buildDynamicTemplates(List<?> dynamicTemplates) {
+		for (Object dynamicTemplate : dynamicTemplates) {
+			if (dynamicTemplate instanceof Map<?, ?> template) {
+				template.forEach((name, value) -> {
+					if (value instanceof Map<?, ?> settings) {
+						this.dynamicTemplates.put((String) name,
+								DynamicTemplate.builder()
+										.withMatch(parseMapValue(settings.get("match")))
+										.withPathMatch(parseMapValue(settings.get("path_match")))
+										.withUnmatch(parseMapValue(settings.get("unmatch")))
+										.withPathUnmatch(parseMapValue(settings.get("path_unmatch")))
+										.withMatchMappingType(parseMapValue(settings.get("match_mapping_type")))
+										.withUnmatchMappingType(parseMapValue(settings.get("unmatch_mapping_type")))
+										.build());
+					}
+				});
+			}
+		}
+	}
+
+	/**
+	 * Parses the provided value and converts it into an array of Strings.
+	 */
+	private String[] parseMapValue(@Nullable Object value) {
+		if (value instanceof List<?> values) {
+			return values.toArray(new String[0]);
+		}
+
+		return new String[0];
+	}
+
 	/**
 	 * Building once the aliases for the current document.
 	 */
diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java
index 8b32842b9..9abd71a82 100644
--- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java
+++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java
@@ -86,6 +86,7 @@ public class SimpleElasticsearchPersistentProperty extends
 	@Nullable private PropertyValueConverter propertyValueConverter;
 	private final boolean storeNullValue;
 	private final boolean storeEmptyValue;
+	private final boolean isDynamicFieldMapping;
 
 	public SimpleElasticsearchPersistentProperty(Property property,
 			PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
@@ -114,6 +115,7 @@ public SimpleElasticsearchPersistentProperty(Property property,
 				: isMultiField && getRequiredAnnotation(MultiField.class).mainField().storeNullValue();
 		storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue()
 				: !isMultiField || getRequiredAnnotation(MultiField.class).mainField().storeEmptyValue();
+		isDynamicFieldMapping = isField && getRequiredAnnotation(Field.class).dynamicTemplate();
 	}
 
 	@Override
@@ -393,4 +395,9 @@ public boolean isCompletionProperty() {
 	public boolean isIndexedIndexNameProperty() {
 		return isAnnotationPresent(IndexedIndexName.class);
 	}
+
+	@Override
+	public boolean isDynamicFieldMapping() {
+		return isDynamicFieldMapping;
+	}
 }
diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java
index b4aaab0e9..dffd5b514 100644
--- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java
@@ -16,19 +16,14 @@
 
 package org.springframework.data.elasticsearch.core.index;
 
+import static java.util.UUID.randomUUID;
 import static org.assertj.core.api.Assertions.*;
 import static org.springframework.data.elasticsearch.annotations.FieldType.*;
+import static org.springframework.data.elasticsearch.core.query.StringQuery.MATCH_ALL;
 
 import java.time.Instant;
 import java.time.LocalDate;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -40,7 +35,10 @@
 import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
 import org.springframework.data.elasticsearch.core.IndexOperations;
 import org.springframework.data.elasticsearch.core.MappingContextBaseTests;
+import org.springframework.data.elasticsearch.core.SearchHit;
+import org.springframework.data.elasticsearch.core.SearchHits;
 import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
+import org.springframework.data.elasticsearch.core.query.StringQuery;
 import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
 import org.springframework.data.elasticsearch.utils.IndexNameProvider;
 import org.springframework.lang.Nullable;
@@ -280,6 +278,35 @@ void shouldWriteMappingWithFieldAliases() {
 		operations.indexOps(FieldAliasEntity.class).createWithMapping();
 	}
 
+	@Test
+	void shouldMapDynamicFields() {
+		// Given
+		IndexOperations documentOperations = operations.indexOps(DynamicFieldDocument.class);
+		documentOperations.createWithMapping();
+
+		DynamicFieldDocument document = new DynamicFieldDocument();
+		document.dynamicFields = Map.of("a_str", randomUUID().toString(), "b_str", randomUUID().toString());
+		document.value = new DynamicFieldDocument.Value(1L, new Date());
+		operations.save(document);
+
+		// When
+		SearchHits<DynamicFieldDocument> results = operations.search(new StringQuery(MATCH_ALL),
+				DynamicFieldDocument.class);
+
+		// Then
+		assertThat(results.getTotalHits()).isEqualTo(1);
+		assertThat(results.getSearchHits()).first()
+				.extracting(SearchHit::getContent)
+				.extracting(doc -> doc.dynamicFields)
+				.isEqualTo(document.dynamicFields);
+		assertThat(results.getSearchHits()).first()
+				.extracting(SearchHit::getContent)
+				.extracting(doc -> doc.value)
+				.isEqualTo(document.value);
+
+		documentOperations.delete();
+	}
+
 	// region Entities
 	@Document(indexName = "#{@indexNameProvider.indexName()}")
 	static class Book {
@@ -933,5 +960,47 @@ private static class FieldAliasEntity {
 		@Field(type = Text) private String otherText;
 	}
 
+	@SuppressWarnings("unused")
+	@Document(indexName = "#{@indexNameProvider.indexName()}-foo")
+	@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings_three.json")
+	private static class DynamicFieldDocument {
+		@Nullable
+		@Id String id;
+
+		@Field(name = "_str", dynamicTemplate = true) private Map<String, String> dynamicFields = new HashMap<>();
+
+		@Nullable
+		@Field(name = "obj", dynamicTemplate = true) private Value value;
+
+		static class Value {
+			@Nullable
+			@Field(name = "value_sum", type = FieldType.Long)
+			private Long sum;
+
+			@Nullable
+			@Field(name = "value_date", type = FieldType.Long)
+			private Date date;
+
+			public Value() {
+			}
+
+			public Value(Long sum, Date date) {
+				this.sum = sum;
+				this.date = date;
+			}
+
+			@Override
+			public boolean equals(Object o) {
+				if (this == o) return true;
+				if (!(o instanceof Value value)) return false;
+                return Objects.equals(sum, value.sum) && Objects.equals(date, value.date);
+			}
+
+			@Override
+			public int hashCode() {
+				return Objects.hash(sum, date);
+			}
+		}
+	}
 	// endregion
 }
diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexTemplateIntegrationTests.java
index e64ce7e10..adbad3223 100644
--- a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexTemplateIntegrationTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexTemplateIntegrationTests.java
@@ -18,12 +18,12 @@
 import static org.assertj.core.api.Assertions.*;
 import static org.skyscreamer.jsonassert.JSONAssert.*;
 import static org.springframework.data.elasticsearch.core.IndexOperationsAdapter.*;
+import static org.springframework.data.elasticsearch.core.query.StringQuery.MATCH_ALL;
 
+import reactor.core.publisher.Flux;
 import reactor.test.StepVerifier;
 
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
 
 import org.assertj.core.api.SoftAssertions;
 import org.json.JSONException;
@@ -34,16 +34,20 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.elasticsearch.annotations.Document;
+import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
 import org.springframework.data.elasticsearch.annotations.Field;
 import org.springframework.data.elasticsearch.annotations.FieldType;
 import org.springframework.data.elasticsearch.annotations.Setting;
 import org.springframework.data.elasticsearch.core.IndexOperations;
+import org.springframework.data.elasticsearch.core.IndexOperationsAdapter;
 import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
 import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
+import org.springframework.data.elasticsearch.core.SearchHit;
 import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
 import org.springframework.data.elasticsearch.core.query.Criteria;
 import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
 import org.springframework.data.elasticsearch.core.query.Query;
+import org.springframework.data.elasticsearch.core.query.StringQuery;
 import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
 import org.springframework.data.elasticsearch.utils.IndexNameProvider;
 import org.springframework.lang.Nullable;
@@ -415,6 +419,40 @@ void shouldDeleteTemplate() {
 		assertThat(exists).isFalse();
 	}
 
+	@Test
+	void shouldMapDynamicFields() {
+		// Given
+		IndexOperationsAdapter documentOperations = blocking(operations.indexOps(DynamicFieldDocument.class));
+		documentOperations.createWithMapping();
+
+		DynamicFieldDocument document = new DynamicFieldDocument();
+		document.dynamicFields = Map.of("a_str", UUID.randomUUID().toString(), "b_str", UUID.randomUUID().toString());
+		document.value = new DynamicFieldDocument.Value(1L, new Date());
+		operations.save(document).block();
+
+		// When
+		Flux<SearchHit<DynamicFieldDocument>> results = operations.search(new StringQuery(MATCH_ALL),
+				DynamicFieldDocument.class);
+
+		// Then
+		results.as(StepVerifier::create)
+				.expectNextMatches((hits) -> {
+					assertThat(hits)
+							.extracting(SearchHit::getContent)
+							.extracting(doc -> doc.dynamicFields)
+							.isEqualTo(document.dynamicFields);
+
+					assertThat(hits)
+							.extracting(SearchHit::getContent)
+							.extracting(doc -> doc.value)
+							.isEqualTo(document.value);
+
+					return true;
+				}).verifyComplete();
+
+		documentOperations.delete();
+	}
+
 	@Document(indexName = "#{@indexNameProvider.indexName()}")
 	@Setting(refreshInterval = "5s")
 	static class TemplateClass {
@@ -442,4 +480,46 @@ public void setMessage(@Nullable String message) {
 		}
 	}
 
+	@SuppressWarnings("unused")
+	@Document(indexName = "#{@indexNameProvider.indexName()}-foo")
+	@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings_three.json")
+	private static class DynamicFieldDocument {
+		@Nullable
+		@Id String id;
+
+		@Field(name = "_str", dynamicTemplate = true) private Map<String, String> dynamicFields = new HashMap<>();
+
+		@Nullable
+		@Field(name = "obj", dynamicTemplate = true) private Value value;
+
+		static class Value {
+			@Nullable
+			@Field(name = "value_sum", type = FieldType.Long)
+			private Long sum;
+
+			@Nullable
+			@Field(name = "value_date", type = FieldType.Long)
+			private Date date;
+
+			public Value() {
+			}
+
+			public Value(Long sum, Date date) {
+				this.sum = sum;
+				this.date = date;
+			}
+
+			@Override
+			public boolean equals(Object o) {
+				if (this == o) return true;
+				if (!(o instanceof Value value)) return false;
+                return Objects.equals(sum, value.sum) && Objects.equals(date, value.date);
+			}
+
+			@Override
+			public int hashCode() {
+				return Objects.hash(sum, date);
+			}
+		}
+	}
 }
diff --git a/src/test/resources/mappings/test-dynamic_templates_mappings_three.json b/src/test/resources/mappings/test-dynamic_templates_mappings_three.json
new file mode 100644
index 000000000..01bf78398
--- /dev/null
+++ b/src/test/resources/mappings/test-dynamic_templates_mappings_three.json
@@ -0,0 +1,21 @@
+{
+  "dynamic_templates": [
+    {
+      "_str": {
+        "match": "*_str",
+        "mapping": {
+          "type": "keyword"
+        }
+      }
+    },
+    {
+      "obj": {
+        "match": "value_*",
+        "mapping": {
+          "type": "text",
+          "index": false
+        }
+      }
+    }
+  ]
+}