diff --git a/pom.xml b/pom.xml
index 5225d1e3df..9074164110 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 
 	<groupId>org.springframework.data</groupId>
 	<artifactId>spring-data-mongodb-parent</artifactId>
-	<version>4.2.0-SNAPSHOT</version>
+	<version>4.2.x-4379-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>Spring Data MongoDB</name>
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index 2de4b6b635..1e0a519d25 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>4.2.0-SNAPSHOT</version>
+		<version>4.2.x-4379-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 060a6d0dd9..fb47df3eb8 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -15,7 +15,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>4.2.0-SNAPSHOT</version>
+		<version>4.2.x-4379-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 921254ca44..b99b0b3502 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>4.2.0-SNAPSHOT</version>
+		<version>4.2.x-4379-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java
index 3fdc1b125c..0ceb1c84d7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java
@@ -23,6 +23,7 @@
 import org.bson.codecs.configuration.CodecRegistry;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.mongodb.CodecRegistryProvider;
+import org.springframework.data.mongodb.MongoCollectionUtils;
 import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
@@ -79,7 +80,30 @@ default Document getMappedObject(Document document) {
 	FieldReference getReference(String name);
 
 	/**
-	 * Returns the {@link Fields} exposed by the type. May be a {@literal class} or an {@literal interface}. The default
+	 * Obtain the target field name for a given field/type combination.
+	 *
+	 * @param type The type containing the field.
+	 * @param field The property/field name
+	 * @return never {@literal null}.
+	 * @since 4.2
+	 */
+	default String getMappedFieldName(Class<?> type, String field) {
+		return field;
+	}
+
+	/**
+	 * Obtain the collection name for a given {@link Class type} combination.
+	 *
+	 * @param type
+	 * @return never {@literal null}.
+	 * @since 4.2
+	 */
+	default String getCollection(Class<?> type) {
+		return MongoCollectionUtils.getPreferredCollectionName(type);
+	}
+
+	/**
+	 * Returns the {@link Fields} exposed by the type. Can be a {@literal class} or an {@literal interface}. The default
 	 * implementation uses {@link BeanUtils#getPropertyDescriptors(Class) property descriptors} discover fields from a
 	 * {@link Class}.
 	 *
@@ -109,7 +133,7 @@ default Fields getFields(Class<?> type) {
 
 	/**
 	 * This toggle allows the {@link AggregationOperationContext context} to use any given field name without checking for
-	 * its existence. Typically the {@link AggregationOperationContext} fails when referencing unknown fields, those that
+	 * its existence. Typically, the {@link AggregationOperationContext} fails when referencing unknown fields, those that
 	 * are not present in one of the previous stages or the input source, throughout the pipeline.
 	 *
 	 * @return a more relaxed {@link AggregationOperationContext}.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
index a2fe238627..02b3fb4ea8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
@@ -46,7 +46,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
 	private static final Set<Class<?>> ALLOWED_START_TYPES = new HashSet<Class<?>>(
 			Arrays.<Class<?>> asList(AggregationExpression.class, String.class, Field.class, Document.class));
 
-	private final String from;
+	private final Object from;
 	private final List<Object> startWith;
 	private final Field connectFrom;
 	private final Field connectTo;
@@ -55,7 +55,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
 	private final @Nullable Field depthField;
 	private final @Nullable CriteriaDefinition restrictSearchWithMatch;
 
-	private GraphLookupOperation(String from, List<Object> startWith, Field connectFrom, Field connectTo, Field as,
+	private GraphLookupOperation(Object from, List<Object> startWith, Field connectFrom, Field connectTo, Field as,
 			@Nullable Long maxDepth, @Nullable Field depthField, @Nullable CriteriaDefinition restrictSearchWithMatch) {
 
 		this.from = from;
@@ -82,7 +82,7 @@ public Document toDocument(AggregationOperationContext context) {
 
 		Document graphLookup = new Document();
 
-		graphLookup.put("from", from);
+		graphLookup.put("from", getCollectionName(context));
 
 		List<Object> mappedStartWith = new ArrayList<>(startWith.size());
 
@@ -99,7 +99,7 @@ public Document toDocument(AggregationOperationContext context) {
 
 		graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
 
-		graphLookup.put("connectFromField", connectFrom.getTarget());
+		graphLookup.put("connectFromField", getForeignFieldName(context));
 		graphLookup.put("connectToField", connectTo.getTarget());
 		graphLookup.put("as", as.getName());
 
@@ -118,6 +118,16 @@ public Document toDocument(AggregationOperationContext context) {
 		return new Document(getOperator(), graphLookup);
 	}
 
+	String getCollectionName(AggregationOperationContext context) {
+		return from instanceof Class<?> type ? context.getCollection(type) : from.toString();
+	}
+
+	String getForeignFieldName(AggregationOperationContext context) {
+
+		return from instanceof Class<?> type ? context.getMappedFieldName(type, connectFrom.getTarget())
+				: connectFrom.getTarget();
+	}
+
 	@Override
 	public String getOperator() {
 		return "$graphLookup";
@@ -128,7 +138,7 @@ public ExposedFields getFields() {
 
 		List<ExposedField> fields = new ArrayList<>(2);
 		fields.add(new ExposedField(as, true));
-		if(depthField != null) {
+		if (depthField != null) {
 			fields.add(new ExposedField(depthField, true));
 		}
 		return ExposedFields.from(fields.toArray(new ExposedField[0]));
@@ -146,6 +156,17 @@ public interface FromBuilder {
 		 * @return never {@literal null}.
 		 */
 		StartWithBuilder from(String collectionName);
+
+		/**
+		 * Use the given type to determine name of the foreign collection and map
+		 * {@link ConnectFromBuilder#connectFrom(String)} against it to consider eventually present
+		 * {@link org.springframework.data.mongodb.core.mapping.Field} annotations.
+		 *
+		 * @param type must not be {@literal null}.
+		 * @return never {@literal null}.
+		 * @since 4.2
+		 */
+		StartWithBuilder from(Class<?> type);
 	}
 
 	/**
@@ -218,7 +239,7 @@ public interface ConnectToBuilder {
 	static final class GraphLookupOperationFromBuilder
 			implements FromBuilder, StartWithBuilder, ConnectFromBuilder, ConnectToBuilder {
 
-		private @Nullable String from;
+		private @Nullable Object from;
 		private @Nullable List<? extends Object> startWith;
 		private @Nullable String connectFrom;
 
@@ -231,6 +252,14 @@ public StartWithBuilder from(String collectionName) {
 			return this;
 		}
 
+		@Override
+		public StartWithBuilder from(Class<?> type) {
+
+			Assert.notNull(type, "Type must not be null");
+			this.from = type;
+			return this;
+		}
+
 		@Override
 		public ConnectFromBuilder startWith(String... fieldReferences) {
 
@@ -321,7 +350,7 @@ public GraphLookupOperationBuilder connectTo(String fieldName) {
 	 */
 	public static final class GraphLookupOperationBuilder {
 
-		private final String from;
+		private final Object from;
 		private final List<Object> startWith;
 		private final Field connectFrom;
 		private final Field connectTo;
@@ -329,7 +358,7 @@ public static final class GraphLookupOperationBuilder {
 		private @Nullable Field depthField;
 		private @Nullable CriteriaDefinition restrictSearchWithMatch;
 
-		protected GraphLookupOperationBuilder(String from, List<? extends Object> startWith, String connectFrom,
+		protected GraphLookupOperationBuilder(Object from, List<? extends Object> startWith, String connectFrom,
 				String connectTo) {
 
 			this.from = from;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java
index 44d0f1569e..3cfda35cef 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java
@@ -39,7 +39,7 @@
  */
 public class LookupOperation implements FieldsExposingAggregationOperation, InheritsFieldsAggregationOperation {
 
-	private final String from;
+	private Object from;
 
 	@Nullable //
 	private final Field localField;
@@ -97,6 +97,22 @@ public LookupOperation(String from, @Nullable Let let, AggregationPipeline pipel
 	 */
 	public LookupOperation(String from, @Nullable Field localField, @Nullable Field foreignField, @Nullable Let let,
 			@Nullable AggregationPipeline pipeline, Field as) {
+		this((Object) from, localField, foreignField, let, pipeline, as);
+	}
+
+	/**
+	 * Creates a new {@link LookupOperation} for the given combination of {@link Field}s and {@link AggregationPipeline
+	 * pipeline}.
+	 *
+	 * @param from must not be {@literal null}. Can be eiter the target collection name or a {@link Class}.
+	 * @param localField can be {@literal null} if {@literal pipeline} is present.
+	 * @param foreignField can be {@literal null} if {@literal pipeline} is present.
+	 * @param let can be {@literal null} if {@literal localField} and {@literal foreignField} are present.
+	 * @param as must not be {@literal null}.
+	 * @since 4.2
+	 */
+	private LookupOperation(Object from, @Nullable Field localField, @Nullable Field foreignField, @Nullable Let let,
+			@Nullable AggregationPipeline pipeline, Field as) {
 
 		Assert.notNull(from, "From must not be null");
 		if (pipeline == null) {
@@ -125,12 +141,14 @@ public Document toDocument(AggregationOperationContext context) {
 
 		Document lookupObject = new Document();
 
-		lookupObject.append("from", from);
+		lookupObject.append("from", getCollectionName(context));
+
 		if (localField != null) {
 			lookupObject.append("localField", localField.getTarget());
 		}
+
 		if (foreignField != null) {
-			lookupObject.append("foreignField", foreignField.getTarget());
+			lookupObject.append("foreignField", getForeignFieldName(context));
 		}
 		if (let != null) {
 			lookupObject.append("let", let.toDocument(context).get("$let", Document.class).get("vars"));
@@ -144,6 +162,16 @@ public Document toDocument(AggregationOperationContext context) {
 		return new Document(getOperator(), lookupObject);
 	}
 
+	String getCollectionName(AggregationOperationContext context) {
+		return from instanceof Class<?> type ? context.getCollection(type) : from.toString();
+	}
+
+	String getForeignFieldName(AggregationOperationContext context) {
+
+		return from instanceof Class<?> type ? context.getMappedFieldName(type, foreignField.getTarget())
+				: foreignField.getTarget();
+	}
+
 	@Override
 	public String getOperator() {
 		return "$lookup";
@@ -158,16 +186,28 @@ public static FromBuilder newLookup() {
 		return new LookupOperationBuilder();
 	}
 
-	public static interface FromBuilder {
+	public interface FromBuilder {
 
 		/**
 		 * @param name the collection in the same database to perform the join with, must not be {@literal null} or empty.
 		 * @return never {@literal null}.
 		 */
 		LocalFieldBuilder from(String name);
+
+		/**
+		 * Use the given type to determine name of the foreign collection and map
+		 * {@link ForeignFieldBuilder#foreignField(String)} against it to consider eventually present
+		 * {@link org.springframework.data.mongodb.core.mapping.Field} annotations.
+		 *
+		 * @param type the type of the target collection in the same database to perform the join with, must not be
+		 *          {@literal null}.
+		 * @return never {@literal null}.
+		 * @since 4.2
+		 */
+		LocalFieldBuilder from(Class<?> type);
 	}
 
-	public static interface LocalFieldBuilder extends PipelineBuilder {
+	public interface LocalFieldBuilder extends PipelineBuilder {
 
 		/**
 		 * @param name the field from the documents input to the {@code $lookup} stage, must not be {@literal null} or
@@ -177,7 +217,7 @@ public static interface LocalFieldBuilder extends PipelineBuilder {
 		ForeignFieldBuilder localField(String name);
 	}
 
-	public static interface ForeignFieldBuilder {
+	public interface ForeignFieldBuilder {
 
 		/**
 		 * @param name the field from the documents in the {@code from} collection, must not be {@literal null} or empty.
@@ -246,7 +286,7 @@ default AsBuilder pipeline(AggregationOperation... stages) {
 		LookupOperation as(String name);
 	}
 
-	public static interface AsBuilder extends PipelineBuilder {
+	public interface AsBuilder extends PipelineBuilder {
 
 		/**
 		 * @param name the name of the new array field to add to the input documents, must not be {@literal null} or empty.
@@ -264,7 +304,7 @@ public static interface AsBuilder extends PipelineBuilder {
 	public static final class LookupOperationBuilder
 			implements FromBuilder, LocalFieldBuilder, ForeignFieldBuilder, AsBuilder {
 
-		private @Nullable String from;
+		private @Nullable Object from;
 		private @Nullable Field localField;
 		private @Nullable Field foreignField;
 		private @Nullable ExposedField as;
@@ -288,6 +328,14 @@ public LocalFieldBuilder from(String name) {
 			return this;
 		}
 
+		@Override
+		public LocalFieldBuilder from(Class<?> type) {
+
+			Assert.notNull(type, "'From' must not be null");
+			from = type;
+			return this;
+		}
+
 		@Override
 		public AsBuilder foreignField(String name) {
 
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java
index 4d0ee5ff6c..27b1ab6737 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java
@@ -30,9 +30,8 @@
 
 /**
  * {@link AggregationOperationContext} implementation prefixing non-command keys on root level with the given prefix.
- * Useful when mapping fields to domain specific types while having to prefix keys for query purpose.
- * <br />
- * Fields to be excluded from prefixing my be added to a {@literal denylist}.
+ * Useful when mapping fields to domain specific types while having to prefix keys for query purpose. <br />
+ * Fields to be excluded from prefixing can be added to a {@literal denylist}.
  *
  * @author Christoph Strobl
  * @author Mark Paluch
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
index fd54514cbb..efd135d328 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
@@ -92,6 +92,20 @@ public FieldReference getReference(String name) {
 		return getReferenceFor(field(name));
 	}
 
+	@Override
+	public String getCollection(Class<?> type) {
+
+		MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
+		return persistentEntity != null ? persistentEntity.getCollection() : AggregationOperationContext.super.getCollection(type);
+	}
+
+	@Override
+	public String getMappedFieldName(Class<?> type, String field) {
+
+		PersistentPropertyPath<MongoPersistentProperty> persistentPropertyPath = mappingContext.getPersistentPropertyPath(field, type);
+		return persistentPropertyPath.getLeafProperty().getFieldName();
+	}
+
 	@Override
 	public Fields getFields(Class<?> type) {
 
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTestUtils.java
new file mode 100644
index 0000000000..35e45915f5
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTestUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 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.mongodb.core.aggregation;
+
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
+import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.mongodb.test.util.MongoTestMappingContext;
+
+/**
+ * @author Christoph Strobl
+ */
+public final class AggregationTestUtils {
+
+	public static AggregationContextBuilder<TypeBasedAggregationOperationContext> strict(Class<?> type) {
+
+		AggregationContextBuilder<AggregationOperationContext> builder = new AggregationContextBuilder<>();
+		builder.strict = true;
+		return builder.forType(type);
+	}
+
+	public static AggregationContextBuilder<TypeBasedAggregationOperationContext> relaxed(Class<?> type) {
+
+		AggregationContextBuilder<AggregationOperationContext> builder = new AggregationContextBuilder<>();
+		builder.strict = false;
+		return builder.forType(type);
+	}
+
+	public static class AggregationContextBuilder<T extends AggregationOperationContext> {
+
+		Class<?> targetType;
+		MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
+		QueryMapper queryMapper;
+		boolean strict;
+
+		public AggregationContextBuilder<TypeBasedAggregationOperationContext> forType(Class<?> type) {
+
+			this.targetType = type;
+			return (AggregationContextBuilder<TypeBasedAggregationOperationContext>) this;
+		}
+
+		public AggregationContextBuilder<T> using(
+				MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
+
+			this.mappingContext = mappingContext;
+			return this;
+		}
+
+		public AggregationContextBuilder<T> using(QueryMapper queryMapper) {
+
+			this.queryMapper = queryMapper;
+			return this;
+		}
+
+		public T ctx() {
+			//
+			if (targetType == null) {
+				return (T) Aggregation.DEFAULT_CONTEXT;
+			}
+
+			MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> ctx = mappingContext != null
+					? mappingContext
+					: MongoTestMappingContext.newTestContext().init();
+			QueryMapper qm = queryMapper != null ? queryMapper
+					: new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, ctx));
+			return (T) (strict ? new TypeBasedAggregationOperationContext(targetType, ctx, qm)
+					: new RelaxedTypeBasedAggregationOperationContext(targetType, ctx, qm));
+		}
+	}
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java
index 5280b603f6..6d85b31ffb 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java
@@ -22,6 +22,7 @@
 import org.bson.Document;
 import org.junit.jupiter.api.Test;
 import org.springframework.data.mongodb.core.Person;
+import org.springframework.data.mongodb.core.mapping.Field;
 import org.springframework.data.mongodb.core.query.Criteria;
 
 /**
@@ -34,7 +35,7 @@ public class GraphLookupOperationUnitTests {
 
 	@Test // DATAMONGO-1551
 	public void rejectsNullFromCollection() {
-		assertThatIllegalArgumentException().isThrownBy(() -> GraphLookupOperation.builder().from(null));
+		assertThatIllegalArgumentException().isThrownBy(() -> GraphLookupOperation.builder().from((String) null));
 	}
 
 	@Test // DATAMONGO-1551
@@ -158,4 +159,59 @@ public void depthFieldShouldUseTargetFieldInsteadOfAlias() {
 
 		assertThat(document).containsEntry("$graphLookup.depthField", "foo.bar");
 	}
+
+	@Test // GH-4379
+	void unmappedLookupWithFromExtractedFromType() {
+
+		GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() //
+				.from(Employee.class) //
+				.startWith(LiteralOperators.Literal.asLiteral("hello")) //
+				.connectFrom("manager") //
+				.connectTo("name") //
+				.as("reportingHierarchy");
+
+		assertThat(graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo("""
+				{ $graphLookup:
+					{
+						from: "employee",
+						startWith : { $literal : "hello" },
+						connectFromField: "manager",
+						connectToField: "name",
+						as: "reportingHierarchy"
+					}
+				}}
+				""");
+	}
+
+	@Test // GH-4379
+	void mappedLookupWithFromExtractedFromType() {
+
+		GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() //
+				.from(Employee.class) //
+				.startWith(LiteralOperators.Literal.asLiteral("hello")) //
+				.connectFrom("manager") //
+				.connectTo("name") //
+				.as("reportingHierarchy");
+
+		assertThat(graphLookupOperation.toDocument(AggregationTestUtils.strict(Employee.class).ctx())).isEqualTo("""
+				{ $graphLookup:
+					{
+						from: "employees",
+						startWith : { $literal : "hello" },
+						connectFromField: "reportsTo",
+						connectToField: "name",
+						as: "reportingHierarchy"
+					}
+				}}
+				""");
+	}
+
+	@org.springframework.data.mongodb.core.mapping.Document("employees")
+	static class Employee {
+
+		String id;
+
+		@Field("reportsTo")
+		String manager;
+	}
 }
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java
index 45b63763cf..1127ad6eac 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java
@@ -25,6 +25,7 @@
 import org.bson.Document;
 import org.junit.jupiter.api.Test;
 import org.springframework.data.mongodb.core.DocumentTestUtils;
+import org.springframework.data.mongodb.core.mapping.Field;
 import org.springframework.data.mongodb.core.query.Criteria;
 
 /**
@@ -92,7 +93,7 @@ private Document extractDocumentFromLookupOperation(LookupOperation lookupOperat
 
 	@Test // DATAMONGO-1326
 	public void builderRejectsNullFromField() {
-		assertThatIllegalArgumentException().isThrownBy(() -> LookupOperation.newLookup().from(null));
+		assertThatIllegalArgumentException().isThrownBy(() -> LookupOperation.newLookup().from((String) null));
 	}
 
 	@Test // DATAMONGO-1326
@@ -195,10 +196,10 @@ void buildsLookupWithJustPipeline() {
 	void buildsLookupWithLocalAndForeignFieldAsWellAsLetAndPipeline() {
 
 		LookupOperation lookupOperation = Aggregation.lookup().from("restaurants") //
-				.localField("restaurant_name")
-				.foreignField("name")
+				.localField("restaurant_name") //
+				.foreignField("name") //
 				.let(newVariable("orders_drink").forField("drink")) //
-				.pipeline(match(ctx -> new Document("$expr", new Document("$in", List.of("$$orders_drink", "$beverages")))))
+				.pipeline(match(ctx -> new Document("$expr", new Document("$in", List.of("$$orders_drink", "$beverages"))))) //
 				.as("matches");
 
 		assertThat(lookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo("""
@@ -216,4 +217,54 @@ void buildsLookupWithLocalAndForeignFieldAsWellAsLetAndPipeline() {
 				}}
 				""");
 	}
+
+	@Test // GH-4379
+	void unmappedLookupWithFromExtractedFromType() {
+
+		LookupOperation lookupOperation = Aggregation.lookup().from(Restaurant.class) //
+				.localField("restaurant_name") //
+				.foreignField("name") //
+				.as("restaurants");
+
+		assertThat(lookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo("""
+				{ $lookup:
+					{
+						from: "restaurant",
+						localField: "restaurant_name",
+						foreignField: "name",
+						as: "restaurants"
+					}
+				}}
+				""");
+	}
+
+	@Test // GH-4379
+	void mappedLookupWithFromExtractedFromType() {
+
+		LookupOperation lookupOperation = Aggregation.lookup().from(Restaurant.class) //
+				.localField("restaurant_name") //
+				.foreignField("name") //
+				.as("restaurants");
+
+
+		assertThat(lookupOperation.toDocument(AggregationTestUtils.strict(Restaurant.class).ctx())).isEqualTo("""
+				{ $lookup:
+					{
+						from: "sites",
+						localField: "restaurant_name",
+						foreignField: "rs_name",
+						as: "restaurants"
+					}
+				}}
+				""");
+	}
+
+	@org.springframework.data.mongodb.core.mapping.Document("sites")
+	static class Restaurant {
+
+		String id;
+
+		@Field("rs_name") //
+		String name;
+	}
 }