diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarder.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarder.java
index 2909ed3b5712195f45ae6aab8623b0db9998fd27..5fa1cc80c6f40e94cd5ebfcbe29cace8d6962564 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarder.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarder.java
@@ -26,6 +26,7 @@ import de.ozgcloud.vorgang.vorgang.IncomingFileGroup;
 import de.ozgcloud.vorgang.vorgang.IncomingFileMapper;
 import io.grpc.stub.ClientCallStreamObserver;
 import io.grpc.stub.ClientResponseObserver;
+import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
@@ -39,9 +40,13 @@ class EingangForwarder {
 	private ForwardingResponseObserver responseObserver;
 	private ClientCallStreamObserver<GrpcRouteForwardingRequest> requestObserver;
 
-	public CompletableFuture<Void> forward(GrpcRouteForwarding grpcRouteForwarding, List<IncomingFileGroup> attachments,
+	@Getter
+	private CompletableFuture<Void> forwardFuture;
+
+	public EingangForwarder forward(GrpcRouteForwarding grpcRouteForwarding, List<IncomingFileGroup> attachments,
 			List<IncomingFile> representations) {
-		return CompletableFuture.allOf(
+
+		forwardFuture = CompletableFuture.allOf(
 				callService(),
 				sendRouteForwarding(grpcRouteForwarding)
 						.thenCompose(ignored -> sendAttachments(attachments))
@@ -54,6 +59,7 @@ class EingangForwarder {
 							}
 						})
 		);
+		return this;
 	}
 
 	CompletableFuture<GrpcRouteForwardingResponse> callService() {
@@ -67,11 +73,15 @@ class EingangForwarder {
 
 	CompletableFuture<GrpcRouteForwardingResponse> sendRouteForwarding(GrpcRouteForwarding grpcRouteForwarding) {
 		CompletableFuture<GrpcRouteForwardingResponse> future = new CompletableFuture<>();
-		responseObserver.registerOnReadyHandler(() -> {
+		responseObserver.registerOnReadyHandler(getSendRouteForwardingRunnable(grpcRouteForwarding, future));
+		return future;
+	}
+
+	Runnable getSendRouteForwardingRunnable(GrpcRouteForwarding grpcRouteForwarding, CompletableFuture<GrpcRouteForwardingResponse> future) {
+		return () -> {
 			requestObserver.onNext(GrpcRouteForwardingRequest.newBuilder().setRouteForwarding(grpcRouteForwarding).build());
 			future.complete(GrpcRouteForwardingResponse.newBuilder().build());
-		});
-		return future;
+		};
 	}
 
 	CompletableFuture<GrpcRouteForwardingResponse> sendAttachments(List<IncomingFileGroup> attachments) {
@@ -92,10 +102,12 @@ class EingangForwarder {
 		return ignored -> sendAttachmentFile(groupName, file);
 	}
 
-	private CompletableFuture<GrpcRouteForwardingResponse> sendAttachmentFile(String groupName, IncomingFile file) {
+	CompletableFuture<GrpcRouteForwardingResponse> sendAttachmentFile(String groupName, IncomingFile file) {
 		var fileContentStream = fileService.getUploadedFileStream(file.getId());
 		var sender = createAttachmentFileSender(groupName, file, fileContentStream).send();
-		return sender.getResultFuture();
+		var future = sender.getResultFuture();
+		configureToCancelIfForwardFutureCompleted(future);
+		return future;
 	}
 
 	StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createAttachmentFileSender(String groupName,
@@ -130,14 +142,17 @@ class EingangForwarder {
 				);
 	}
 
-	private Function<GrpcRouteForwardingResponse, CompletableFuture<GrpcRouteForwardingResponse>> getSendRepresentationFileFunction(IncomingFile file) {
+	private Function<GrpcRouteForwardingResponse, CompletableFuture<GrpcRouteForwardingResponse>> getSendRepresentationFileFunction(
+			IncomingFile file) {
 		return ignored -> sendRepresentationFile(file);
 	}
 
-	private CompletableFuture<GrpcRouteForwardingResponse> sendRepresentationFile(IncomingFile file) {
+	CompletableFuture<GrpcRouteForwardingResponse> sendRepresentationFile(IncomingFile file) {
 		var fileContentStream = fileService.getUploadedFileStream(file.getId());
 		var sender = createRepresentationFileSender(file, fileContentStream).send();
-		return sender.getResultFuture();
+		var future = sender.getResultFuture();
+		configureToCancelIfForwardFutureCompleted(future);
+		return future;
 	}
 
 	StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createRepresentationFileSender(IncomingFile file,
@@ -161,6 +176,14 @@ class EingangForwarder {
 				.build();
 	}
 
+	void configureToCancelIfForwardFutureCompleted(CompletableFuture<GrpcRouteForwardingResponse> future) {
+		forwardFuture.whenComplete((result, ex) -> {
+			if (forwardFuture.isDone() && !future.isDone()) {
+				future.cancel(true);
+			}
+		});
+	}
+
 	GrpcFileContent buildGrpcFileContent(byte[] chunk, int length) {
 		var fileContentBuilder = GrpcFileContent.newBuilder();
 		if (length <= 0) {
@@ -173,7 +196,8 @@ class EingangForwarder {
 
 	StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createSenderWithoutMetadata(
 			BiFunction<byte[], Integer, GrpcRouteForwardingRequest> chunkBuilder, InputStream fileContentStream) {
-		return GrpcFileUploadUtils.createStreamSharingSender(chunkBuilder, fileContentStream, requestObserver, responseObserver::registerOnReadyHandler);
+		return GrpcFileUploadUtils.createStreamSharingSender(chunkBuilder, fileContentStream, requestObserver,
+				responseObserver::registerOnReadyHandler);
 	}
 
 	@RequiredArgsConstructor
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java
index 371374eca99a57ee7d85200e254df8d20348851b..b76dde08cd2cf42fd85b5d7dbd830c8f9aec35b0 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java
@@ -42,7 +42,7 @@ import net.devh.boot.grpc.client.inject.GrpcClient;
 @RequiredArgsConstructor
 class ForwardingRemoteService {
 
-	private static final int TIMEOUT_MINUTES = 10;
+	static final int TIMEOUT_MINUTES = 10;
 	private final VorgangService vorgangService;
 	private final ForwardingRequestMapper forwardingRequestMapper;
 	@GrpcClient("forwarder")
@@ -53,7 +53,8 @@ class ForwardingRemoteService {
 	public void forward(ForwardingRequest request) {
 		var eingang = vorgangService.getById(request.getVorgangId()).getEingangs().getFirst();
 		var grpcRouteForwarding = forwardingRequestMapper.toGrpcRouteForwarding(request, eingang);
-		var responseFuture = new EingangForwarder(serviceStub, fileService, incomingFileMapper).forward(grpcRouteForwarding, eingang.getAttachments(), eingang.getRepresentations());
+		var responseFuture = new EingangForwarder(serviceStub, fileService, incomingFileMapper).forward(grpcRouteForwarding, eingang.getAttachments(),
+				eingang.getRepresentations()).getForwardFuture();
 		waitForCompletion(responseFuture);
 	}
 
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileGroupTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileGroupTestFactory.java
index f95a612361b800020d17bc95b39bcb2e3dae6a57..4075d2c3bd063b6af34b47df2f3ce43f5ee723db 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileGroupTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileGroupTestFactory.java
@@ -23,15 +23,23 @@
  */
 package de.ozgcloud.vorgang.vorgang;
 
+import de.ozgcloud.vorgang.files.FileId;
+
 public class IncomingFileGroupTestFactory {
 
 	public static final String NAME = GrpcIncomingFileGroupTestFactory.NAME;
 	public static final IncomingFile FILE = IncomingFileTestFactory.create();
+	public static final IncomingFile FILE2 = IncomingFileTestFactory.createBuilder()
+			.id(FileId.createNew()).build();
 
 	public static IncomingFileGroup create() {
 		return createBuilder().build();
 	}
 
+	public static IncomingFileGroup createWithTwoFiles() {
+		return createBuilder().file(FILE2).build();
+	}
+
 	public static IncomingFileGroup.IncomingFileGroupBuilder createBuilder() {
 		return IncomingFileGroup.builder()
 				.name(NAME)
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarderTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..78f1837c3b657bd7221ba2e2c1a6d0a493d3fb2a
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/EingangForwarderTest.java
@@ -0,0 +1,689 @@
+package de.ozgcloud.vorgang.vorgang.redirect;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Spy;
+
+import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils;
+import de.ozgcloud.common.binaryfile.StreamingFileSender;
+import de.ozgcloud.common.test.ReflectionTestUtils;
+import de.ozgcloud.eingang.forwarder.RouteForwardingServiceGrpc;
+import de.ozgcloud.eingang.forwarding.GrpcRouteForwarding;
+import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingRequest;
+import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingResponse;
+import de.ozgcloud.vorgang.callcontext.VorgangManagerClientCallContextAttachingInterceptor;
+import de.ozgcloud.vorgang.files.FileId;
+import de.ozgcloud.vorgang.files.FileService;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
+import de.ozgcloud.vorgang.vorgang.IncomingFileGroup;
+import de.ozgcloud.vorgang.vorgang.IncomingFileGroupTestFactory;
+import de.ozgcloud.vorgang.vorgang.IncomingFileMapper;
+import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
+import de.ozgcloud.vorgang.vorgang.redirect.EingangForwarder.ForwardingResponseObserver;
+import io.grpc.stub.ClientCallStreamObserver;
+
+class EingangForwarderTest {
+
+	@Mock
+	private RouteForwardingServiceGrpc.RouteForwardingServiceStub serviceStub;
+	@Mock
+	private FileService fileService;
+	@Mock
+	private IncomingFileMapper incomingFileMapper;
+	@InjectMocks
+	@Spy
+	private EingangForwarder forwarder;
+
+	@Nested
+	class TestForward {
+
+		@Mock
+		private ClientCallStreamObserver<GrpcRouteForwardingRequest> requestObserver;
+		@Mock
+		private GrpcRouteForwarding grpcRouteForwarding;
+		private final List<IncomingFileGroup> attachments = List.of(IncomingFileGroupTestFactory.create());
+		private final List<IncomingFile> representations = List.of(IncomingFileTestFactory.create());
+
+		@BeforeEach
+		void init() {
+			setRequestObserverInForwarder(requestObserver);
+		}
+
+		@Test
+		void shouldCallOnCompletedOnSuccess() {
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).callService();
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendRouteForwarding(grpcRouteForwarding);
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendAttachments(attachments);
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendRepresentations(representations);
+
+			CompletableFuture<Void> future = forwarder.forward(grpcRouteForwarding, attachments, representations).getForwardFuture();
+
+			assertOnCompletedCalled(future);
+		}
+
+		@Test
+		void shouldCallOnErrorOnFailureInRouteForwarding() {
+			var error = new RuntimeException("Route forwarding failed");
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).callService();
+			doReturn(CompletableFuture.failedFuture(error)).when(forwarder).sendRouteForwarding(grpcRouteForwarding);
+
+			var future = forwarder.forward(grpcRouteForwarding, attachments, representations).getForwardFuture();
+
+			assertOnErrorCalled(future, error);
+		}
+
+		@Test
+		void shouldCallOnErrorOnFailureInSendAttachments() {
+			var error = new RuntimeException("Send attachments failed");
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).callService();
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendRouteForwarding(grpcRouteForwarding);
+			doReturn(CompletableFuture.failedFuture(error)).when(forwarder).sendAttachments(attachments);
+
+			var future = forwarder.forward(grpcRouteForwarding, attachments, representations).getForwardFuture();
+
+			assertOnErrorCalled(future, error);
+		}
+
+		@Test
+		void shouldCallOnErrorOnFailureInSendRepresentations() {
+			var error = new RuntimeException("Send representations failed");
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).callService();
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendRouteForwarding(grpcRouteForwarding);
+			doReturn(CompletableFuture.completedFuture(null)).when(forwarder).sendAttachments(attachments);
+			doReturn(CompletableFuture.failedFuture(error)).when(forwarder).sendRepresentations(representations);
+
+			var future = forwarder.forward(grpcRouteForwarding, attachments, representations).getForwardFuture();
+
+			assertOnErrorCalled(future, error);
+		}
+
+		private void assertOnCompletedCalled(CompletableFuture<Void> future) {
+			future.join();
+			verify(requestObserver).onCompleted();
+			verify(requestObserver, never()).onError(any());
+		}
+
+		private void assertOnErrorCalled(CompletableFuture<Void> future, Throwable error) {
+			future.handle((result, ex) -> {
+				verify(requestObserver).onError(argThat(e -> e instanceof CompletionException && e.getCause() == error));
+				verify(requestObserver, never()).onCompleted();
+				return null;
+			}).join();
+		}
+	}
+
+	@Nested
+	class TestCallService {
+
+		@BeforeEach
+		void init() {
+			when(serviceStub.withInterceptors(any())).thenReturn(serviceStub);
+		}
+
+		@Test
+		void shouldAttachClientCallContextToServiceStub() {
+			forwarder.callService();
+
+			verify(serviceStub).withInterceptors(any(VorgangManagerClientCallContextAttachingInterceptor.class));
+		}
+
+		@Test
+		void shouldCreateResponseObserver() {
+			forwarder.callService();
+
+			assertThat(getResponseObserverFromForwarder()).isNotNull();
+		}
+
+		@Test
+		void shouldMakeGrpcCallToRouteForwarding() {
+			forwarder.callService();
+
+			verify(serviceStub).routeForwarding(getResponseObserverFromForwarder());
+		}
+	}
+
+	@Nested
+	class TestSendRouteForwarding {
+
+		private final GrpcRouteForwarding grpcRouteForwarding = GrpcRouteForwarding.newBuilder().build();
+		@Mock
+		private ForwardingResponseObserver responseObserver;
+		@Mock
+		private Runnable onReadyHandler;
+		@Captor
+		private ArgumentCaptor<Runnable> onReadyHandlerCaptor;
+
+		@BeforeEach
+		void init() {
+			setResponseObserverInForwarder(responseObserver);
+			doReturn(onReadyHandler).when(forwarder).getSendRouteForwardingRunnable(any(), any());
+		}
+
+		@Test
+		void shouldGetSendRouteForwardingRunnable() {
+			var future = forwarder.sendRouteForwarding(grpcRouteForwarding);
+
+			verify(forwarder).getSendRouteForwardingRunnable(grpcRouteForwarding, future);
+		}
+
+		@Test
+		void shouldRegisterOnReadyHandler() {
+			forwarder.sendRouteForwarding(grpcRouteForwarding);
+
+			verify(responseObserver).registerOnReadyHandler(onReadyHandlerCaptor.capture());
+			assertThatIsResultOfGetSendRouteForwardingRunnable(onReadyHandlerCaptor.getValue());
+		}
+
+		private void assertThatIsResultOfGetSendRouteForwardingRunnable(Runnable runnable) {
+			runnable.run();
+			verify(onReadyHandler).run();
+		}
+	}
+
+	@Nested
+	class TestGetSendRouteForwardingRunnable {
+
+		private final GrpcRouteForwarding grpcRouteForwarding = GrpcRouteForwardingTestFactory.create();
+		@Mock
+		private ClientCallStreamObserver<GrpcRouteForwardingRequest> requestObserver;
+		@Mock
+		private CompletableFuture<GrpcRouteForwardingResponse> future;
+
+		@BeforeEach
+		void init() {
+			setRequestObserverInForwarder(requestObserver);
+		}
+
+		@Test
+		void shouldCallOnNext() {
+			forwarder.getSendRouteForwardingRunnable(grpcRouteForwarding, future).run();
+
+			verify(requestObserver).onNext(GrpcRouteForwardingRequestTestFactory.create());
+		}
+
+		@Test
+		void shouldCallOnComplete() {
+			forwarder.getSendRouteForwardingRunnable(grpcRouteForwarding, future).run();
+
+			verify(future).complete(GrpcRouteForwardingResponse.newBuilder().build());
+		}
+	}
+
+	@Nested
+	class TestSendAttachments {
+
+		private final List<IncomingFileGroup> attachments = List.of(IncomingFileGroupTestFactory.createWithTwoFiles());
+		private final CompletableFuture<GrpcRouteForwardingResponse> future = new CompletableFuture<>();
+		private final CompletableFuture<GrpcRouteForwardingResponse> future2 = new CompletableFuture<>();
+
+		@BeforeEach
+		void init() {
+			doReturn(future, future2).when(forwarder).sendAttachmentFile(any(), any());
+		}
+
+		@Test
+		void shouldReturnFuture() {
+			var returned = forwarder.sendAttachments(attachments);
+
+			assertThat(returned).isNotNull();
+		}
+
+		@Test
+		void shouldInitiallySendOnlyFirstFile() {
+			forwarder.sendAttachments(attachments);
+
+			verify(forwarder).sendAttachmentFile(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE);
+			verify(forwarder, times(1)).sendAttachmentFile(anyString(), any());
+		}
+
+		@Test
+		void shouldSendSecondFileAfterFirstFutureCompleted() {
+			forwarder.sendAttachments(attachments);
+
+			future.complete(GrpcRouteForwardingResponse.newBuilder().build());
+
+			verify(forwarder).sendAttachmentFile(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE2);
+			verify(forwarder, times(2)).sendAttachmentFile(anyString(), any());
+		}
+
+		@Test
+		void shouldReturnedFutureBeInitiallyIncomplete() {
+			var returned = forwarder.sendAttachments(attachments);
+
+			assertThat(returned.isDone()).isFalse();
+		}
+
+		@Test
+		void shouldReturnedFutureBeIncompleteAfterSendingFirstFile() {
+			var returned = forwarder.sendAttachments(attachments);
+
+			future.complete(GrpcRouteForwardingResponse.newBuilder().build());
+
+			assertThat(returned.isDone()).isFalse();
+		}
+
+		@Test
+		void shouldReturnedFutureBeDoneAfterSendingAllFiles() {
+			var returned = forwarder.sendAttachments(attachments);
+
+			future.complete(GrpcRouteForwardingResponse.newBuilder().build());
+			future2.complete(GrpcRouteForwardingResponse.newBuilder().build());
+
+			assertThat(returned.isDone()).isTrue();
+		}
+	}
+
+	@Nested
+	class TestSendAttachmentFile {
+
+		@Mock
+		private StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender;
+		private final CompletableFuture<GrpcRouteForwardingResponse> resultFuture = new CompletableFuture<>();
+		@Mock
+		private InputStream fileContentStream;
+
+		@BeforeEach
+		void init() {
+			when(fileService.getUploadedFileStream(any())).thenReturn(fileContentStream);
+			doReturn(fileSender).when(forwarder).createAttachmentFileSender(any(), any(), any());
+			doReturn(fileSender).when(fileSender).send();
+			when(fileSender.getResultFuture()).thenReturn(resultFuture);
+			doNothing().when(forwarder).configureToCancelIfForwardFutureCompleted(any());
+		}
+
+		@Test
+		void shouldGetUploadFileStream() {
+			sendAttachmentFile();
+
+			verify(fileService).getUploadedFileStream(IncomingFileGroupTestFactory.FILE.getId());
+		}
+
+		@Test
+		void shouldCreateAttachmentFileSender() {
+			sendAttachmentFile();
+
+			verify(forwarder).createAttachmentFileSender(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE, fileContentStream);
+		}
+
+		@Test
+		void shouldSend() {
+			sendAttachmentFile();
+
+			verify(fileSender).send();
+		}
+
+		@Test
+		void shouldConfigureFutureToCancelIfForwardFutureCompleted() {
+			sendAttachmentFile();
+
+			verify(forwarder).configureToCancelIfForwardFutureCompleted(resultFuture);
+		}
+
+		@Test
+		void shouldReturnResultFuture() {
+			var returned = sendAttachmentFile();
+
+			assertThat(returned).isSameAs(resultFuture);
+		}
+
+		private CompletableFuture<GrpcRouteForwardingResponse> sendAttachmentFile() {
+			return forwarder.sendAttachmentFile(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE);
+		}
+	}
+
+	@Nested
+	class TestCreateAttachmentFileSender {
+
+		@Mock
+		private InputStream inputStream;
+		@Mock
+		private GrpcFileUploadUtils.FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender;
+		@Mock
+		private GrpcFileUploadUtils.FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSenderWithMetadata;
+		@Captor
+		private ArgumentCaptor<BiFunction<byte[], Integer, GrpcRouteForwardingRequest>> chunkBuilderCaptor;
+
+		private final byte[] chunk = RandomUtils.insecure().randomBytes(5);
+		private final GrpcRouteForwardingRequest metadataRequest = GrpcRouteForwardingRequestTestFactory.create();
+
+		@BeforeEach
+		void init() {
+			doReturn(fileSender).when(forwarder).createSenderWithoutMetadata(any(), any());
+			doReturn(metadataRequest).when(forwarder).buildGrpcAttachmentFile(any(), any());
+			when(fileSender.withMetaData(any())).thenReturn(fileSenderWithMetadata);
+		}
+
+		@Test
+		void shouldCallCreateSenderWithoutMetadata() {
+			createAttachmentFileSender();
+
+			verify(forwarder).createSenderWithoutMetadata(chunkBuilderCaptor.capture(), eq(inputStream));
+			chunkBuilderCaptor.getValue().apply(chunk, chunk.length);
+			verify(forwarder).buildAttachmentChunk(chunk, chunk.length);
+		}
+
+		@Test
+		void shouldCallBuildGrpcAttachmentFile() {
+			createAttachmentFileSender();
+
+			verify(forwarder).buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE);
+		}
+
+		@Test
+		void shouldSetMetaData() {
+			createAttachmentFileSender();
+
+			verify(fileSender).withMetaData(metadataRequest);
+		}
+
+		@Test
+		void shouldReturnBuiltFileSender() {
+			var returnedFileSender = createAttachmentFileSender();
+
+			assertThat(returnedFileSender).isSameAs(fileSenderWithMetadata);
+		}
+
+		private StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createAttachmentFileSender() {
+			return forwarder.createAttachmentFileSender(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE, inputStream);
+		}
+	}
+
+	@Nested
+	class TestBuildAttachmentChunk {
+
+		private final byte[] chunk = RandomUtils.insecure().randomBytes(5);
+
+		@BeforeEach
+		void mock() {
+			doReturn(GrpcAttachmentTestFactory.CONTENT).when(forwarder).buildGrpcFileContent(any(), anyInt());
+		}
+
+		@Test
+		void shouldCallBuildGrpcFileContent() {
+			forwarder.buildAttachmentChunk(chunk, chunk.length);
+
+			verify(forwarder).buildGrpcFileContent(chunk, chunk.length);
+		}
+
+		@Test
+		void shouldReturnGrpcRouteForwardingRequest() {
+			var result = forwarder.buildAttachmentChunk(chunk, chunk.length);
+
+			assertThat(result).isEqualTo(GrpcRouteForwardingRequestTestFactory.createWithAttachmentContent());
+		}
+	}
+
+	@Nested
+	class TestBuildGrpcAttachmentFile {
+
+		private final IncomingFile file = IncomingFileTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(incomingFileMapper.toAttachmentFile(any(), any())).thenReturn(GrpcAttachmentFileTestFactory.create());
+		}
+
+		@Test
+		void shouldCallIncomingFileMapper() {
+			forwarder.buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, file);
+
+			verify(incomingFileMapper).toAttachmentFile(IncomingFileGroupTestFactory.NAME, file);
+		}
+
+		@Test
+		void shouldReturnAttachmentMetadataRequest() {
+			var result = forwarder.buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, file);
+
+			assertThat(result).isEqualTo(GrpcRouteForwardingRequestTestFactory.createWithAttachmentMetadata());
+		}
+	}
+
+	@Nested
+	class TestSendRepresentations {
+
+		private static final IncomingFile FILE = IncomingFileTestFactory.create();
+		private static final IncomingFile FILE2 = IncomingFileTestFactory.createBuilder().id(FileId.createNew()).build();
+		private final List<IncomingFile> representations = List.of(FILE, FILE2);
+		private final CompletableFuture<GrpcRouteForwardingResponse> future = new CompletableFuture<>();
+		private final CompletableFuture<GrpcRouteForwardingResponse> future2 = new CompletableFuture<>();
+
+		@BeforeEach
+		void init() {
+			doReturn(future, future2).when(forwarder).sendRepresentationFile(any());
+		}
+
+		@Test
+		void shouldReturnFuture() {
+			var returned = forwarder.sendRepresentations(representations);
+
+			assertThat(returned).isNotNull();
+		}
+	}
+
+	@Nested
+	class TestCreateRepresentationFileSender {
+
+		@Mock
+		private InputStream inputStream;
+		@Mock
+		private GrpcFileUploadUtils.FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender;
+		@Mock
+		private GrpcFileUploadUtils.FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSenderWithMetadata;
+		@Captor
+		private ArgumentCaptor<BiFunction<byte[], Integer, GrpcRouteForwardingRequest>> chunkBuilderCaptor;
+
+		private final byte[] chunk = RandomUtils.insecure().randomBytes(5);
+		private final GrpcRouteForwardingRequest metadataRequest = GrpcRouteForwardingRequestTestFactory.create();
+		private final IncomingFile incomingFile = IncomingFileTestFactory.create();
+
+		@BeforeEach
+		void init() {
+			doReturn(fileSender).when(forwarder).createSenderWithoutMetadata(any(), any());
+			doReturn(metadataRequest).when(forwarder).buildGrpcRepresentationFile(any());
+			when(fileSender.withMetaData(any())).thenReturn(fileSenderWithMetadata);
+		}
+
+		@Test
+		void shouldCallCreateSenderWithoutMetadata() {
+			createRepresentationFileSender();
+
+			verify(forwarder).createSenderWithoutMetadata(chunkBuilderCaptor.capture(), eq(inputStream));
+			chunkBuilderCaptor.getValue().apply(chunk, chunk.length);
+			verify(forwarder).buildRepresentationChunk(chunk, chunk.length);
+		}
+
+		@Test
+		void shouldCallBuildGrpcRepresentationFile() {
+			createRepresentationFileSender();
+
+			verify(forwarder).buildGrpcRepresentationFile(incomingFile);
+		}
+
+		@Test
+		void shouldSetMetaData() {
+			createRepresentationFileSender();
+
+			verify(fileSender).withMetaData(metadataRequest);
+		}
+
+		@Test
+		void shouldReturnBuiltFileSender() {
+			var returnedFileSender = createRepresentationFileSender();
+
+			assertThat(returnedFileSender).isSameAs(fileSenderWithMetadata);
+		}
+
+		private StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createRepresentationFileSender() {
+			return forwarder.createRepresentationFileSender(incomingFile, inputStream);
+		}
+	}
+
+	@Nested
+	class TestBuildRepresentationChunk {
+
+		private final byte[] chunk = RandomUtils.insecure().randomBytes(5);
+
+		@BeforeEach
+		void mock() {
+			doReturn(GrpcRepresentationTestFactory.CONTENT).when(forwarder).buildGrpcFileContent(any(), anyInt());
+		}
+
+		@Test
+		void shouldCallBuildGrpcFileContent() {
+			forwarder.buildRepresentationChunk(chunk, chunk.length);
+
+			verify(forwarder).buildGrpcFileContent(chunk, chunk.length);
+		}
+
+		@Test
+		void shouldReturnGrpcRouteForwardingRequest() {
+			var result = forwarder.buildRepresentationChunk(chunk, chunk.length);
+
+			assertThat(result).isEqualTo(GrpcRouteForwardingRequestTestFactory.createWithRepresentationContent());
+		}
+	}
+
+	@Nested
+	class TestBuildGrpcRepresentationFile {
+
+		private final IncomingFile file = IncomingFileTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(incomingFileMapper.toRepresentationFile(any())).thenReturn(GrpcRepresentationFileTestFactory.create());
+		}
+
+		@Test
+		void shouldCallIncomingFileMapper() {
+			forwarder.buildGrpcRepresentationFile(file);
+
+			verify(incomingFileMapper).toRepresentationFile(file);
+		}
+
+		@Test
+		void shouldReturnRepresentationMetadataRequest() {
+			var result = forwarder.buildGrpcRepresentationFile(file);
+
+			assertThat(result).isEqualTo(GrpcRouteForwardingRequestTestFactory.createWithRepresentationMetadata());
+		}
+	}
+
+	@Nested
+	class TestBuildGrpcFileContent {
+
+		@Nested
+		class TestOnEndOfFile {
+
+			@Test
+			void shouldBuildEndOfFileChunk() {
+				var fileContent = forwarder.buildGrpcFileContent(new byte[0], -1);
+
+				assertThat(fileContent).isEqualTo(GrpcFileContentTestFactory.createEndOfFile());
+			}
+		}
+
+		@Nested
+		class TestOnContentProvided {
+
+			@Test
+			void shouldBuildEndOfFileChunk() {
+				var fileContent = forwarder.buildGrpcFileContent(GrpcFileContentTestFactory.CONTENT, GrpcFileContentTestFactory.CONTENT.length);
+
+				assertThat(fileContent).isEqualTo(GrpcFileContentTestFactory.create());
+			}
+		}
+	}
+
+	@Nested
+	class TestCreateSenderWithoutMetadata {
+
+		private MockedStatic<GrpcFileUploadUtils> grpcFileUploadUtilsMock;
+		@Mock
+		private BiFunction<byte[], Integer, GrpcRouteForwardingRequest> chunkBuilder;
+		@Mock
+		private ClientCallStreamObserver<GrpcRouteForwardingRequest> requestObserver;
+		@Mock
+		private ForwardingResponseObserver responseObserver;
+		@Mock
+		private InputStream inputStream;
+		@Mock
+		private StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender;
+		@Mock
+		private Runnable onReadyHandler;
+		@Captor
+		private ArgumentCaptor<Consumer<Runnable>> onReadyHandlerCaptor;
+
+		@BeforeEach
+		void init() {
+			grpcFileUploadUtilsMock = mockStatic(GrpcFileUploadUtils.class);
+			grpcFileUploadUtilsMock.when(() -> GrpcFileUploadUtils.createStreamSharingSender(any(), any(), any(), any())).thenReturn(fileSender);
+			ReflectionTestUtils.setField(forwarder, "responseObserver", responseObserver);
+			ReflectionTestUtils.setField(forwarder, "requestObserver", requestObserver);
+		}
+
+		@AfterEach
+		void tearDown() {
+			grpcFileUploadUtilsMock.close();
+		}
+
+		@Test
+		void shouldCreateFileSender() {
+
+			createSenderWithoutMetadata();
+
+			grpcFileUploadUtilsMock
+					.verify(() -> GrpcFileUploadUtils.<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse>createStreamSharingSender(
+							eq(chunkBuilder), eq(inputStream), eq(requestObserver), onReadyHandlerCaptor.capture()));
+			assertIsRegisterOnReadyHandler(onReadyHandlerCaptor);
+		}
+
+		@Test
+		void shouldReturnCreatedFileSender() {
+			var returnedFileSender = createSenderWithoutMetadata();
+
+			assertThat(returnedFileSender).isSameAs(fileSender);
+		}
+
+		private void assertIsRegisterOnReadyHandler(ArgumentCaptor<Consumer<Runnable>> captor) {
+			captor.getValue().accept(onReadyHandler);
+			verify(responseObserver).registerOnReadyHandler(onReadyHandler);
+		}
+
+		private StreamingFileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createSenderWithoutMetadata() {
+			return forwarder.createSenderWithoutMetadata(chunkBuilder, inputStream);
+		}
+	}
+
+	private void setResponseObserverInForwarder(ForwardingResponseObserver responseObserver) {
+		ReflectionTestUtils.setField(forwarder, "responseObserver", responseObserver);
+	}
+
+	private ForwardingResponseObserver getResponseObserverFromForwarder() {
+		return ReflectionTestUtils.getField(forwarder, "responseObserver", ForwardingResponseObserver.class);
+	}
+
+	private void setRequestObserverInForwarder(ClientCallStreamObserver<GrpcRouteForwardingRequest> requestObserver) {
+		ReflectionTestUtils.setField(forwarder, "requestObserver", requestObserver);
+	}
+}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3df3152b6a388d150b384d6cd918639d79f24f7d
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java
@@ -0,0 +1,123 @@
+package de.ozgcloud.vorgang.vorgang.redirect;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import de.ozgcloud.eingang.forwarder.RouteForwardingServiceGrpc;
+import de.ozgcloud.vorgang.files.FileService;
+import de.ozgcloud.vorgang.vorgang.IncomingFileMapper;
+import de.ozgcloud.vorgang.vorgang.VorgangService;
+import lombok.SneakyThrows;
+
+class ForwardingRemoteServiceTest {
+
+	@Mock
+	private VorgangService vorgangService;
+	@Mock
+	private ForwardingRequestMapper forwardingRequestMapper;
+	@Mock
+	private RouteForwardingServiceGrpc.RouteForwardingServiceStub serviceStub;
+	@Mock
+	private FileService fileService;
+	@Mock
+	private IncomingFileMapper incomingFileMapper;
+	@InjectMocks
+	@Spy
+	private ForwardingRemoteService service;
+
+	@Nested
+	class TestWaitForCompletion {
+
+		@Mock
+		private CompletableFuture<Void> future;
+
+		@SneakyThrows
+		@Test
+		void shouldGetFromFuture() {
+			waitForCompletion();
+
+			verify(future).get(ForwardingRemoteService.TIMEOUT_MINUTES, TimeUnit.MINUTES);
+		}
+
+		@Nested
+		class TestOnInterruptedException {
+
+			private final InterruptedException exception = new InterruptedException();
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(future.get(anyLong(), any())).thenThrow(exception);
+			}
+
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThrows(TechnicalException.class, de.ozgcloud.vorgang.vorgang.redirect.ForwardingRemoteServiceTest.TestWaitForCompletion.this::waitForCompletion);
+			}
+
+			@Test
+			void shouldInterruptThread() {
+				try {
+					waitForCompletion();
+				} catch (TechnicalException e) {
+					// expected
+				}
+
+				assertThat(Thread.currentThread().isInterrupted()).isTrue();
+			}
+		}
+
+		@Nested
+		class TestOnExecutionException {
+
+			private final ExecutionException exception = new ExecutionException(new Exception());
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(future.get(anyLong(), any())).thenThrow(exception);
+			}
+
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThrows(TechnicalException.class, de.ozgcloud.vorgang.vorgang.redirect.ForwardingRemoteServiceTest.TestWaitForCompletion.this::waitForCompletion);
+			}
+		}
+
+		@Nested
+		class TestOnTimeoutException {
+
+			private final TimeoutException exception = new TimeoutException();
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(future.get(anyLong(), any())).thenThrow(exception);
+			}
+
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThrows(TechnicalException.class, de.ozgcloud.vorgang.vorgang.redirect.ForwardingRemoteServiceTest.TestWaitForCompletion.this::waitForCompletion);
+			}
+		}
+
+		private void waitForCompletion() {
+			service.waitForCompletion(future);
+		}
+	}
+}