diff --git a/collaboration-manager-interface/src/main/protobuf/collaboration.model.proto b/collaboration-manager-interface/src/main/protobuf/collaboration.model.proto
index 8666ef1741a63fc287746088b92e9f3b211d2d1c..2add82e8addc986fe55016290c1c3e47782c07fa 100644
--- a/collaboration-manager-interface/src/main/protobuf/collaboration.model.proto
+++ b/collaboration-manager-interface/src/main/protobuf/collaboration.model.proto
@@ -120,4 +120,13 @@ message GrpcFormField {
 	string name = 1;
 	string label = 2;
 	string value = 3;
+}
+
+message GrpcGetFileContentRequest {
+	string samlToken = 1;
+	string id = 2;
+}
+
+message GrpcGetFileContentResponse {
+	bytes fileContent = 1;
 }
\ No newline at end of file
diff --git a/collaboration-manager-interface/src/main/protobuf/collaboration.proto b/collaboration-manager-interface/src/main/protobuf/collaboration.proto
index 33cf53ed2f4e93a8cc2169a15350fcc054b2d442..fae46694d42cd9da6f5d3f7d3ec7e9a11c6d9991 100644
--- a/collaboration-manager-interface/src/main/protobuf/collaboration.proto
+++ b/collaboration-manager-interface/src/main/protobuf/collaboration.proto
@@ -35,4 +35,7 @@ service CollaborationService {
 
 	rpc FindVorgang(GrpcFindVorgangRequest) returns (GrpcFindVorgangResponse) {
 	}
+	
+	rpc GetFileContent(GrpcGetFileContentRequest) returns (stream GrpcGetFileContentResponse) {
+	}
 }
\ No newline at end of file
diff --git a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationEventListener.java b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationEventListener.java
index bedfa508a2bf83651c5d5abcdfbae1801fd8efdb..f7e3362bb1c9cf7ad5df00a9929628be7c936740 100644
--- a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationEventListener.java
+++ b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationEventListener.java
@@ -48,8 +48,7 @@ class CollaborationEventListener {
 	public static final String CREATE_COLLABORATION_REQUEST_ORDER = "CREATE_COLLABORATION_REQUEST";
 	public static final Predicate<Command> IS_CREATE_COLLABORATION_REQUEST_COMMAND = command -> CREATE_COLLABORATION_REQUEST_ORDER.equals(
 			command.getOrder());
-	private static final String IS_CREATE_COLLABORATION_REQUEST =
-			"{T(de.ozgcloud.collaboration.CollaborationEventListener).IS_CREATE_COLLABORATION_REQUEST_COMMAND.test(event.getSource())}";
+	private static final String IS_CREATE_COLLABORATION_REQUEST = "{T(de.ozgcloud.collaboration.CollaborationEventListener).IS_CREATE_COLLABORATION_REQUEST_COMMAND.test(event.getSource())}";
 
 	private static final String LOG_MESSAGE_TEMPLATE = "{}. Command failed.";
 	private static final String ERROR_MESSAGE_TEMPLATE = "Error on executing %s Command (id: %s).";
diff --git a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationGrpcService.java b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationGrpcService.java
index 17d5c1c2bda129376973019c196c08d8673f37e1..cc18107d4389788104f2de800e1d70c12aaf85b0 100644
--- a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationGrpcService.java
+++ b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationGrpcService.java
@@ -23,9 +23,16 @@
  */
 package de.ozgcloud.collaboration;
 
+import org.springframework.core.task.TaskExecutor;
+
+import com.google.protobuf.ByteString;
+
+import de.ozgcloud.apilib.file.OzgCloudFileId;
+import de.ozgcloud.collaboration.common.grpc.GrpcDownloader;
 import de.ozgcloud.collaboration.vorgang.CollaborationVorgangMapper;
 import de.ozgcloud.collaboration.vorgang.Vorgang;
 import de.ozgcloud.collaboration.vorgang.VorgangService;
+import io.grpc.stub.CallStreamObserver;
 import io.grpc.stub.StreamObserver;
 import lombok.RequiredArgsConstructor;
 import net.devh.boot.grpc.server.service.GrpcService;
@@ -34,13 +41,19 @@ import net.devh.boot.grpc.server.service.GrpcService;
 @RequiredArgsConstructor
 public class CollaborationGrpcService extends CollaborationServiceGrpc.CollaborationServiceImplBase {
 
-	private final VorgangService vorgangService;
+	public static final int CHUNK_SIZE = 255 * 1024;
 
+	private final CollaborationService service;
 	private final CollaborationVorgangMapper vorgangMapper;
 
+	private final VorgangService vorgangService;
+
+	private final TaskExecutor taskExecutor;
+
 	@Override
 	public void findVorgang(GrpcFindVorgangRequest request, StreamObserver<GrpcFindVorgangResponse> responseObserver) {
 		var vorgang = vorgangService.getVorgang(request.getVorgangId());
+
 		responseObserver.onNext(buildFindVorgangResponse(vorgang));
 		responseObserver.onCompleted();
 	}
@@ -50,4 +63,23 @@ public class CollaborationGrpcService extends CollaborationServiceGrpc.Collabora
 				.setVorgang(vorgangMapper.toGrpc(vorgang))
 				.build();
 	}
-}
+
+	@Override
+	public void getFileContent(GrpcGetFileContentRequest request, StreamObserver<GrpcGetFileContentResponse> responseObserver) {
+		buildDownloader(request.getId(), responseObserver).start();
+	}
+
+	GrpcDownloader<GrpcGetFileContentResponse> buildDownloader(String fileId,
+			StreamObserver<GrpcGetFileContentResponse> responseObserver) {
+		return GrpcDownloader.<GrpcGetFileContentResponse>builder()
+				.callObserver((CallStreamObserver<GrpcGetFileContentResponse>) responseObserver)
+				.downloadConsumer(outputStream -> service.writeFileContent(OzgCloudFileId.from(fileId), outputStream))
+				.chunkBuilder(this::buildChunkResponse)
+				.taskExecutor(taskExecutor)
+				.build();
+	}
+
+	GrpcGetFileContentResponse buildChunkResponse(ByteString bytes) {
+		return GrpcGetFileContentResponse.newBuilder().setFileContent(bytes).build();
+	}
+}
\ No newline at end of file
diff --git a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationService.java b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationService.java
index aefb5b81224ba56834d9ea7d44e02677c1aed33e..40e51fc27c73e3163fe5ddfe082ee5da34b7fd66 100644
--- a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationService.java
+++ b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/CollaborationService.java
@@ -23,6 +23,8 @@
  */
 package de.ozgcloud.collaboration;
 
+import java.io.OutputStream;
+
 import jakarta.validation.Valid;
 
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -31,6 +33,8 @@ import org.springframework.validation.annotation.Validated;
 
 import de.ozgcloud.apilib.common.command.OzgCloudCommandService;
 import de.ozgcloud.apilib.common.command.OzgCloudCreateSubCommandsRequest;
+import de.ozgcloud.apilib.file.OzgCloudFileId;
+import de.ozgcloud.apilib.file.OzgCloudFileService;
 import de.ozgcloud.collaboration.common.user.UserProfileService;
 import de.ozgcloud.collaboration.vorgang.VorgangService;
 import lombok.RequiredArgsConstructor;
@@ -52,6 +56,8 @@ public class CollaborationService {
 	@Qualifier(CollaborationManagerConfiguration.USER_PROFILE_SERVICE_NAME) // NOSONAR
 	private final UserProfileService userProfileService;
 
+	private final OzgCloudFileService fileService;
+
 	private final CollaborationManagerCollaborationRequestMapper collaborationRequestMapper;
 
 	public void createCollaborationRequest(@Valid CollaborationRequest collaborationRequest) {
@@ -76,4 +82,7 @@ public class CollaborationService {
 				.build();
 	}
 
-}
+	public void writeFileContent(OzgCloudFileId fileId, OutputStream out) {
+		fileService.writeFileDataToStream(fileId, out);
+	}
+}
\ No newline at end of file
diff --git a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/ExceptionRunnable.java b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/ExceptionRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..0fc69b2046870612318ed74500e3eb83c6884f89
--- /dev/null
+++ b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/ExceptionRunnable.java
@@ -0,0 +1,7 @@
+package de.ozgcloud.collaboration.common.grpc;
+
+import java.io.IOException;
+
+interface ExceptionRunnable {
+	void run() throws IOException; // NOSONAR
+}
\ No newline at end of file
diff --git a/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloader.java b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloader.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d777066a4cea56cb6df3276760a5f44df47b567
--- /dev/null
+++ b/collaboration-manager-server/src/main/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloader.java
@@ -0,0 +1,109 @@
+package de.ozgcloud.collaboration.common.grpc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.springframework.core.task.TaskExecutor;
+
+import com.google.protobuf.ByteString;
+
+import de.ozgcloud.apilib.file.OzgCloudFileId;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import io.grpc.stub.CallStreamObserver;
+import lombok.Builder;
+
+public class GrpcDownloader<T> {
+
+	private static final int CHUNK_SIZE = 255 * 1024;
+
+	private CallStreamObserver<T> callObserver;
+	private Function<ByteString, T> chunkBuilder;
+	private Consumer<OutputStream> downloadConsumer;
+	private TaskExecutor taskExecutor;
+
+	private final byte[] buffer = new byte[GrpcDownloader.CHUNK_SIZE];
+	private final AtomicBoolean downloadInProgress = new AtomicBoolean(false);
+
+	private PipedInputStream inputStream;
+	private PipedOutputStream outputStream;
+
+	@Builder
+	public GrpcDownloader(CallStreamObserver<T> callObserver, OzgCloudFileId fileId, Function<ByteString, T> chunkBuilder,
+			Consumer<OutputStream> downloadConsumer, TaskExecutor taskExecutor) {
+		this.callObserver = callObserver;
+		this.chunkBuilder = chunkBuilder;
+		this.downloadConsumer = downloadConsumer;
+		this.taskExecutor = taskExecutor;
+	}
+
+	public void start() {
+		handleSafety(this::doStart);
+	}
+
+	void doStart() throws IOException {
+		setupStreams();
+		taskExecutor.execute(this::startDownload);
+		callObserver.setOnReadyHandler(this::onReadyHandler);
+	}
+
+	void setupStreams() throws IOException {
+		outputStream = new PipedOutputStream();
+		inputStream = new PipedInputStream(GrpcDownloader.CHUNK_SIZE);
+		outputStream.connect(inputStream);
+	}
+
+	void startDownload() {
+		handleSafety(this::doDownload);
+	}
+
+	void doDownload() throws IOException {
+		downloadInProgress.set(true);
+		downloadConsumer.accept(outputStream);
+		downloadInProgress.set(false);
+		outputStream.close();
+	}
+
+	synchronized void onReadyHandler() {
+		if (callObserver.isReady()) {
+			sendChunks();
+		}
+	}
+
+	void sendChunks() {
+		handleSafety(this::doSendChunks);
+	}
+
+	void doSendChunks() throws IOException {
+		int bytesRead;
+		while (callObserver.isReady() && (bytesRead = inputStream.read(buffer)) != -1) {
+			callObserver.onNext(chunkBuilder.apply(ByteString.copyFrom(buffer, 0, bytesRead)));
+		}
+		if (!downloadInProgress.get()) {
+			inputStream.close();
+			callObserver.onCompleted();
+		}
+	}
+
+	void handleSafety(ExceptionRunnable runnable) {
+		try {
+			runnable.run();
+		} catch (IOException e) {
+			try {
+				inputStream.close();
+				outputStream.close();
+			} catch (IOException e1) {
+				throwException(e1);
+			}
+			throwException(e);
+		}
+	}
+
+	private void throwException(IOException e) {
+		throw new TechnicalException("Error occurred during downloading file content download.", e);
+	}
+}
\ No newline at end of file
diff --git a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationEventListenerITCase.java b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationEventListenerITCase.java
index 641aec972ec11ef3a6bcc27022c91556b2206f5f..fbb35a1212b534eeed6a723475225b7742e2e902 100644
--- a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationEventListenerITCase.java
+++ b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationEventListenerITCase.java
@@ -28,9 +28,11 @@ import static org.mockito.Mockito.*;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.SpyBean;
 import org.springframework.context.ApplicationEventPublisher;
 
+import de.ozgcloud.apilib.file.OzgCloudFileService;
 import de.ozgcloud.command.Command;
 import de.ozgcloud.command.CommandCreatedEventTestFactory;
 import de.ozgcloud.command.CommandTestFactory;
@@ -45,6 +47,9 @@ class CollaborationEventListenerITCase {
 	@Autowired
 	private ApplicationEventPublisher publisher;
 
+	@MockBean
+	private OzgCloudFileService fileService;
+
 	@Nested
 	class TestOnCreateCollaborationRequest {
 
diff --git a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationGrpcServiceTest.java b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationGrpcServiceTest.java
index b95b5e952486d72dfd99a17ac67f60b37ed76a45..7614a782ae107dcd1fdd6d3e5c521d6777b98e36 100644
--- a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationGrpcServiceTest.java
+++ b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationGrpcServiceTest.java
@@ -27,19 +27,31 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
+import java.io.OutputStream;
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 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 org.springframework.core.task.TaskExecutor;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.google.protobuf.ByteString;
 
+import de.ozgcloud.collaboration.common.grpc.GrpcDownloader;
 import de.ozgcloud.collaboration.vorgang.CollaborationVorgangMapper;
 import de.ozgcloud.collaboration.vorgang.GrpcVorgangTestFactory;
 import de.ozgcloud.collaboration.vorgang.Vorgang;
 import de.ozgcloud.collaboration.vorgang.VorgangService;
 import de.ozgcloud.collaboration.vorgang.VorgangTestFactory;
 import de.ozgcloud.command.CommandTestFactory;
+import io.grpc.stub.CallStreamObserver;
 import io.grpc.stub.StreamObserver;
 
 class CollaborationGrpcServiceTest {
@@ -47,11 +59,16 @@ class CollaborationGrpcServiceTest {
 	@Spy
 	@InjectMocks
 	private CollaborationGrpcService grpcService;
+	@Mock
+	private CollaborationService service;
+	@Mock
+	private CollaborationVorgangMapper vorgangMapper;
 
 	@Mock
 	private VorgangService vorgangService;
+
 	@Mock
-	private CollaborationVorgangMapper vorgangMapper;
+	private TaskExecutor taskExecutor;
 
 	@Nested
 	class TestFindVorgang {
@@ -131,4 +148,93 @@ class CollaborationGrpcServiceTest {
 			return grpcService.buildFindVorgangResponse(vorgang);
 		}
 	}
+
+	@DisplayName("Get file content")
+	@Nested
+	class TestGetFileContent {
+
+		@Mock
+		private StreamObserver<GrpcGetFileContentResponse> responseObserver;
+		@Mock
+		private GrpcDownloader<GrpcGetFileContentResponse> grpcDownloader;
+
+		private final String fileId = UUID.randomUUID().toString();
+		private final GrpcGetFileContentRequest request = GrpcGetFileContentRequest.newBuilder().setId(fileId).build();
+
+		@BeforeEach
+		void mock() {
+			doReturn(grpcDownloader).when(grpcService).buildDownloader(any(), any());
+		}
+
+		@Test
+		void shouldCreateDownloader() {
+			grpcService.getFileContent(request, responseObserver);
+
+			verify(grpcService).buildDownloader(fileId, responseObserver);
+		}
+
+		@Test
+		void shouldStartDownload() {
+			grpcService.getFileContent(request, responseObserver);
+
+			verify(grpcDownloader).start();
+		}
+	}
+
+	@DisplayName("build downloader")
+	@Nested
+	class TestBuildDownloader {
+
+		private final String fileId = UUID.randomUUID().toString();
+
+		@Mock
+		private CallStreamObserver<GrpcGetFileContentResponse> callStreamObserver;
+
+		@Test
+		void shouldContainCallObserver() {
+			var downloader = grpcService.buildDownloader(fileId, callStreamObserver);
+
+			assertThat(getCallObserver(downloader)).isEqualTo(callStreamObserver);
+		}
+
+		@Test
+		void shouldContainDownloadConsumer() {
+			var downloader = grpcService.buildDownloader(fileId, callStreamObserver);
+
+			assertThat(getDownloadConsumer(downloader)).isNotNull();
+		}
+
+		@Test
+		void shouldContainChunkBuilder() {
+			var downloader = grpcService.buildDownloader(fileId, callStreamObserver);
+
+			assertThat(getChunkBuilder(downloader)).isNotNull();
+		}
+
+		@Test
+		void shouldContainTaskBuilder() {
+			var downloader = grpcService.buildDownloader(fileId, callStreamObserver);
+
+			assertThat(getTaskExecuter(downloader)).isEqualTo(taskExecutor);
+		}
+
+		@SuppressWarnings("unchecked")
+		private CallStreamObserver<GrpcGetFileContentResponse> getCallObserver(GrpcDownloader<GrpcGetFileContentResponse> downloader) {
+			return (CallStreamObserver<GrpcGetFileContentResponse>) ReflectionTestUtils.getField(downloader, "callObserver");
+		}
+
+		@SuppressWarnings("unchecked")
+		private Consumer<OutputStream> getDownloadConsumer(GrpcDownloader<GrpcGetFileContentResponse> downloader) {
+			return (Consumer<OutputStream>) ReflectionTestUtils.getField(downloader, "downloadConsumer");
+		}
+
+		@SuppressWarnings("unchecked")
+		private Function<ByteString, GrpcGetFileContentResponse> getChunkBuilder(GrpcDownloader<GrpcGetFileContentResponse> downloader) {
+			return (Function<ByteString, GrpcGetFileContentResponse>) ReflectionTestUtils.getField(downloader, "chunkBuilder");
+		}
+
+		private TaskExecutor getTaskExecuter(GrpcDownloader<GrpcGetFileContentResponse> downloader) {
+			return (TaskExecutor) ReflectionTestUtils.getField(downloader, "taskExecutor");
+		}
+	}
 }
\ No newline at end of file
diff --git a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceITCase.java b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceITCase.java
index 79b329d35e24adf9632866c45f9f0276f04f8f91..f76bc81cecb3e09161f1713ea87bb52e9bea2171 100644
--- a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceITCase.java
+++ b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceITCase.java
@@ -33,7 +33,9 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.NullAndEmptySource;
 import org.junit.jupiter.params.provider.ValueSource;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
 
+import de.ozgcloud.apilib.file.OzgCloudFileService;
 import de.ozgcloud.common.test.ITCase;
 
 @ITCase
@@ -42,6 +44,9 @@ class CollaborationServiceITCase {
 	@Autowired
 	private CollaborationService service;
 
+	@MockBean
+	private OzgCloudFileService fileService;
+
 	@Nested
 	class TestCreateCollaborationRequest {
 
diff --git a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceTest.java b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceTest.java
index 964039613ad6589b6cc9977d516b34a6e1984c9c..68b30f7562caebea5f60a864dc746a1c88d2ee11 100644
--- a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceTest.java
+++ b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/CollaborationServiceTest.java
@@ -27,7 +27,11 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
+import java.io.OutputStream;
+import java.util.UUID;
+
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
@@ -37,6 +41,8 @@ import org.mockito.Spy;
 import de.ozgcloud.apilib.common.command.OzgCloudCommand;
 import de.ozgcloud.apilib.common.command.OzgCloudCommandService;
 import de.ozgcloud.apilib.common.command.OzgCloudCreateSubCommandsRequest;
+import de.ozgcloud.apilib.file.OzgCloudFileId;
+import de.ozgcloud.apilib.file.OzgCloudFileService;
 import de.ozgcloud.collaboration.common.user.UserId;
 import de.ozgcloud.collaboration.common.user.UserProfile;
 import de.ozgcloud.collaboration.common.user.UserProfileService;
@@ -57,12 +63,14 @@ class CollaborationServiceTest {
 	private UserProfileService userProfileService;
 	@Mock
 	private CollaborationManagerCollaborationRequestMapper collaborationRequestMapper;
+	@Mock
+	private OzgCloudFileService fileService;
 
 	@Nested
 	class TestCreateCollaborationRequest {
 
-		private static final CollaborationRequest COLLABORATION_REQUEST =
-				CollaborationRequestTestFactory.createBuilder().collaborationVorgangId(null).createdBy(null).build();
+		private static final CollaborationRequest COLLABORATION_REQUEST = CollaborationRequestTestFactory.createBuilder().collaborationVorgangId(null)
+				.createdBy(null).build();
 		private static final CollaborationRequest ENRICHED_COLLABORATION_REQUEST = CollaborationRequestTestFactory.create();
 
 		@Mock
@@ -175,8 +183,8 @@ class CollaborationServiceTest {
 	@Nested
 	class TestBuildCreateCollaborationRequestRequest {
 
-		private static final CollaborationRequest COLLABORATION_REQUEST =
-				CollaborationRequestTestFactory.createBuilder().collaborationVorgangId(null).createdBy(null).build();
+		private static final CollaborationRequest COLLABORATION_REQUEST = CollaborationRequestTestFactory.createBuilder().collaborationVorgangId(null)
+				.createdBy(null).build();
 
 		@Mock
 		private OzgCloudCommand subCommand;
@@ -225,4 +233,21 @@ class CollaborationServiceTest {
 			return service.buildCreateCollaborationRequestRequest(COLLABORATION_REQUEST);
 		}
 	}
+
+	@DisplayName("Write file content")
+	@Nested
+	class TestWriteFileContent {
+
+		@Mock
+		private OutputStream stream;
+
+		private final OzgCloudFileId fileId = OzgCloudFileId.from(UUID.randomUUID().toString());
+
+		@Test
+		void shouldCallFileService() {
+			service.writeFileContent(fileId, stream);
+
+			verify(fileService).writeFileDataToStream(fileId, stream);
+		}
+	}
 }
\ No newline at end of file
diff --git a/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloaderTest.java b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloaderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a20435f5a258fb348bcc0c11a5abf3b9a5e5372
--- /dev/null
+++ b/collaboration-manager-server/src/test/java/de/ozgcloud/collaboration/common/grpc/GrpcDownloaderTest.java
@@ -0,0 +1,335 @@
+package de.ozgcloud.collaboration.common.grpc;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.google.protobuf.ByteString;
+
+import de.ozgcloud.apilib.file.OzgCloudFileId;
+import de.ozgcloud.collaboration.vorgang.FileTestFactory;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import io.grpc.stub.ClientCallStreamObserver;
+import lombok.SneakyThrows;
+
+class GrpcDownloaderTest {
+
+	@SuppressWarnings("unchecked")
+	private ClientCallStreamObserver<GrpcResponseDummy> callObserver = Mockito.mock(ClientCallStreamObserver.class);
+	@SuppressWarnings("unchecked")
+	private Function<ByteString, GrpcResponseDummy> chunkBuilder = Mockito.mock(Function.class);
+	@SuppressWarnings("unchecked")
+	private Consumer<OutputStream> downloadConsumer = Mockito.mock(Consumer.class);
+	private TaskExecutor taskExecutor = mock(TaskExecutor.class);
+
+	private OzgCloudFileId fileId = FileTestFactory.ID;
+
+	@Spy
+	private GrpcDownloader<GrpcResponseDummy> downloader = GrpcDownloader.<GrpcResponseDummy>builder()
+			.callObserver(callObserver)
+			.fileId(fileId)
+			.downloadConsumer(downloadConsumer)
+			.chunkBuilder(chunkBuilder)
+			.taskExecutor(taskExecutor)
+			.build();
+
+	@DisplayName("Start")
+	@Nested
+	class TestStart {
+
+		@SneakyThrows
+		@Test
+		void shouldCallDoStart() {
+			doNothing().when(downloader).doStart();
+
+			downloader.start();
+
+			verify(downloader).doStart();
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCallHandleSafety() {
+			doNothing().when(downloader).doStart();
+
+			downloader.start();
+
+			verify(downloader).handleSafety(any(ExceptionRunnable.class));
+		}
+
+		@DisplayName("do")
+		@Nested
+		class TestDoStart {
+
+			@Captor
+			private ArgumentCaptor<Runnable> runnableCaptor;
+
+			@SneakyThrows
+			@Test
+			void shouldSetupStreams() {
+				downloader.doStart();
+
+				verify(downloader).setupStreams();
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCallTaskExecutor() {
+				downloader.doStart();
+
+				verify(taskExecutor).execute(any());
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldStartDownload() {
+				doNothing().when(downloader).startDownload();
+
+				downloader.doStart();
+
+				verify(taskExecutor).execute(runnableCaptor.capture());
+				runnableCaptor.getValue().run();
+				verify(downloader).startDownload();
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldSetOnReadyHandler() {
+				downloader.doStart();
+
+				verify(callObserver).setOnReadyHandler(runnableCaptor.capture());
+				assertThat(runnableCaptor.getValue()).isNotNull();
+			}
+		}
+	}
+
+	@DisplayName("Start download")
+	@Nested
+	class TestStartDownload {
+
+		@SneakyThrows
+		@Test
+		void shouldCallHandleSafety() {
+			doNothing().when(downloader).doDownload();
+
+			downloader.startDownload();
+
+			verify(downloader).handleSafety(any(ExceptionRunnable.class));
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCallDoDownoad() {
+			doNothing().when(downloader).doDownload();
+
+			downloader.startDownload();
+
+			verify(downloader).doDownload();
+		}
+
+		@DisplayName("do")
+		@Nested
+		class TestDoDownload {
+
+			@Mock
+			private PipedOutputStream outputStream;
+
+			@BeforeEach
+			void mock() {
+				ReflectionTestUtils.setField(downloader, "outputStream", outputStream);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCallDownloadConsumer() {
+				downloader.doDownload();
+
+				verify(downloadConsumer).accept(outputStream);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCloseOutputstream() {
+				downloader.doDownload();
+
+				verify(outputStream).close();
+			}
+		}
+	}
+
+	@DisplayName("On ready handler")
+	@Nested
+	class TestOnReadyHandler {
+
+		@Test
+		void shouldSendChunksIfCallObserverIsReady() {
+			when(callObserver.isReady()).thenReturn(true);
+			doNothing().when(downloader).sendChunks();
+
+			downloader.onReadyHandler();
+
+			verify(downloader).sendChunks();
+		}
+	}
+
+	@DisplayName("Send chunks")
+	@Nested
+	class TestSendChunks {
+
+		@SneakyThrows
+		@Test
+		void shouldCallHandleSafety() {
+			doNothing().when(downloader).doSendChunks();
+
+			downloader.sendChunks();
+
+			verify(downloader).handleSafety(any(ExceptionRunnable.class));
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCallDoDownoad() {
+			doNothing().when(downloader).doSendChunks();
+
+			downloader.sendChunks();
+
+			verify(downloader).doSendChunks();
+		}
+
+		@DisplayName("do")
+		@Nested
+		class TestDoSendChunks {
+
+			@Mock
+			private PipedInputStream inputStream;
+
+			private final int data = 20;
+
+			@BeforeEach
+			void mock() {
+				ReflectionTestUtils.setField(downloader, "inputStream", inputStream);
+			}
+
+			@SneakyThrows
+			@DisplayName("should send next chunk if callObserver is ready and stream already received data")
+			@Test
+			void shouldCallOnNext() {
+				when(callObserver.isReady()).thenReturn(true);
+				when(inputStream.read(any())).thenReturn(data, -1);
+
+				downloader.doSendChunks();
+
+				verify(callObserver).onNext(any());
+			}
+
+			@SneakyThrows
+			@DisplayName("should call complete grpc stream if download has finished and stream has no data left")
+			@Test
+			void shouldCallOnCompleted() {
+				ReflectionTestUtils.setField(downloader, "downloadInProgress", new AtomicBoolean(false));
+
+				downloader.doSendChunks();
+
+				verify(callObserver).onCompleted();
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCloseInputStream() {
+				ReflectionTestUtils.setField(downloader, "downloadInProgress", new AtomicBoolean(false));
+
+				downloader.doSendChunks();
+
+				verify(inputStream).close();
+			}
+		}
+	}
+
+	@DisplayName("Handle safety")
+	@Nested
+	class TestHandleSafety {
+
+		@DisplayName("on exception")
+		@Nested
+		class TestOnException {
+
+			@Mock
+			private PipedOutputStream outputStream;
+			@Mock
+			private PipedInputStream inputStream;
+
+			private final IOException exception = new IOException();
+
+			@SneakyThrows
+			@BeforeEach
+			void mock() {
+				ReflectionTestUtils.setField(downloader, "inputStream", inputStream);
+				ReflectionTestUtils.setField(downloader, "outputStream", outputStream);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThatThrownBy(this::handleSafety)
+						.isInstanceOf(TechnicalException.class)
+						.extracting(exception -> exception.getCause()).isEqualTo(exception);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCloseOutputStream() {
+				try {
+					handleSafety();
+				} catch (Exception e) {
+					// do nothing
+				}
+
+				verify(outputStream).close();
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCloseInputStream() {
+				try {
+					handleSafety();
+				} catch (Exception e) {
+					// do nothing
+				}
+
+				verify(inputStream).close();
+			}
+
+			private void handleSafety() {
+				downloader.handleSafety(this::dummyMethodThrowingException);
+			}
+
+			private void dummyMethodThrowingException() throws IOException {
+				throw exception;
+			}
+		}
+
+	}
+
+	class GrpcResponseDummy {
+	}
+}
\ No newline at end of file