diff --git a/bescheid-manager/pom.xml b/bescheid-manager/pom.xml
index c33028eb7bd0a0344ff922ca671c29468d84b1b8..fd462edc30289980adea15766cf8b3aff47c2313 100644
--- a/bescheid-manager/pom.xml
+++ b/bescheid-manager/pom.xml
@@ -30,7 +30,7 @@
 	<parent>
 		<groupId>de.ozgcloud.common</groupId>
 		<artifactId>ozgcloud-common-parent</artifactId>
-		<version>4.7.0</version>
+		<version>4.9.0-SNAPSHOT</version>
 		<relativePath />
 	</parent>
 
@@ -41,9 +41,9 @@
 	<inceptionYear>2020</inceptionYear>
 
 	<properties>
-		<vorgang-manager.version>2.21.0-SNAPSHOT</vorgang-manager.version>
-		<nachrichten-manager.version>2.15.0</nachrichten-manager.version>
-		<document-manager.version>1.1.0</document-manager.version>
+		<vorgang-manager.version>2.20.0-SNAPSHOT</vorgang-manager.version>
+		<nachrichten-manager.version>2.16.0-SNAPSHOT</nachrichten-manager.version>
+		<document-manager.version>1.2.0-SNAPSHOT</document-manager.version>
 		<api-lib.version>0.14.0</api-lib.version>
 		<spring-cloud-config-client.version>4.1.3</spring-cloud-config-client.version>
 	</properties>
diff --git a/vorgang-manager-base/pom.xml b/vorgang-manager-base/pom.xml
index 5648a38b31f2d21e021bc10ae6806161694f2924..b2c2b11f6ce7f25b29e1d56c1ca2ea3b74652308 100644
--- a/vorgang-manager-base/pom.xml
+++ b/vorgang-manager-base/pom.xml
@@ -31,7 +31,7 @@
 	<parent>
 		<groupId>de.ozgcloud.common</groupId>
 		<artifactId>ozgcloud-common-parent</artifactId>
-		<version>4.7.0</version>
+		<version>4.9.0-SNAPSHOT</version>
 		<relativePath />
 	</parent>
 
diff --git a/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java b/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java
index 034260a3895c4a986e77f2a34d2fc32c78dd8a70..72cc88019d829bb1df5a2487979ff6b497093f6b 100644
--- a/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java
+++ b/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java
@@ -55,6 +55,7 @@ public class TestCallContextAttachingInterceptor implements ClientInterceptor {
 		public void start(Listener<RespT> responseListener, Metadata headers) {
 			var metadata = new Metadata();
 			metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_CLIENT_NAME), clientName.getBytes());
+			metadata.put(GrpcUtil.HEADER_KEY_USER_ID, UserTestFactory.ID.getBytes());
 			metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_ACCESS_LIMITED_ORGAID), organisationEinheitId.getBytes());
 			metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_ACCESS_LIMITED),
 					organisationEinheitIdCheckNecessary.toString().getBytes());
diff --git a/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto b/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto
new file mode 100644
index 0000000000000000000000000000000000000000..1a88a3cf2e096d6c1501e92c0a74e6599d5150d2
--- /dev/null
+++ b/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+syntax = "proto3";
+
+package de.ozgcloud.vorgang.grpc.binaryFile;
+
+import "callcontext.proto";
+import "file.model.proto";
+
+option java_multiple_files = true;
+option java_package = "de.ozgcloud.vorgang.grpc.binaryFile";
+option java_outer_classname = "BinaryFileModelProto";
+
+message GrpcBinaryFilesRequest {
+  de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
+  repeated string fileId = 2;
+}
+
+message GrpcFindFilesResponse {
+  repeated de.ozgcloud.vorgang.grpc.file.GrpcOzgFile file = 1;
+}
+
+message GrpcBinaryFileDataRequest {
+  de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
+  string fileId = 2;
+}
+
+message GrpcUploadBinaryFileRequest {
+  oneof request {
+    GrpcUploadBinaryFileMetaData metadata = 1;
+    bytes fileContent = 2;
+  }
+}
+
+message GrpcUploadBinaryFileMetaData {
+  de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
+  string vorgangId = 2;
+  string field = 3;
+  string fileName = 4;
+  string contentType = 5;
+  int64 size = 6 [deprecated = true];
+}
+
+message GrpcUploadBinaryFileResponse {
+  string fileId = 2;
+}
+
+message GrpcGetBinaryFileDataRequest {
+  de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
+  string fileId = 2;
+}
+
+message GrpcGetBinaryFileDataResponse {
+  bytes fileContent = 1;
+}
+
+message GrpcBinaryFile {
+  string id = 1;
+  string name = 2;
+  int64 size = 3;
+  string contentType = 4;
+}
+
+message GrpcUpdateBinaryFileRequest {
+  oneof request {
+    GrpcBinaryFileMetadata metadata = 1;
+    bytes content = 2;
+  }
+}
+
+message GrpcBinaryFileMetadata {
+  string fileId = 1;
+  string fileName = 2;
+  string contentType = 3;
+}
+
+message GrpcUpdateBinaryFileResponse {}
\ No newline at end of file
diff --git a/vorgang-manager-interface/src/main/protobuf/binaryfile.proto b/vorgang-manager-interface/src/main/protobuf/binaryfile.proto
index b9ff8205436dabe835f429448dcfc3a252524b76..1b555f47d87d9bb8f05d79f2f6daf9991d910f80 100644
--- a/vorgang-manager-interface/src/main/protobuf/binaryfile.proto
+++ b/vorgang-manager-interface/src/main/protobuf/binaryfile.proto
@@ -25,8 +25,7 @@ syntax = "proto3";
 
 package de.ozgcloud.vorgang.grpc.binaryFile;
 
-import "callcontext.proto";
-import "file.model.proto";
+import "binaryfile.model.proto";
 
 option java_multiple_files = true;
 option java_package = "de.ozgcloud.vorgang.grpc.binaryFile";
@@ -42,54 +41,7 @@ service BinaryFileService {
 	
 	rpc FindBinaryFilesMetaData(GrpcBinaryFilesRequest) returns (GrpcFindFilesResponse) {
 	}
-}
-
-message GrpcBinaryFilesRequest {
-	de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
-	repeated string fileId = 2;
-}
-
-message GrpcFindFilesResponse {
-	repeated de.ozgcloud.vorgang.grpc.file.GrpcOzgFile file = 1;
-}
-
-message GrpcBinaryFileDataRequest {
-	de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
-	string fileId = 2;
-}
 
-message GrpcUploadBinaryFileRequest {
-	oneof request {
-    	GrpcUploadBinaryFileMetaData metadata = 1;
-    	bytes fileContent = 2;
+	rpc UpdateBinaryFile(stream GrpcUpdateBinaryFileRequest) returns (GrpcUpdateBinaryFileResponse) {
 	}
 }
-
-message GrpcUploadBinaryFileMetaData {
-	de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
-	string vorgangId = 2;
-	string field = 3;
-	string fileName = 4;
-	string contentType = 5;
-	int64 size = 6 [deprecated=true];
-}
-
-message GrpcUploadBinaryFileResponse {
-	string fileId = 2;
-}
-
-message GrpcGetBinaryFileDataRequest {
-	de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1;
-	string fileId = 2;
-}
-
-message GrpcGetBinaryFileDataResponse {
-	bytes fileContent = 1;
-}
-
-message GrpcBinaryFile {
-	string id = 1;
-	string name = 2;
-	int64 size = 3;
-	string contentType = 4;
-}
\ No newline at end of file
diff --git a/vorgang-manager-server/pom.xml b/vorgang-manager-server/pom.xml
index 8d62130edc126483cdbe565da4114c4588407841..7ab016718e586f5e415376590eaf3265b6486a7a 100644
--- a/vorgang-manager-server/pom.xml
+++ b/vorgang-manager-server/pom.xml
@@ -32,7 +32,7 @@
 	<parent>
 		<groupId>de.ozgcloud.common</groupId>
 		<artifactId>ozgcloud-common-parent</artifactId>
-		<version>4.7.0</version>
+		<version>4.9.0-SNAPSHOT</version>
 		<relativePath />
 	</parent>
 
@@ -55,12 +55,12 @@
 		<user-manager-interface.version>2.12.0</user-manager-interface.version>
 		<bescheid-manager.version>1.24.0-SNAPSHOT</bescheid-manager.version>
 		<processor-manager.version>0.5.0</processor-manager.version>
-		<nachrichten-manager.version>2.15.0</nachrichten-manager.version>
-		<api-lib.version>0.15.0</api-lib.version>
-		<notification-manager.version>2.14.0</notification-manager.version>
+		<nachrichten-manager.version>2.17.0-SNAPSHOT</nachrichten-manager.version>
+		<api-lib.version>0.16.0-SNAPSHOT</api-lib.version>
+		<notification-manager.version>2.15.0-SNAPSHOT</notification-manager.version>
 		<collaboration-manager.version>0.7.0</collaboration-manager.version>
 		<archive-manager.version>0.2.0-SNAPSHOT</archive-manager.version>
-		<document-manager.version>1.1.0</document-manager.version>
+		<document-manager.version>1.2.0-SNAPSHOT</document-manager.version>
 
 		<zip.version>2.11.5</zip.version>
 		<jsoup.version>1.15.3</jsoup.version>
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java
index cc3e98af5730e8a6ba96b32af5d8b716401b13a9..9ae1627de5586094048df4edf11c5ac1be2d79f8 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java
@@ -23,6 +23,7 @@
  */
 package de.ozgcloud.vorgang;
 
+import org.mapstruct.factory.Mappers;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Primary;
@@ -32,6 +33,8 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie
 import de.ozgcloud.apilib.common.command.OzgCloudCommandService;
 import de.ozgcloud.apilib.common.command.grpc.CommandMapper;
 import de.ozgcloud.apilib.common.command.grpc.GrpcOzgCloudCommandService;
+import de.ozgcloud.apilib.vorgang.OzgCloudFileIdMapper;
+import de.ozgcloud.apilib.vorgang.OzgCloudUserIdMapper;
 import de.ozgcloud.vorgang.grpc.command.CommandServiceGrpc;
 import io.mongock.runner.springboot.EnableMongock;
 import net.devh.boot.grpc.client.inject.GrpcClient;
@@ -51,4 +54,14 @@ public class VorgangManagerServerConfiguration {
 		return new GrpcOzgCloudCommandService(commandServiceStub, commandMapper, contextProvider,
 				GrpcOzgCloudCommandService.DEFAULT_COMMAND_REQUEST_THRESHOLD_MILLIS);
 	}
+
+	@Bean
+	OzgCloudFileIdMapper ozgCloudFileIdMapper() {
+		return Mappers.getMapper(OzgCloudFileIdMapper.class);
+	}
+
+	@Bean
+	OzgCloudUserIdMapper ozgCloudUserIdMapper() {
+		return Mappers.getMapper(OzgCloudUserIdMapper.class);
+	}
 }
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java
index 7e417524f868acc569c55663de2d6bef73c164cd..2e2ad5c358ad8bcb3ce8f7f955236cd6efb1b6f8 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java
@@ -23,28 +23,17 @@
  */
 package de.ozgcloud.vorgang.command;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.net.URLConnection;
-import java.util.Base64;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-import jakarta.activation.MimetypesFileTypeMap;
-
-import org.apache.http.entity.ContentType;
 import org.springframework.stereotype.Service;
 
-import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.nachrichten.postfach.AttachmentFile;
+import de.ozgcloud.nachrichten.file.AttachmentFile;
 import de.ozgcloud.nachrichten.postfach.PersistPostfachNachrichtService;
 import de.ozgcloud.nachrichten.postfach.PostfachNachricht;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemService;
-import de.ozgcloud.vorgang.files.FileService;
-import de.ozgcloud.vorgang.files.OzgFile;
-import de.ozgcloud.vorgang.files.UploadedFilesReference;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
@@ -59,10 +48,8 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric
 
 	static final String CLIENT = "OzgCloud_NachrichtenManager";
 	static final String ITEM_NAME = "PostfachMail";
-	static final String ATTACHMENT_NAME = "PostfachAttachment";
 
 	private final VorgangAttachedItemService attachedItemService;
-	private final FileService fileService;
 
 	@Override
 	public void persistNachricht(Optional<String> userId, PostfachNachricht nachricht) {
@@ -91,49 +78,7 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric
 
 	@Override
 	public String persistAttachment(String vorgangId, AttachmentFile attachment) {
-		var contentType = getTypeByFile(attachment);
-		try (var content = attachment.getContent()) {
-			var decContent = Base64.getDecoder().decode(content.readAllBytes());
-
-			return fileService.uploadFileStream(
-					createUploadedFilesReference(vorgangId),
-					createOzgFile(attachment.getName(), contentType, decContent.length),
-					Optional.empty(),
-					new ByteArrayInputStream(decContent)).toString();
-		} catch (IOException e) {
-			throw new TechnicalException("Can not read attached file", e);
-		}
-	}
-
-	UploadedFilesReference createUploadedFilesReference(String vorgangId) {
-		return UploadedFilesReference.builder().vorgangId(vorgangId).client(CLIENT).name(ATTACHMENT_NAME).build();
-	}
-
-	OzgFile createOzgFile(String fileName, String contentType, long size) {
-		return OzgFile.builder().name(fileName).contentType(contentType).size(size).build();
-	}
-
-	String getTypeByFile(AttachmentFile attachmentFile) {
-		var fileNameMap = URLConnection.getFileNameMap();
-
-		return Optional.ofNullable(fileNameMap.getContentTypeFor(attachmentFile.getName())).orElseGet(() -> getTypeByContent(attachmentFile));
-	}
-
-	private String getTypeByContent(AttachmentFile attachmentFile) {
-		try (var contentStream = attachmentFile.getContent()) {
-			return Optional.ofNullable(URLConnection.guessContentTypeFromStream(contentStream))
-					.orElseGet(() -> getByMimeTypes(attachmentFile.getName()));
-		} catch (IOException e) {
-			LOG.warn("IO-Exception while guessing content type", e);
-		}
-		return ContentType.APPLICATION_OCTET_STREAM.toString();
-	}
-
-	// uses map file: src/main/resources/mime.types
-	private String getByMimeTypes(String fileName) {
-		var fileTypeMap = new MimetypesFileTypeMap();
-
-		return fileTypeMap.getContentType(fileName);
+		throw new UnsupportedOperationException("Not implemented. Use a gRPC service instead.");
 	}
 
 	@Override
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java
new file mode 100644
index 0000000000000000000000000000000000000000..aadcacb5de8dcfd2163427d738099acd2ac29c8a
--- /dev/null
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java
@@ -0,0 +1,57 @@
+package de.ozgcloud.vorgang.common.migration;
+
+import org.bson.Document;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.MergeOperation;
+
+import io.mongock.api.annotations.ChangeUnit;
+import io.mongock.api.annotations.Execution;
+import io.mongock.api.annotations.RollbackExecution;
+
+@ChangeUnit(id = "2024-11-27 09:00:00 OZG-6810", order = "M013", author = "ebardin", runAlways = true)
+public class M013_CreateOzgCloudFileCollection { // NOSONAR
+
+	static final String OLD_COLLECTION_NAME = "fs.files";
+	static final String NEW_COLLECTION_NAME = "ozgCloudFile";
+	static final String FIELD_ID = "_id";
+	static final String FIELD_VERSION = "version";
+	static final String FIELD_FILE_NAME = "name";
+	static final String FIELD_CONTENT_TYPE = "contentType";
+	static final String FIELD_CLIENT = "client";
+	static final String FIELD_VORGANG_ID = "vorgangId";
+	static final String FIELD_CREATED_BY = "createdBy";
+	static final String FIELD_FIELD_NAME = "fieldName";
+	static final String FIELD_CONTENT_ID = "contentId";
+	static final String FIELD_SIZE = "size";
+	static final String FIELD_LENGTH = "length";
+	static final String SET_VERSION_EXPRESSION = "{$literal: 0}";
+	static final String SET_CONTENT_ID_EXPRESSION = "{$toString: \"$%s\" }".formatted(FIELD_ID);
+
+	@Execution
+	public void doMigration(MongoTemplate template) {
+		template.aggregate(buildAggregationPipeline(), OLD_COLLECTION_NAME, Document.class);
+	}
+
+	private Aggregation buildAggregationPipeline() {
+		return Aggregation.newAggregation(
+				Aggregation.project(FIELD_ID)
+						.andExpression(SET_VERSION_EXPRESSION).as(FIELD_VERSION)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_VORGANG_ID).as(FIELD_VORGANG_ID)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_CLIENT).as(FIELD_CLIENT)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_FIELD_NAME).as(FIELD_FIELD_NAME)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_CONTENT_TYPE).as(FIELD_CONTENT_TYPE)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_FILE_NAME).as(FIELD_FILE_NAME)
+						.and(RenameUtil.METADATA_KEY + "." + FIELD_CREATED_BY).as(FIELD_CREATED_BY)
+						.andExpression(SET_CONTENT_ID_EXPRESSION).as(FIELD_CONTENT_ID)
+						.and(FIELD_LENGTH).as(FIELD_SIZE),
+				Aggregation.merge().intoCollection(NEW_COLLECTION_NAME).on(FIELD_ID)
+						.whenDocumentsMatch(MergeOperation.WhenDocumentsMatch.keepExistingDocument()).build()
+		);
+	}
+
+	@RollbackExecution
+	public void rollback() {
+		// kein rollback implementiert
+	}
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java
index 7d8b554d9de12c57b9ea771f63a440e3612de492..4b3fb8ad150032313db008bb00b114062279198e 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java
@@ -33,27 +33,27 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.data.mongodb.core.aggregation.Aggregation;
 import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
-import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.stereotype.Repository;
 
 import de.ozgcloud.command.Command;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem;
 import de.ozgcloud.vorgang.common.db.CriteriaUtil;
 import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
+import de.ozgcloud.vorgang.files.OzgCloudFile;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 
 @Repository
 class PolicyRepository {
 
-	private static final String VORANG_ID_AS_OBJECT_ID_FIELD = """
-			{"$addFields" : {"vorgangObjecId" : {"$toObjectId": "$vorgangId"}}}
+	private static final String VORGANG_ID_AS_OBJECT_ID_FIELD = """
+			{"$addFields" : {"vorgangObjectId" : {"$toObjectId": "$vorgangId"}}}
 			""";
-	private static final String VORANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD = """
-			{"$addFields" : {"vorgangObjecId" : {"$toObjectId": "$metadata.vorgangId"}}}
+	private static final String VORGANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD = """
+			{"$addFields" : {"vorgangObjectId" : {"$toObjectId": "$vorgangId"}}}
 			""";
-	private static final AggregationOperation ADD_VORGANG_OBJECT_ID_FIELD = context -> org.bson.Document.parse(VORANG_ID_AS_OBJECT_ID_FIELD);
+	private static final AggregationOperation ADD_VORGANG_OBJECT_ID_FIELD = context -> org.bson.Document.parse(VORGANG_ID_AS_OBJECT_ID_FIELD);
 
-	private static final AggregationOperation ADD_VORGANG_FROM_FILE = context -> org.bson.Document.parse(VORANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD);
+	private static final AggregationOperation ADD_VORGANG_FROM_FILE = context -> org.bson.Document.parse(VORGANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD);
 
 	public static final String FIELD_ORGANISATIONSEINHEIT_ID = "organisationseinheitenId";
 	public static final String FIELD_VORGANG = "vorgang";
@@ -69,12 +69,12 @@ class PolicyRepository {
 	}
 
 	public boolean existsByFileId(String fileId, Collection<String> organisationEinheitenIds) {
-		var resultDocument = executeAggregation(buildAggregation(matchId(fileId), ADD_VORGANG_FROM_FILE), "fs.files");
+		var resultDocument = executeAggregation(buildAggregation(matchId(fileId), ADD_VORGANG_FROM_FILE), OzgCloudFile.class);
 		return resultDocument.map(document -> evaluateOrganisationseinheitId(document, organisationEinheitenIds)).orElse(false);
 	}
 
 	private AggregationOperation matchId(String id) {
-		return Aggregation.match(new Criteria(Vorgang.MONGODB_FIELDNAME_ID).is(id));
+		return Aggregation.match(CriteriaUtil.isId(id));
 	}
 
 	public boolean existsByVorgangAttachedItem(String vorgangAttachedItemId, Collection<String> organisationEinheitenIds) {
@@ -97,7 +97,7 @@ class PolicyRepository {
 	}
 
 	private AggregationOperation lookupVorgang() {
-		return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjecId", "_id", FIELD_VORGANG);
+		return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjectId", "_id", FIELD_VORGANG);
 	}
 
 	private Optional<Document> executeAggregation(Aggregation aggregation, Class<?> inputType) {
@@ -105,11 +105,6 @@ class PolicyRepository {
 		return getResult(results);
 	}
 
-	private Optional<Document> executeAggregation(Aggregation aggregation, String collectionName) {
-		var results = template.aggregate(aggregation, collectionName, Document.class).getRawResults();
-		return getResult(results);
-	}
-
 	Optional<Document> getResult(Document aggregationResult) {
 		return aggregationResult.getList("results", Document.class).stream().findFirst();
 	}
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java
deleted file mode 100644
index ba302ba1cd4b52fd6c4194e155e7bdc0bfe85716..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-import static org.springframework.data.mongodb.core.query.Criteria.*;
-import static org.springframework.data.mongodb.core.query.Query.*;
-
-import java.io.InputStream;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bson.Document;
-import org.bson.types.ObjectId;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.gridfs.GridFsTemplate;
-import org.springframework.data.mongodb.gridfs.GridFsUpload;
-import org.springframework.stereotype.Repository;
-
-import com.mongodb.client.gridfs.model.GridFSFile;
-
-@Repository
-class BinaryFileRepository {
-
-	static final String FILE_NAME = "name";
-	static final String CONTENT_TYPE = "contentType";
-	static final String CLIENT = "client";
-	static final String VORGANG_ID = "vorgangId";
-	static final String CREATED_BY = "createdBy";
-	static final String FIELD_NAME = "fieldName";
-
-	static final String GRID_FS_FIELD_ID = "_id";
-
-	@Autowired
-	private GridFsTemplate gridFsTemplate;
-
-	public FileId addContentStream(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) {
-		GridFsUpload<ObjectId> upload = GridFsUpload.fromStream(content)
-				.metadata(createMetaData(ref, file, userId.orElse(null)))
-				.filename(createFilePath(ref, file))
-				.build();
-		ObjectId id = gridFsTemplate.store(upload);
-
-		return FileId.from(id.toString());
-	}
-
-	String createFilePath(UploadedFilesReference ref, OzgFile file) {
-		return String.format("%s/%s/%s/%s", ref.getVorgangId(), ref.getClient(), ref.getName(), file.getName());
-	}
-
-	Document createMetaData(UploadedFilesReference ref, OzgFile file, String userId) {
-		Document metadata = new Document();
-		metadata.append(VORGANG_ID, ref.getVorgangId());
-		metadata.append(CLIENT, ref.getClient());
-		metadata.append(FIELD_NAME, ref.getName());
-		metadata.append(CONTENT_TYPE, file.getContentType());
-		metadata.append(FILE_NAME, file.getName());
-		metadata.append(CREATED_BY, userId);
-		return metadata;
-	}
-
-	public InputStream getFileContent(FileId fileId) {
-		return gridFsTemplate.getResource(getFile(fileId)).getContent();
-	}
-
-	private Query queryByObjectId(FileId fileId) {
-		return Query.query(Criteria.where(GRID_FS_FIELD_ID).is(fileId.toString()));
-	}
-
-	public Stream<OzgFile> getAll(Iterable<String> fileIds) {
-		return StreamSupport.stream(fileIds.spliterator(), false)
-				.map(FileId::from)
-				.map(this::getFile)
-				.filter(Objects::nonNull)
-				.map(this::mapToOzgFile);
-	}
-
-	public GridFSFile getFile(FileId fileId) {
-		return gridFsTemplate.findOne(queryByObjectId(fileId));
-	}
-
-	Stream<OzgFile> mapGridFsFiles(Iterable<GridFSFile> gridFiles) {
-		return StreamSupport.stream(gridFiles.spliterator(), false).map(this::mapToOzgFile);
-	}
-
-	OzgFile mapToOzgFile(GridFSFile gridFile) {
-		return OzgFile.builder()
-				.id(FileId.from(gridFile.getObjectId().toString()))
-				.vorgangId(gridFile.getMetadata().getString(VORGANG_ID))
-				.name((String) gridFile.getMetadata().getOrDefault(FILE_NAME, StringUtils.EMPTY))
-				.size(gridFile.getLength())
-				.contentType((String) gridFile.getMetadata().getOrDefault(CONTENT_TYPE, StringUtils.EMPTY))
-				.build();
-	}
-
-	public void deleteAllByVorgang(String vorgangId) {
-		gridFsTemplate.delete(query(where("metadata." + VORGANG_ID).is(vorgangId)));
-	}
-}
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java
index 3e4c9d91307a8d6fb305bea807974733e5d1b205..172e30e0274ce53d2aaa4c2cd2d72207aeafc9a3 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java
@@ -35,6 +35,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.stereotype.Repository;
 
 import de.ozgcloud.vorgang.common.db.CriteriaUtil;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 
 @Repository
@@ -49,7 +50,7 @@ class EingangFilesRepository {
 	private static final String FIELD_ATTACHMENT_FILES = DB_FIELDNAME_EINGANGS + ".attachments." + DB_FIELDNAME_FILES;
 	private static final String FIELD_REPRESENTATION_FILES = DB_FIELDNAME_EINGANGS + ".representations";
 
-	public List<OzgFile> findAttachments(String vorgangId, Optional<String> eingangId) {
+	public List<IncomingFile> findAttachments(String vorgangId, Optional<String> eingangId) {
 		return findFiles(buildSearchCriteria(vorgangId, eingangId), buildExtractAttachmentsAggregation());
 	}
 
@@ -65,7 +66,7 @@ class EingangFilesRepository {
 		);
 	}
 
-	public List<OzgFile> findRepresentations(String vorgangId, Optional<String> eingangId) {
+	public List<IncomingFile> findRepresentations(String vorgangId, Optional<String> eingangId) {
 		return findFiles(buildSearchCriteria(vorgangId, eingangId), buildFindRepresentationsAggregation());
 	}
 
@@ -91,10 +92,10 @@ class EingangFilesRepository {
 				Aggregation.replaceRoot(DB_FIELDNAME_FILES));
 	}
 
-	private List<OzgFile> findFiles(Criteria criteria, List<AggregationOperation> extractOperations) {
+	private List<IncomingFile> findFiles(Criteria criteria, List<AggregationOperation> extractOperations) {
 		List<AggregationOperation> operations = new ArrayList<>();
 		operations.add(Aggregation.match(criteria));
 		operations.addAll(extractOperations);
-		return mongoTemplate.aggregate(Aggregation.newAggregation(operations), Vorgang.COLLECTION_NAME, OzgFile.class).getMappedResults();
+		return mongoTemplate.aggregate(Aggregation.newAggregation(operations), Vorgang.COLLECTION_NAME, IncomingFile.class).getMappedResults();
 	}
 }
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java
index c1d7f24fae27e67b8eefd1c8c3c681ad0c61974c..046e90d7556fd5be53960979e3f04ba20d68cc97 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java
@@ -26,78 +26,70 @@ package de.ozgcloud.vorgang.files;
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
 
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import com.mongodb.client.gridfs.model.GridFSFile;
-
-import de.ozgcloud.nachrichten.postfach.BinaryFileService;
 import de.ozgcloud.vorgang.common.security.PolicyService;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
 import lombok.RequiredArgsConstructor;
 
 //TODO make service package protected, as soon PersistPostfachMailByCommandService and FileStreamService is gone
 @Service
 @RequiredArgsConstructor
-public class FileService implements BinaryFileService {
+public class FileService {
 
 	public static final int CHUNK_SIZE = 255 * 1024;
 
 	private final PolicyService policyService;
 	private final EingangFilesRepository repository;
-	private final BinaryFileRepository binaryFileRepository;
+	private final OzgCloudFileRepository ozgCloudFileRepository;
 	private final FileIdMapper fileIdMapper;
 
-	public List<OzgFile> getAttachments(String vorgangId, Optional<String> eingangId) {
+	public List<IncomingFile> getAttachments(String vorgangId, Optional<String> eingangId) {
 		policyService.checkPermission(vorgangId);
 		return repository.findAttachments(vorgangId, eingangId);
 	}
 
-	public List<OzgFile> getRepresentations(String vorgangId, Optional<String> eingangId) {
+	public List<IncomingFile> getRepresentations(String vorgangId, Optional<String> eingangId) {
 		policyService.checkPermission(vorgangId);
 		return repository.findRepresentations(vorgangId, eingangId);
 	}
 
-	public FileId uploadFileStream(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) {
-		return uploadFile(ref, file, userId, content);
+	public FileId saveOzgCloudFile(OzgCloudFile ozgCloudFile) {
+		return fileIdMapper.toFileId(ozgCloudFileRepository.save(ozgCloudFile).getId());
 	}
 
-	@Async
-	public CompletableFuture<FileId> uploadFileStreamAsync(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) {
-		return CompletableFuture.completedFuture(uploadFile(ref, file, userId, content));
+	public void patch(FileId fileId, long version, Map<String, Object> patch) {
+		ozgCloudFileRepository.patch(fileId, version, patch);
 	}
 
-	FileId uploadFile(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) {
-		return binaryFileRepository.addContentStream(ref, file, userId, content);
-	}
-
-	public InputStream getUploadedFileStream(FileId fileId) {
-		return binaryFileRepository.getFileContent(fileId);
+	@Async
+	public CompletableFuture<String> uploadFileContentStreamAsync(UploadedFilesReference ref, InputStream content) {
+		return CompletableFuture.completedFuture(uploadFileContent(ref, content));
 	}
 
-	@Override
-	public InputStream getUploadedFileStream(de.ozgcloud.nachrichten.postfach.FileId fileId) {
-		return binaryFileRepository.getFileContent(FileId.from(fileId.toString()));
+	String uploadFileContent(UploadedFilesReference ref, InputStream content) {
+		return ozgCloudFileRepository.uploadContent(ref, content);
 	}
 
-	@Override
-	public GridFSFile getFile(de.ozgcloud.nachrichten.postfach.FileId fileId) {
-		return binaryFileRepository.getFile(FileId.from(fileId.toString()));
+	public InputStream getUploadedFileStream(FileId fileId) {
+		return ozgCloudFileRepository.getFileContent(fileId);
 	}
 
-	public Stream<OzgFile> findFilesMetaData(Collection<FileId> ids) {
-		return StreamSupport.stream(binaryFileRepository.getAll(mapFileIdToString(ids)).spliterator(), false);
+	public Stream<OzgCloudFile> findOzgCloudFiles(Collection<FileId> ids) {
+		return ozgCloudFileRepository.findAll(ids);
 	}
 
-	private Iterable<String> mapFileIdToString(Collection<FileId> ids) {
-		return ids.stream().map(fileIdMapper::toString).toList();
+	public void deleteAllByVorgang(String vorgangId) {
+		ozgCloudFileRepository.deleteAllByVorgang(vorgangId);
 	}
 
-	public void deleteAllByVorgang(String vorgangId) {
-		binaryFileRepository.deleteAllByVorgang(vorgangId);
+	public void deleteContent(String contentId) {
+		ozgCloudFileRepository.deleteContent(contentId);
 	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java
index 564e6c265acfb593791d50256354a4e01de2f3c9..81d539b400f6bc00bb0795fd0f8351f7b956debb 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java
@@ -24,59 +24,63 @@
 package de.ozgcloud.vorgang.files;
 
 import java.io.BufferedInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 import org.apache.commons.io.IOUtils;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.commons.lang3.StringUtils;
 
 import com.google.protobuf.ByteString;
 
 import de.ozgcloud.common.errorhandling.TechnicalException;
+import de.ozgcloud.vorgang.callcontext.CallContextUser;
+import de.ozgcloud.vorgang.callcontext.CurrentUserService;
+import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
 import de.ozgcloud.vorgang.common.security.PolicyService;
 import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceImplBase;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFilesRequest;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
 import io.grpc.stub.CallStreamObserver;
 import io.grpc.stub.StreamObserver;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 import net.devh.boot.grpc.server.service.GrpcService;
 
 @Log4j2
 @GrpcService
+@RequiredArgsConstructor
 public class GrpcBinaryFileService extends BinaryFileServiceImplBase {
 
-	// FIXME use client from security context
-	static final String CLIENT = "Alfa"; // currently single client is hardcoded
-
-	@Autowired
-	private FileService service;
-	@Autowired
-	private GrpcOzgFileMapper fileMapper;
-	@Autowired
-	private PolicyService policyService;
+	private final FileService service;
+	private final GrpcOzgFileMapper fileMapper;
+	private final PolicyService policyService;
+	private final CurrentUserService currentUserService;
 
 	@Override
 	public void findBinaryFilesMetaData(GrpcBinaryFilesRequest request, StreamObserver<GrpcFindFilesResponse> responseObserver) {
-		var found = service.findFilesMetaData(mapFileIds(request.getFileIdList())).toList();
+		var found = service.findOzgCloudFiles(mapFileIds(request.getFileIdList())).toList();
 
-		policyService.checkPermission(found.stream().map(OzgFile::getVorgangId).collect(Collectors.toSet()));
+		policyService.checkPermission(found.stream().map(OzgCloudFile::getVorgangId).collect(Collectors.toSet()));
 
 		responseObserver.onNext(buildResponseForOzgFiles(found));
 		responseObserver.onCompleted();
 	}
 
-	public GrpcFindFilesResponse buildResponseForOzgFiles(List<OzgFile> files) {
+	GrpcFindFilesResponse buildResponseForOzgFiles(List<OzgCloudFile> files) {
 		var builder = GrpcFindFilesResponse.newBuilder();
 
-		files.stream().map(file -> fileMapper.map(file)).forEach(builder::addFile);
+		files.stream().map(fileMapper::map).forEach(builder::addFile);
 		return builder.build();
 	}
 
@@ -86,10 +90,47 @@ public class GrpcBinaryFileService extends BinaryFileServiceImplBase {
 
 	@Override
 	public StreamObserver<GrpcUploadBinaryFileRequest> uploadBinaryFileAsStream(StreamObserver<GrpcUploadBinaryFileResponse> responseObserver) {
-		return new UploadStreamObserver(responseObserver, service, policyService);
+		return UploadServerStreamObserver.<OzgCloudFile, GrpcUploadBinaryFileRequest>builder()
+				.requestVerifier(fileUploadRequest -> policyService.checkPermission(fileUploadRequest.getMetadata().getVorgangId()))
+				.hasMetadata(GrpcUploadBinaryFileRequest::hasMetadata)
+				.metadataMapper(this::buildOzgCloudFile)
+				.metadataUploader(this::uploadOzgCloudFile)
+				.fileContentUploader(this::uploadFileContent)
+				.getFileContent(fileUploadRequest -> fileUploadRequest.getFileContent().toByteArray())
+				.responseConsumer(fileId -> completeRequest(buildResponse(fileId), responseObserver))
+				.cleanupFileContent(service::deleteContent)
+				.build();
+	}
+
+	FileId uploadOzgCloudFile(OzgCloudFile ozgCloudFile, Optional<String> contentId) {
+		return service.saveOzgCloudFile(updateContentId(ozgCloudFile, contentId));
+	}
+
+	OzgCloudFile buildOzgCloudFile(GrpcUploadBinaryFileRequest fileUploadRequest) {
+		return OzgCloudFile.builder()
+				.vorgangId(fileUploadRequest.getMetadata().getVorgangId())
+				.name(fileUploadRequest.getMetadata().getFileName())
+				.contentType(fileUploadRequest.getMetadata().getContentType())
+				.fieldName(fileUploadRequest.getMetadata().getField())
+				.createdBy(getUserId(fileUploadRequest))
+				.client(getClient(fileUploadRequest))
+				.build();
+	}
+
+	String getUserId(GrpcUploadBinaryFileRequest fileUploadRequest) {
+		return currentUserService.findUser().flatMap(CallContextUser::getUserId)
+				.orElseGet(() -> fileUploadRequest.getMetadata().getContext().getUser().getId());
 	}
 
-	static GrpcUploadBinaryFileResponse buildResponse(FileId fileId) {
+	String getClient(GrpcUploadBinaryFileRequest fileUploadRequest) {
+		return findContextClient().or(() -> getRequestClient(fileUploadRequest)).orElseThrow(() -> new IllegalStateException("Client is missing"));
+	}
+
+	Optional<String> getRequestClient(GrpcUploadBinaryFileRequest fileUploadRequest) {
+		return Optional.of(fileUploadRequest.getMetadata().getContext().getClient());
+	}
+
+	GrpcUploadBinaryFileResponse buildResponse(FileId fileId) {
 		return GrpcUploadBinaryFileResponse.newBuilder().setFileId(fileId.toString()).build();
 	}
 
@@ -102,57 +143,149 @@ public class GrpcBinaryFileService extends BinaryFileServiceImplBase {
 		downloadFile(responseObserver, new BufferedInputStream(fileContent, FileService.CHUNK_SIZE));
 	}
 
-	void downloadFile(final StreamObserver<GrpcGetBinaryFileDataResponse> streamObserver, final InputStream fileStream) {
+	void downloadFile(StreamObserver<GrpcGetBinaryFileDataResponse> streamObserver, InputStream fileStream) {
 		AtomicBoolean doSendFile = new AtomicBoolean(true);
-		final CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver = (CallStreamObserver<GrpcGetBinaryFileDataResponse>) streamObserver;
+		var callObserver = (CallStreamObserver<GrpcGetBinaryFileDataResponse>) streamObserver;
 		callObserver.setOnReadyHandler(() -> sendChunks(callObserver, fileStream, doSendFile));
+		sendChunks(callObserver, fileStream, doSendFile);
 	}
 
-	void sendChunks(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) {
-		try {
-			// TODO remove additional flow control when alfa side has been fixed see
-			// OZG-2631
-			if (doSendFile.get()) {
-				int size = sendNextChunk(callObserver, fileStream);
-
-				if (size < FileService.CHUNK_SIZE) {
-					handleFileEndReached(callObserver, fileStream, doSendFile);
-				}
+	synchronized void sendChunks(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) {
+		if (!doSendFile.get()) {
+			return;
+		}
+		while (callObserver.isReady()) {
+			LOG.debug("observer is ready. sending...");
+			int size = sendNextChunk(callObserver, fileStream);
+			if (size < FileService.CHUNK_SIZE) {
+				handleFileEndReached(callObserver, fileStream, doSendFile);
+				break;
 			}
-		} catch (Exception e) {
-			handleException(fileStream, e);
 		}
+		LOG.debug("Stop sending");
 	}
 
-	int sendNextChunk(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream)
-			throws IOException {
+	int sendNextChunk(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream) {
 		var bytes = new byte[FileService.CHUNK_SIZE];
-		int size = fileStream.read(bytes);
+		try {
+			int size = fileStream.read(bytes);
+			LOG.debug("Read {} bytes from file.", size);
+			if (size > 0) {
+				callObserver.onNext(buildChunkResponse(bytes, size));
+			}
+			return size;
 
-		if (size > 0) {
-			callObserver.onNext(buildChunkResponse(bytes, size));
+		} catch (Exception exception) {
+			IOUtils.closeQuietly(fileStream, e -> LOG.error("InputStream cannot be closed.", e));
+			throw new TechnicalException("Fehler beim Senden des Datei-Chunks", exception);
 		}
-
-		return size;
 	}
 
-	private void handleFileEndReached(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream,
-			AtomicBoolean doSendFile) {
-		IOUtils.closeQuietly(fileStream);
-		callObserver.onCompleted();
+	void handleFileEndReached(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) {
+		LOG.debug("File end reached");
 		doSendFile.getAndSet(false);
+		IOUtils.closeQuietly(fileStream, e -> LOG.error("InputStream cannot be closed.", e));
+		callObserver.onCompleted();
+	}
+
+	GrpcGetBinaryFileDataResponse buildChunkResponse(byte[] bytes, int size) {
+		return GrpcGetBinaryFileDataResponse.newBuilder().setFileContent(ByteString.copyFrom(bytes, 0, size)).build();
+	}
+
+	@Override
+	public StreamObserver<GrpcUpdateBinaryFileRequest> updateBinaryFile(StreamObserver<GrpcUpdateBinaryFileResponse> responseObserver) {
+		return UploadServerStreamObserver.<OzgCloudFile, GrpcUpdateBinaryFileRequest>builder()
+				.requestVerifier(this::verifyUpdateRequest)
+				.hasMetadata(GrpcUpdateBinaryFileRequest::hasMetadata)
+				.metadataMapper(this::buildOzgCloudFile)
+				.metadataUploader(this::patchOzgCloudFile)
+				.fileContentUploader(this::uploadFileContent)
+				.getFileContent(fileUpdateRequest -> fileUpdateRequest.getContent().toByteArray())
+				.responseConsumer(fileId -> completeRequest(GrpcUpdateBinaryFileResponse.getDefaultInstance(), responseObserver))
+				.cleanupFileContent(service::deleteContent)
+				.build();
+	}
 
+	void verifyUpdateRequest(GrpcUpdateBinaryFileRequest fileUpdateRequest) {
+		validateMetadata(fileUpdateRequest);
+		policyService.checkPermissionByFile(fileUpdateRequest.getMetadata().getFileId());
 	}
 
-	private void handleException(InputStream fileStream, Exception e) {
-		LOG.error("Fehler beim Senden des Datei-Chunks", e);
+	void validateMetadata(GrpcUpdateBinaryFileRequest fileUpdateRequest) {
+		if (!fileUpdateRequest.hasMetadata()) {
+			throw new IllegalArgumentException("Metadata is missing");
+		}
+		var metadata = fileUpdateRequest.getMetadata();
+		if (StringUtils.isBlank(metadata.getFileId())) {
+			throw new IllegalArgumentException("File ID is missing");
+		} else if (StringUtils.isBlank(metadata.getFileName())) {
+			throw new IllegalArgumentException("File name is missing");
+		} else if (StringUtils.isBlank(metadata.getContentType())) {
+			throw new IllegalArgumentException("Content type is missing");
+		}
+	}
 
-		IOUtils.closeQuietly(fileStream);
+	OzgCloudFile buildOzgCloudFile(GrpcUpdateBinaryFileRequest fileUpdateRequest) {
+		var fileId = FileId.from(fileUpdateRequest.getMetadata().getFileId());
+		return service.findOzgCloudFiles(List.of(fileId)).map(ozgCloudFile -> updateOzgCloudFile(ozgCloudFile, fileUpdateRequest)).findFirst()
+				.orElseThrow(() -> new NotFoundException(OzgCloudFile.class, fileId));
+	}
 
-		throw new TechnicalException("Fehler beim Senden des Datei-Chunks", e);
+	OzgCloudFile updateOzgCloudFile(OzgCloudFile ozgCloudFile, GrpcUpdateBinaryFileRequest fileUpdateRequest) {
+		return ozgCloudFile.toBuilder()
+				.name(fileUpdateRequest.getMetadata().getFileName())
+				.contentType(fileUpdateRequest.getMetadata().getContentType())
+				.build();
 	}
 
-	GrpcGetBinaryFileDataResponse buildChunkResponse(byte[] bytes, int size) {
-		return GrpcGetBinaryFileDataResponse.newBuilder().setFileContent(ByteString.copyFrom(bytes, 0, size)).build();
+	FileId patchOzgCloudFile(OzgCloudFile ozgCloudFile, Optional<String> newContentId) {
+		var fileId = FileId.from(ozgCloudFile.getId());
+		service.patch(fileId, ozgCloudFile.getVersion(), buildPatch(updateContentId(ozgCloudFile, newContentId)));
+		newContentId.ifPresent(newId -> service.deleteContent(ozgCloudFile.getContentId()));
+		return fileId;
+	}
+
+	OzgCloudFile updateContentId(OzgCloudFile ozgCloudFile, Optional<String> contentId) {
+		return contentId.map(id -> ozgCloudFile.toBuilder().contentId(id).build()).orElse(ozgCloudFile);
+	}
+
+	Map<String, Object> buildPatch(OzgCloudFile ozgCloudFile) {
+		return Map.of(
+				OzgCloudFile.FIELD_FILE_NAME, ozgCloudFile.getName(),
+				OzgCloudFile.FIELD_CONTENT_TYPE, ozgCloudFile.getContentType(),
+				OzgCloudFile.FIELD_CONTENT_ID, ozgCloudFile.getContentId(),
+				OzgCloudFile.FIELD_CREATED_BY, getUserId(),
+				OzgCloudFile.FIELD_CLIENT, getClient()
+		);
+	}
+
+	String getUserId() {
+		return currentUserService.getUser().getUserId().orElseThrow(() -> new IllegalStateException("User ID is missing"));
 	}
+
+	String getClient() {
+		return findContextClient().orElseThrow(() -> new IllegalStateException("Client is missing"));
+	}
+
+	Optional<String> findContextClient() {
+		return currentUserService.findUser().map(CallContextUser::getClientName);
+	}
+
+	CompletableFuture<String> uploadFileContent(OzgCloudFile ozgCloudFile, InputStream pipedInput) {
+		return service.uploadFileContentStreamAsync(buildUploadFilesReferences(ozgCloudFile), pipedInput);
+	}
+
+	UploadedFilesReference buildUploadFilesReferences(OzgCloudFile ozgCloudFile) {
+		return UploadedFilesReference.builder()
+				.vorgangId(ozgCloudFile.getVorgangId())
+				.client(ozgCloudFile.getClient())
+				.fileName(ozgCloudFile.getName())
+				.name(ozgCloudFile.getFieldName()).build();
+	}
+
+	<T> void completeRequest(T response, StreamObserver<T> responseObserver) {
+		responseObserver.onNext(response);
+		responseObserver.onCompleted();
+	}
+
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java
index 7e2f7f5b63dbf8bd35d9869325f9445a6ee12370..40b879e78a2f97c049ccc2aa75d96d0a8dc9a652 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java
@@ -35,6 +35,7 @@ import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsRequest;
 import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsResponse;
 import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsRequest;
 import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsResponse;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
 import io.grpc.stub.StreamObserver;
 import net.devh.boot.grpc.server.service.GrpcService;
 
@@ -49,9 +50,8 @@ public class GrpcFileService extends FileServiceImplBase {
 
 	@Override
 	public void getAttachments(GrpcGetAttachmentsRequest request, StreamObserver<GrpcGetAttachmentsResponse> responseObserver) {
-		List<OzgFile> response = service.getAttachments(getVorgangId(request), getEingangId(request));
-
-		responseObserver.onNext(GrpcGetAttachmentsResponse.newBuilder().addAllFile(fileMapper.map(response)).build());
+		var response = service.getAttachments(getVorgangId(request), getEingangId(request));
+		responseObserver.onNext(buildAttachmentsResponse(response));
 		responseObserver.onCompleted();
 	}
 
@@ -63,11 +63,17 @@ public class GrpcFileService extends FileServiceImplBase {
 		return Optional.of(request.getEingangId()).map(StringUtils::trimToNull);
 	}
 
+
+	GrpcGetAttachmentsResponse buildAttachmentsResponse(List<IncomingFile> files) {
+		var responseBuilder = GrpcGetAttachmentsResponse.newBuilder();
+		files.stream().map(fileMapper::map).forEach(responseBuilder::addFile);
+		return responseBuilder.build();
+	}
+
 	@Override
 	public void getRepresentations(GrpcGetRepresentationsRequest request, StreamObserver<GrpcGetRepresentationsResponse> responseObserver) {
-		List<OzgFile> response = service.getRepresentations(getVorgangId(request), getEingangId(request));
-
-		responseObserver.onNext(GrpcGetRepresentationsResponse.newBuilder().addAllFile(fileMapper.map(response)).build());
+		var response = service.getRepresentations(getVorgangId(request), getEingangId(request));
+		responseObserver.onNext(buildRepresentationsResponse(response));
 		responseObserver.onCompleted();
 	}
 
@@ -79,6 +85,12 @@ public class GrpcFileService extends FileServiceImplBase {
 		return Optional.of(request.getEingangId()).map(StringUtils::trimToNull);
 	}
 
+	GrpcGetRepresentationsResponse buildRepresentationsResponse(List<IncomingFile> files) {
+		var responseBuilder = GrpcGetRepresentationsResponse.newBuilder();
+		files.stream().map(fileMapper::map).forEach(responseBuilder::addFile);
+		return responseBuilder.build();
+	}
+
 	TechnicalException createMissingVorgangIdException() {
 		return new TechnicalException("VorgangId is required");
 	}
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java
index e7eb4da2b1ebabff3f5b632bc224f6d8737888c2..343a545b2d671524e51e0856c822b221d7ba647d 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java
@@ -30,11 +30,13 @@ import org.mapstruct.Mapping;
 import org.mapstruct.NullValueCheckStrategy;
 
 import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
 
 // TODO rename to GrpcBinaryFileMapper
 @Mapper(uses = FileIdMapper.class, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
 interface GrpcOzgFileMapper {
 
+	@Mapping(target = "defaultInstanceForType", ignore = true)
 	@Mapping(target = "mergeFrom", ignore = true)
 	@Mapping(target = "clearField", ignore = true)
 	@Mapping(target = "clearOneof", ignore = true)
@@ -44,7 +46,19 @@ interface GrpcOzgFileMapper {
 	@Mapping(target = "nameBytes", ignore = true)
 	@Mapping(target = "unknownFields", ignore = true)
 	@Mapping(target = "allFields", ignore = true)
-	GrpcOzgFile map(OzgFile file);
+	GrpcOzgFile map(OzgCloudFile file);
 
-	List<GrpcOzgFile> map(List<OzgFile> files);
+	@Mapping(target = "unknownFields", ignore = true)
+	@Mapping(target = "nameBytes", ignore = true)
+	@Mapping(target = "mergeUnknownFields", ignore = true)
+	@Mapping(target = "mergeFrom", ignore = true)
+	@Mapping(target = "idBytes", ignore = true)
+	@Mapping(target = "defaultInstanceForType", ignore = true)
+	@Mapping(target = "contentTypeBytes", ignore = true)
+	@Mapping(target = "clearOneof", ignore = true)
+	@Mapping(target = "clearField", ignore = true)
+	@Mapping(target = "allFields", ignore = true)
+	GrpcOzgFile map(IncomingFile file);
+
+	List<GrpcOzgFile> map(List<OzgCloudFile> files);
 }
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..991d42fb434dd442a19e7eaa68cdda8062117227
--- /dev/null
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.TypeAlias;
+import org.springframework.data.annotation.Version;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.ToString;
+
+@Builder(toBuilder = true)
+@ToString
+@Getter
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+@Document(collection = OzgCloudFile.COLLECTION_NAME)
+@TypeAlias("OzgCloudFile")
+public class OzgCloudFile {
+
+	public static final String COLLECTION_NAME = "ozgCloudFile";
+
+	static final String FIELD_ID = "_id";
+	static final String FIELD_VERSION = "version";
+	static final String FIELD_FILE_NAME = "name";
+	static final String FIELD_CONTENT_TYPE = "contentType";
+	static final String FIELD_SIZE = "size";
+	static final String FIELD_CONTENT_ID = "contentId";
+	static final String FIELD_CLIENT = "client";
+	static final String FIELD_VORGANG_ID = "vorgangId";
+	static final String FIELD_CREATED_BY = "createdBy";
+	static final String FIELD_FIELD_NAME = "fieldName";
+
+	@Id
+	private String id;
+	@Version
+	private long version;
+	private String vorgangId;
+	private String name;
+	private Long size;
+	private String contentType;
+	private String fieldName;
+	private String client;
+	private String contentId;
+	private String createdBy;
+}
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9ec33225bde9b06c4ed58a412a517d291d29660
--- /dev/null
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import static org.springframework.data.mongodb.core.query.Criteria.*;
+import static org.springframework.data.mongodb.core.query.Query.*;
+
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
+import org.springframework.data.mongodb.gridfs.GridFsResource;
+import org.springframework.data.mongodb.gridfs.GridFsTemplate;
+import org.springframework.data.mongodb.gridfs.GridFsUpload;
+import org.springframework.stereotype.Repository;
+
+import com.mongodb.client.gridfs.model.GridFSFile;
+
+import de.ozgcloud.vorgang.common.db.CollisionVerifier;
+import de.ozgcloud.vorgang.common.db.CriteriaUtil;
+import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
+import lombok.RequiredArgsConstructor;
+
+@Repository
+@RequiredArgsConstructor
+class OzgCloudFileRepository {
+
+	static final String GRID_FS_FIELD_ID = "_id";
+
+	private final CollisionVerifier collisionVerifier = new CollisionVerifier(this::existOzgCloudFile);
+
+	private final GridFsTemplate gridFsTemplate;
+	private final MongoOperations mongoOperations;
+
+	public OzgCloudFile save(OzgCloudFile file) {
+		return mongoOperations.save(setContentSize(file));
+	}
+
+	private OzgCloudFile setContentSize(OzgCloudFile file) {
+		return file.toBuilder().size(getContentSize(file.getContentId())).build();
+	}
+
+	public void patch(FileId fileId, long version, Map<String, Object> patch) {
+		var updateResult = mongoOperations.updateFirst(queryByIdAndVersion(fileId, version), buildUpdate(patch), OzgCloudFile.COLLECTION_NAME);
+		collisionVerifier.verify(updateResult, fileId.toString());
+	}
+
+	private Query queryByIdAndVersion(FileId fileId, long version) {
+		return query(CriteriaUtil.whereIdAndVersion(fileId.toString(), version));
+	}
+
+	private UpdateDefinition buildUpdate(Map<String, Object> patch) {
+		var update = new Update();
+		update.inc(OzgCloudFile.FIELD_VERSION);
+		patch.forEach(update::set);
+		Optional.ofNullable(patch.get(OzgCloudFile.FIELD_CONTENT_ID)).map(String.class::cast)
+				.ifPresent(contentId -> update.set(OzgCloudFile.FIELD_SIZE, getContentSize(contentId)));
+		return update;
+	}
+
+	private long getContentSize(String contentId) {
+		var contentGridFsFile = gridFsTemplate.find(queryByObjectId(contentId)).first();
+		if (Objects.isNull(contentGridFsFile)) {
+			throw new NotFoundException(GridFSFile.class, contentId);
+		}
+		return contentGridFsFile.getLength();
+	}
+
+	public String uploadContent(UploadedFilesReference ref, InputStream content) {
+		return gridFsTemplate.store(buildGridFsUpload(ref, content)).toHexString();
+	}
+
+	GridFsUpload<ObjectId> buildGridFsUpload(UploadedFilesReference ref, InputStream content) {
+		return GridFsUpload.fromStream(content).filename(createFilePath(ref)).build();
+	}
+
+	String createFilePath(UploadedFilesReference ref) {
+		return String.format("%s/%s/%s/%s", ref.getVorgangId(), ref.getClient(), ref.getName(), ref.getFileName());
+	}
+
+	public InputStream getFileContent(FileId fileId) {
+		return Optional.ofNullable(mongoOperations.findById(fileId.toString(), OzgCloudFile.class))
+				.map(OzgCloudFile::getContentId)
+				.map(this::getFile)
+				.map(gridFsTemplate::getResource)
+				.map(GridFsResource::getContent)
+				.orElseThrow(() -> new NotFoundException(OzgCloudFile.class, fileId));
+	}
+
+	GridFSFile getFile(String contentId) {
+		return gridFsTemplate.findOne(queryByObjectId(contentId));
+	}
+
+	public Stream<OzgCloudFile> findAll(Collection<FileId> fileIds) {
+		var ids = fileIds.stream().map(FileId::toString).toList();
+		return mongoOperations.stream(query(where(OzgCloudFile.FIELD_ID).in(ids)), OzgCloudFile.class);
+	}
+
+	public void deleteAllByVorgang(String vorgangId) {
+		var deletedOzgCloudFile = mongoOperations.findAllAndRemove(query(where(OzgCloudFile.FIELD_VORGANG_ID).is(vorgangId)), OzgCloudFile.class);
+		deletedOzgCloudFile.stream().map(OzgCloudFile::getContentId).filter(Objects::nonNull).forEach(this::deleteContent);
+	}
+
+	public void deleteContent(String contentId) {
+		gridFsTemplate.delete(queryByObjectId(contentId));
+	}
+
+	private Query queryByObjectId(String contentId) {
+		return Query.query(Criteria.where(GRID_FS_FIELD_ID).is(contentId));
+	}
+
+	private boolean existOzgCloudFile(String fileId) {
+		return mongoOperations.exists(query(where(OzgCloudFile.FIELD_ID).is(fileId)), OzgCloudFile.class);
+	}
+}
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java
similarity index 71%
rename from vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java
rename to vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java
index 23772dd6777e2262ac9c3d48c481ed9b873ac7a9..1f26bf4fee38aff2f57fa74f356d66277e217824 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java
@@ -23,23 +23,11 @@
  */
 package de.ozgcloud.vorgang.files;
 
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.ToString;
+import de.ozgcloud.vorgang.common.errorhandling.FunctionalException;
 
-// TODO: Rename to BinaryFile
+public class OzgCloudFileUploadException extends FunctionalException {
 
-@Builder(toBuilder = true)
-@ToString
-@Getter
-@AllArgsConstructor(access = AccessLevel.PUBLIC)
-public class OzgFile {
-
-	private FileId id;
-	private String vorgangId;
-	private String name;
-	private Long size;
-	private String contentType;
+	public OzgCloudFileUploadException(String message) {
+		super(message, () -> "OZG_CLOUD_FILE.UPLOAD_FAILED");
+	}
 }
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..9821a129bf1383698da976c85fc3d787d92c13d0
--- /dev/null
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.commons.io.IOUtils;
+
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import io.grpc.stub.StreamObserver;
+import lombok.Builder;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+class UploadServerStreamObserver<M, T> implements StreamObserver<T> {
+
+	static final int TIMEOUT_MINUTES = 10;
+
+	private final Consumer<T> requestVerifier;
+	private final Predicate<T> hasMetadata;
+	private final Function<T, M> metadataMapper;
+	private final BiFunction<M, Optional<String>, FileId> metadataUploader;
+	private final BiFunction<M, InputStream, CompletableFuture<String>> fileContentUploader;
+	private final Function<T, byte[]> getFileContent;
+	private final Consumer<FileId> responseConsumer;
+	private final Consumer<String> cleanupFileContent;
+
+	private final AtomicBoolean metadataUploaded = new AtomicBoolean(false);
+	private final AtomicReference<M> fileMetadata = new AtomicReference<>();
+
+	private PipedOutputStream pipedOutput;
+	private PipedInputStream pipedInput;
+	private CompletableFuture<String> contentIdFuture;
+
+	@Builder
+	UploadServerStreamObserver(Consumer<T> requestVerifier, Predicate<T> hasMetadata, Function<T, M> metadataMapper, //NOSONAR
+			BiFunction<M, Optional<String>, FileId> metadataUploader, BiFunction<M, InputStream, CompletableFuture<String>> fileContentUploader,
+			Function<T, byte[]> getFileContent, Consumer<FileId> responseConsumer, Consumer<String> cleanupFileContent) {
+		this.requestVerifier = requestVerifier;
+		this.hasMetadata = hasMetadata;
+		this.metadataMapper = metadataMapper;
+		this.metadataUploader = metadataUploader;
+		this.fileContentUploader = fileContentUploader;
+		this.getFileContent = getFileContent;
+		this.responseConsumer = responseConsumer;
+		this.cleanupFileContent = cleanupFileContent;
+	}
+
+	@Override
+	public synchronized void onNext(T fileUploadRequest) {
+		if (hasMetadata.test(fileUploadRequest)) {
+			setMetadata(fileUploadRequest);
+		} else {
+			if (Objects.isNull(contentIdFuture)) {
+				contentIdFuture = initContentUpload();
+			}
+			storeFileContent(fileUploadRequest);
+		}
+	}
+
+	CompletableFuture<String> initContentUpload() {
+		return fileContentUploader.apply(fileMetadata.get(), initPipedStreams());
+	}
+
+	void setMetadata(T fileUploadRequest) {
+		if (metadataUploaded.getAndSet(true)) {
+			throw new OzgCloudFileUploadException("Attempt to upload multiple files. Multiple uploads are not allowed.");
+		}
+		requestVerifier.accept(fileUploadRequest);
+		fileMetadata.set(metadataMapper.apply(fileUploadRequest));
+	}
+
+	void storeFileContent(T fileUploadRequest) {
+		if (Objects.isNull(fileMetadata.get())) {
+			throw new OzgCloudFileUploadException("File content received before metadata.");
+		}
+		try {
+			pipedOutput.write(getFileContent.apply(fileUploadRequest));
+		} catch (IOException e) {
+			throw new TechnicalException("Error when writing file content.", e);
+		}
+	}
+
+	InputStream initPipedStreams() {
+		try {
+			pipedInput = new PipedInputStream(FileService.CHUNK_SIZE);
+			pipedOutput = new PipedOutputStream(pipedInput);
+			return pipedInput;
+		} catch (IOException e) {
+			throw new TechnicalException("Upload initialization failed", e);
+		}
+	}
+
+	@Override
+	public void onError(Throwable t) {
+		LOG.error("Error happened. Upload stream closed", t);
+		closeOutputPipe();
+		closeInputPipe();
+	}
+
+	@Override
+	public void onCompleted() {
+		LOG.debug("Complete upload request.");
+		closeOutputPipe();
+		var newContentId = getContentId();
+		try {
+			var fileId = metadataUploader.apply(fileMetadata.get(), newContentId);
+			responseConsumer.accept(fileId);
+		} catch (RuntimeException e) {
+			newContentId.ifPresent(cleanupFileContent);
+			throw new TechnicalException("Cannot complete request.", e);
+		}
+	}
+
+	Optional<String> getContentId() {
+		try {
+			return Objects.isNull(contentIdFuture) ? Optional.empty() : Optional.of(contentIdFuture.get(TIMEOUT_MINUTES, TimeUnit.MINUTES));
+		} catch (ExecutionException | TimeoutException e) {
+			throw new TechnicalException("Upload failed", e);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			throw new TechnicalException("Upload was interrupted", e);
+		} finally {
+			closeInputPipe();
+		}
+	}
+
+	void closeOutputPipe() {
+		IOUtils.closeQuietly(pipedOutput, e -> LOG.error("Cannot close output stream", e));
+	}
+
+	void closeInputPipe() {
+		IOUtils.closeQuietly(pipedInput, e -> LOG.error("Cannot close input stream", e));
+	}
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java
deleted file mode 100644
index 92749b32b947afe2a60c63f8fcbebd56db45ea5a..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-import java.io.IOException;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.vorgang.common.security.PolicyService;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
-import io.grpc.stub.StreamObserver;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.extern.log4j.Log4j2;
-
-@Log4j2
-class UploadStreamObserver implements StreamObserver<GrpcUploadBinaryFileRequest> {
-	private final FileService service;
-	private final PolicyService policyService;
-	private final StreamObserver<GrpcUploadBinaryFileResponse> responseObserver;
-	@Getter(value = AccessLevel.PACKAGE)
-	private PipedOutputStream pipedOutput;
-	@Getter(value = AccessLevel.PACKAGE)
-	private PipedInputStream pipedInput;
-
-	private CompletableFuture<FileId> fileIdFuture;
-
-	public UploadStreamObserver(StreamObserver<GrpcUploadBinaryFileResponse> responseObserver, FileService service, PolicyService policyService) {
-		this.responseObserver = responseObserver;
-		this.service = service;
-		this.policyService = policyService;
-	}
-
-	@Override
-	public void onNext(GrpcUploadBinaryFileRequest fileUploadRequest) {
-		if (fileUploadRequest.hasMetadata()) {
-			policyService.checkPermission(Set.of(fileUploadRequest.getMetadata().getVorgangId()));
-			initPipedStreams();
-			fileIdFuture = storeFileMetaData(fileUploadRequest, pipedInput, pipedOutput);
-		} else {
-			storeFileContent(fileUploadRequest, pipedOutput);
-		}
-	}
-
-	void initPipedStreams() {
-		pipedOutput = new PipedOutputStream();
-		pipedInput = new PipedInputStream(FileService.CHUNK_SIZE);
-	}
-
-	void storeFileContent(GrpcUploadBinaryFileRequest fileUploadRequest, PipedOutputStream pipedOutput) {
-		try {
-			pipedOutput.write(fileUploadRequest.getFileContent().toByteArray());
-		} catch (IOException e) {
-			LOG.error("Fehler beim Speichern des Dateiinhalts.", e);
-		}
-	}
-
-	CompletableFuture<FileId> storeFileMetaData(GrpcUploadBinaryFileRequest fileUploadRequest, PipedInputStream pipedInput,
-			PipedOutputStream pipedOutput) {
-		connectInputStreamToOutputStream(pipedInput, pipedOutput);
-
-		return storeMetaData(fileUploadRequest);
-	}
-
-	void connectInputStreamToOutputStream(PipedInputStream is, PipedOutputStream os) {
-		try {
-			is.connect(os);
-		} catch (IOException e) {
-			LOG.warn("Fehler beim Verbinden der Input/Outputs", e);
-		}
-	}
-
-	CompletableFuture<FileId> storeMetaData(GrpcUploadBinaryFileRequest fileUploadRequest) {
-		var userId = fileUploadRequest.getMetadata().getContext().getUser().getId();
-
-		return service.uploadFileStreamAsync(
-				buildUploadFilesReferences(fileUploadRequest),
-				buildOzgFile(fileUploadRequest),
-				Optional.of(userId), pipedInput);
-	}
-
-	UploadedFilesReference buildUploadFilesReferences(GrpcUploadBinaryFileRequest fileUploadRequest) {
-		return UploadedFilesReference.builder()
-				.vorgangId(fileUploadRequest.getMetadata().getVorgangId())
-				.client(GrpcBinaryFileService.CLIENT)
-				.name(fileUploadRequest.getMetadata().getField()).build();
-	}
-
-	OzgFile buildOzgFile(GrpcUploadBinaryFileRequest fileUploadRequest) {
-		return OzgFile.builder()
-				.name(fileUploadRequest.getMetadata().getFileName())
-				.size(fileUploadRequest.getMetadata().getSize())
-				.contentType(fileUploadRequest.getMetadata().getContentType())
-				.build();
-	}
-
-	@Override
-	public void onError(Throwable t) {
-		LOG.error("GrpcLargeFileRequestObserver onError", t);
-		throw new TechnicalException("GrpcLargeFileRequestObserver onError has been called", t);
-	}
-
-	@Override
-	public void onCompleted() {
-		try {
-			pipedOutput.close();
-
-			responseObserver.onNext(GrpcBinaryFileService.buildResponse(fileIdFuture.get(10, TimeUnit.MINUTES)));
-
-			pipedInput.close();
-		} catch (ExecutionException | IOException | TimeoutException e) {
-			handleException(e);
-		} catch (InterruptedException ie) {
-			handleException(ie);
-			Thread.currentThread().interrupt();
-		}
-		responseObserver.onCompleted();
-
-	}
-
-	void handleException(Exception e) {
-		responseObserver.onNext(GrpcUploadBinaryFileResponse.newBuilder().build());
-		throw new TechnicalException(e.getMessage(), e);
-	}
-}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java
index e05596fc8b73ce347a4bb73b58908fc9589bdfe8..614b83882f926724d86da5ddef113dc470e104ef 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java
@@ -33,4 +33,5 @@ public class UploadedFilesReference {
 	private String vorgangId;
 	private String client;
 	private String name;
+	private String fileName;
 }
diff --git a/vorgang-manager-server/src/main/resources/application-dev.yml b/vorgang-manager-server/src/main/resources/application-dev.yml
index 1deba0a36997fd105df02532f5b84151169c7efb..f0354b98680d11d5aeb2605a24bb903960b8e7bc 100644
--- a/vorgang-manager-server/src/main/resources/application-dev.yml
+++ b/vorgang-manager-server/src/main/resources/application-dev.yml
@@ -1,4 +1,3 @@
-
 ozgcloud:
   redirect:
     mail-from: dev-environment@ozg-cloud.de
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java
index 90bf36e78e775f9bdd28cc97b125602749363529..2ab3074d28285281022fc60a97c55056c8a2fb5b 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java
@@ -47,14 +47,9 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.SpyBean;
 import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.gridfs.GridFsTemplate;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.test.annotation.DirtiesContext;
 
-import com.mongodb.client.gridfs.model.GridFSFile;
-
 import de.ozgcloud.apilib.user.OzgCloudUserId;
 import de.ozgcloud.apilib.user.OzgCloudUserProfile;
 import de.ozgcloud.apilib.user.OzgCloudUserProfileService;
@@ -77,6 +72,7 @@ import de.ozgcloud.vorgang.callcontext.CallContextAuthenticationTokenTestFactory
 import de.ozgcloud.vorgang.callcontext.WithMockCustomUser;
 import de.ozgcloud.vorgang.command.CommandService;
 import de.ozgcloud.vorgang.command.CreateCommandRequest;
+import de.ozgcloud.vorgang.files.OzgCloudFile;
 import de.ozgcloud.vorgang.servicekonto.ServiceKontoTestFactory;
 import de.ozgcloud.vorgang.status.StatusChangedEvent;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
@@ -106,8 +102,6 @@ class BescheidITCase {
 
 	@Autowired
 	private MongoOperations mongoOperations;
-	@Autowired
-	private GridFsTemplate gridFsTemplate;
 
 	@MockBean
 	private PostfachRemoteService postfachRemoteService;
@@ -175,9 +169,9 @@ class BescheidITCase {
 					.build();
 		}
 
-		private GridFSFile getBescheidDocumentFile() {
+		private OzgCloudFile getBescheidDocumentFile() {
 			var fileId = getBescheidDocument().getItem().get(Document.FIELD_DOCUMENT_FILE).toString();
-			return gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
+			return mongoOperations.findById(fileId, OzgCloudFile.class);
 		}
 
 		private VorgangAttachedItem getBescheidDocument() {
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java
index b4370662e31e5988269df10a44045aac25258faa..deeab0b4bb03d66fde39686fd94b065e25bd8e64 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java
@@ -44,7 +44,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.gridfs.GridFsTemplate;
 import org.springframework.security.saml2.core.Saml2Error;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.annotation.DirtiesContext;
@@ -62,8 +61,8 @@ import de.ozgcloud.vorgang.VorgangManagerServerApplication;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem.VorgangAttachedItemBuilder;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory;
-import de.ozgcloud.vorgang.files.GridFsTestFactory;
-import de.ozgcloud.vorgang.files.OzgFileTestFactory;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
 import de.ozgcloud.vorgang.servicekonto.PostfachAddressTestFactory;
 import de.ozgcloud.vorgang.servicekonto.ServiceKonto;
 import de.ozgcloud.vorgang.servicekonto.ServiceKontoTestFactory;
@@ -96,7 +95,7 @@ class AntragraumITCase {
 	@Autowired
 	private MongoOperations mongoOperations;
 	@Autowired
-	private GridFsTemplate gridFsTemplate;
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
 
 	@MockBean
 	private Saml2Decrypter decrypter;
@@ -299,7 +298,7 @@ class AntragraumITCase {
 
 		@BeforeEach
 		void prepareDatabase() {
-			fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()).toString();
+			fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId();
 			var savedVorgang = mongoOperations.save(createVorgang(TrustLevel.LEVEL_3), Vorgang.COLLECTION_NAME);
 			vorgangAttachedItem = mongoOperations.save(
 					createPostfachNachrichtVorgangAttachedItem(savedVorgang.getId(), fileId),
@@ -321,9 +320,9 @@ class AntragraumITCase {
 					.setFileId(fileId)
 					.build();
 			var expectedMetadata = GrpcFileMetadata.newBuilder()
-					.setContentType(OzgFileTestFactory.CONTENT_TYPE)
-					.setName(OzgFileTestFactory.NAME)
-					.setSize(OzgFileTestFactory.CONTENT.length)
+					.setContentType(OzgCloudFileTestFactory.CONTENT_TYPE)
+					.setName(OzgCloudFileTestFactory.NAME)
+					.setSize(OzgCloudFileTestFactory.CONTENT.length)
 					.build();
 
 			grpcService.getAttachmentMetadata(request, responseObserver);
@@ -423,7 +422,7 @@ class AntragraumITCase {
 
 		@BeforeEach
 		void prepareDatabase() {
-			fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()).toString();
+			fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId();
 			var savedVorgang = mongoOperations.save(createVorgang(TrustLevel.LEVEL_3), Vorgang.COLLECTION_NAME);
 			vorgangAttachedItem = mongoOperations.save(
 					createPostfachNachrichtVorgangAttachedItem(savedVorgang.getId(), fileId),
@@ -453,7 +452,7 @@ class AntragraumITCase {
 					.setNachrichtId(vorgangAttachedItem.getId())
 					.setFileId(fileId)
 					.build();
-			var expectedContent = ByteString.copyFrom(OzgFileTestFactory.CONTENT);
+			var expectedContent = ByteString.copyFrom(OzgCloudFileTestFactory.CONTENT);
 
 			grpcService.getAttachmentContent(request, responseObserver);
 
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java
index a5df8366cf4bfb43af9718f5103288244db9f704..eb0983c1cd56dc31bb4d9f4918502dff4407dcab 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java
@@ -55,6 +55,8 @@ import org.springframework.test.web.client.ExpectedCount;
 import org.springframework.test.web.client.MockRestServiceServer;
 import org.springframework.web.client.RestTemplate;
 
+import com.thedeanda.lorem.LoremIpsum;
+
 import de.ozgcloud.common.test.DataITCase;
 import de.ozgcloud.nachrichten.common.grpc.NachrichtenCallContextAttachingInterceptor;
 import de.ozgcloud.nachrichten.postfach.osi.MessageAttachmentTestFactory;
@@ -68,7 +70,8 @@ import de.ozgcloud.vorgang.command.CommandService;
 import de.ozgcloud.vorgang.common.security.PolicyService;
 import de.ozgcloud.vorgang.files.FileId;
 import de.ozgcloud.vorgang.files.FileService;
-import de.ozgcloud.vorgang.files.OzgFile;
+import de.ozgcloud.vorgang.files.OzgCloudFile;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 import io.grpc.stub.StreamObserver;
@@ -77,8 +80,9 @@ import io.grpc.stub.StreamObserver;
 		"grpc.server.in-process-name=postfachitcase",
 		"grpc.client.ozgcloud-command-manager.address=in-process:postfachitcase",
 		"grpc.client.command-manager.address=in-process:postfachitcase",
-		"grpc.client.-manager.address=in-process:postfachitcase",
+		"grpc.client.vorgang-manager.address=in-process:postfachitcase",
 		"grpc.client.pluto.address=in-process:postfachitcase",
+		"grpc.client.file-manager.address=in-process:postfachitcase",
 		"ozgcloud.osi.postfach.proxyapi.url=http://localhost/ApiProxy/V1/Message",
 		"ozgcloud.osi.postfach.proxyapi.key=1234",
 		"ozgcloud.osi.postfach.proxyapi.realm=test-realm",
@@ -100,6 +104,8 @@ class PostfachMailITCase {
 	private RestTemplate restTemplate;
 	@Autowired
 	private FileService fileService;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
 
 	@MockBean
 	private PolicyService policyService;
@@ -142,11 +148,20 @@ class PostfachMailITCase {
 	@Nested
 	class TestSendingMail {
 
+		private static final String ATTACHMENT_CONTENT = LoremIpsum.getInstance().getWords(4);
+
 		@Mock
 		private StreamObserver<GrpcSendPostfachMailResponse> responseObserver;
 		@Mock
 		private StreamObserver<GrpcResendPostfachMailResponse> resendResponseObserver;
 
+		private OzgCloudFile ozgCloudFile;
+
+		@BeforeEach
+		void init() {
+			ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile();
+		}
+
 		@Test
 		void shouldCallOsiPostfachServer() {
 			mockServerSendSuccess();
@@ -218,7 +233,9 @@ class PostfachMailITCase {
 		private GrpcSendPostfachMailRequest buildSendPostfachMailRequestWithoutAttachments(String postfachType) {
 			var grpcPostfachMail = GrpcPostfachMailTestFactory.createBuilder().clearId().clearAttachment().clearCreatedAt().clearDirection()
 					.setPostfachAddress(GrpcPostfachAddressTestFactory.createBuilder().setServiceKontoType(postfachType).build())
-					.setVorgangId(vorgang.getId()).build();
+					.setVorgangId(vorgang.getId())
+					.addAttachment(ozgCloudFile.getId())
+					.build();
 			return GrpcSendPostfachMailRequestTestFactory.createBuilder().setMail(grpcPostfachMail).build();
 		}
 
@@ -334,8 +351,8 @@ class PostfachMailITCase {
 
 				var mails = callGrpcListEndpoint();
 				assertThat(mails).hasSize(1);
-				var binaryFiles = loadAttachments(mails.getFirst());
-				assertThat(binaryFiles).hasSize(1).first().extracting(OzgFile::getContentType)
+				var ozgCloudFiles = loadAttachments(mails.getFirst());
+				assertThat(ozgCloudFiles).hasSize(1).first().extracting(OzgCloudFile::getContentType)
 						.isEqualTo("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
 			}
 
@@ -366,9 +383,9 @@ class PostfachMailITCase {
 				verify(commandService, timeout(60000)).setCommandFinished(any(), any());
 			}
 
-			private List<OzgFile> loadAttachments(GrpcPostfachMail mail) {
+			private List<OzgCloudFile> loadAttachments(GrpcPostfachMail mail) {
 				assertThat(mail.getAttachmentList()).hasSize(1);
-				return fileService.findFilesMetaData((mail.getAttachmentList().stream().map(FileId::from).toList())).toList();
+				return fileService.findOzgCloudFiles((mail.getAttachmentList().stream().map(FileId::from).toList())).toList();
 			}
 
 			private ClientAttribute getHasNewNachrichtAttribute() {
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java
index d9b857b4e886acfbe1454f426cb3e4eeac0be919..d1744dcdccc0ac0f177caed45104ac28594f358c 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java
@@ -41,11 +41,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.annotation.DirtiesContext;
 
 import de.ozgcloud.command.CommandStatus;
 import de.ozgcloud.command.VorgangCreatedEvent;
+import de.ozgcloud.common.test.ITCase;
 import de.ozgcloud.notification.postfach.PostfachService;
+import de.ozgcloud.notification.user.UserNotificationEventListener;
 import de.ozgcloud.processor.processor.ProcessorService;
 import de.ozgcloud.processor.vorgang.Vorgang;
 import de.ozgcloud.processor.vorgang.VorgangId;
@@ -62,11 +64,12 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 		"ozgcloud.processors.0.address=http://localhost:8090/testprocessor",
 		"ozgcloud.processors.0.forms.0.formEngineName=testFormEngine",
 		"ozgcloud.processors.0.forms.0.formId=testForm",
-		"grpc.server.port=9091",
-		"ozgcloud.command-manager.address=static://127.0.0.1:9091"
+		"grpc.server.in-process-name=test",
+		"ozgcloud.command-manager.address=in-process:test",
 }, classes = { VorgangManagerServerApplication.class })
-@ActiveProfiles({ "local", "itcase" })
 @WithMockCustomUser
+@ITCase
+@DirtiesContext
 class ProcessorITCase {
 
 	@Autowired
@@ -79,6 +82,8 @@ class ProcessorITCase {
 	private PostfachService postfachService;
 	@MockBean
 	private de.ozgcloud.notification.vorgang.VorgangService notificationService;
+	@MockBean
+	private UserNotificationEventListener userNotificationEventListener;
 
 	@MockBean
 	private CommandService commandService;
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java
index 5ef251fa5dbd21027b7005cc0bbeedcbf2822385..fa5eada55a5f2ce8c574277bfd632e6e4e85c3ce 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java
@@ -27,8 +27,12 @@ import static org.mockito.Mockito.*;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.gridfs.GridFsTemplate;
 import org.springframework.mail.javamail.JavaMailSender;
 
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
+
 @Configuration
 public class TestConfiguration {
 
@@ -37,4 +41,8 @@ public class TestConfiguration {
 		return mock(JavaMailSender.class);
 	}
 
+	@Bean
+	OzgCloudFileTestUtil ozgCloudFileTestUtil(MongoOperations mongoOperations, GridFsTemplate gridFsTemplate) {
+		return new OzgCloudFileTestUtil(mongoOperations, gridFsTemplate);
+	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java
similarity index 96%
rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java
rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java
index 33e9b67b565d331b23680c5a953b3eb44c00bd05..dc59d07de19615063257e93dee8b8e9fd9adc04f 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
 import de.ozgcloud.common.test.ITCase;
 
 @ITCase
-class VorgangManagerServerApplicationTests {
+class VorgangManagerServerApplicationITCase {
 
 	@Test
 	void contextLoads() { // NOSONAR empty test just starting spring-boot
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java
index 7285590660bcc4f959b39c081ee093131be46848..e080b53a758d72b31dc6b4c688a85da1ade091d3 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java
@@ -38,7 +38,6 @@ import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
-import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -91,9 +90,9 @@ import de.ozgcloud.vorgang.callcontext.WithMockCustomUser;
 import de.ozgcloud.vorgang.command.CommandService;
 import de.ozgcloud.vorgang.command.CommandTestFactory;
 import de.ozgcloud.vorgang.command.CreateCommandRequest;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
 import de.ozgcloud.vorgang.files.FileService;
-import de.ozgcloud.vorgang.files.GridFsTestFactory;
-import de.ozgcloud.vorgang.files.OzgFileTestFactory;
 import de.ozgcloud.vorgang.vorgang.EingangTestFactory;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 import de.ozgcloud.vorgang.vorgang.VorgangHead;
@@ -127,6 +126,8 @@ class CollaborationITCase {
 
 	@Autowired
 	private MongoOperations mongoOperations;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
 
 	@MockBean
 	@Qualifier(CollaborationManagerConfiguration.OZGCLOUD_USER_PROFILE_SERVICE_NAME)
@@ -149,6 +150,7 @@ class CollaborationITCase {
 		mongoOperations.dropCollection(Command.class);
 		mongoOperations.dropCollection(Vorgang.class);
 		mongoOperations.dropCollection(VorgangAttachedItem.class);
+		ozgCloudFileTestUtil.dropFileCollections();
 
 		vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).version(0L).build()).getId();
 	}
@@ -163,44 +165,37 @@ class CollaborationITCase {
 		@Autowired
 		private GridFsTemplate gridFsTemplate;
 
-		@BeforeEach
-		void initDatabase() {
-			gridFsTemplate.delete(new Query());
-		}
-
 		@Test
 		void shouldReturnStoredSmallFile() {
-			var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload());
+			var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId();
 
-			var file = downloadBinaryFile(fileId);
+			var file = downloadOzgCloudFile(fileId);
 
-			assertThat(file).hasSameSizeAs(OzgFileTestFactory.CONTENT).isEqualTo(OzgFileTestFactory.CONTENT);
+			assertThat(file).hasSameSizeAs(OzgCloudFileTestFactory.CONTENT).isEqualTo(OzgCloudFileTestFactory.CONTENT);
 		}
 
 		@Test
 		void shouldReturnStoredFileWithSizeEqualChunkSize() {
-			var content = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE);
-			var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload(content));
+			var content = OzgCloudFileTestFactory.createContentInByte(FileService.CHUNK_SIZE);
+			var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(content).getId();
 
-			var file = downloadBinaryFile(fileId);
+			var file = downloadOzgCloudFile(fileId);
 
 			assertThat(file).hasSameSizeAs(content).isEqualTo(content);
 		}
 
 		@Test
 		void shouldReturnStoredLargeFile() {
-			var input = OzgFileTestFactory.createContentInMiB(5);
-			var fileId = gridFsTemplate
-					.store(GridFsTestFactory.createUpload(input));
+			var input = OzgCloudFileTestFactory.createContentInMiB(5);
+			var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(input).getId();
 
-			var downloadedFile = downloadBinaryFile(fileId);
+			var downloadedFile = downloadOzgCloudFile(fileId);
 
 			assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input);
 		}
 
-		private byte[] downloadBinaryFile(ObjectId fileId) {
-			var request = GrpcGetFileContentRequest.newBuilder()
-					.setId(fileId.toHexString()).build();
+		private byte[] downloadOzgCloudFile(String fileId) {
+			var request = GrpcGetFileContentRequest.newBuilder().setId(fileId).build();
 			var it = getServiceStub().getFileContent(request);
 			var content = new ByteArrayOutputStream();
 			while (it.hasNext()) {
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java
deleted file mode 100644
index b8eeba2de71da1036e75f50f40a4bdb5f82765ff..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.command;
-
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.util.function.Supplier;
-
-import org.apache.commons.io.IOUtils;
-
-import de.ozgcloud.nachrichten.postfach.AttachmentFile;
-import de.ozgcloud.nachrichten.postfach.AttachmentFile.AttachmentFileBuilder;
-
-public class AttachmentFileTestFactory {
-
-	public static final String ATTACHMENT_NAME = "test.txt";
-	public static final String CONTENT_TYPE = "text/plain";
-
-	public static final String BASE64_CONTENT = "dGVzdA==";
-	public static final String CONTENT = "test";
-	public static final long SIZE = 4L;
-	public static final Supplier<InputStream> CONTENT_SUPPLIER = () -> IOUtils.toInputStream(CONTENT, Charset.defaultCharset());
-	public static final Supplier<InputStream> BASE64_CONTENT_SUPPLIER = () -> IOUtils.toInputStream(BASE64_CONTENT, Charset.defaultCharset());
-
-	public static AttachmentFile create() {
-		return createBuilder().build();
-	}
-
-	public static AttachmentFileBuilder createBuilder() {
-		return AttachmentFile.builder()
-				.name(ATTACHMENT_NAME)
-				.contentType(CONTENT_TYPE)
-				.content(CONTENT_SUPPLIER);
-	}
-}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java
index 41f9eacf7ac75658088d2a661d9165a706db7e8f..d4ab5e0f24965e7a9a78b49529f278c980a423eb 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java
@@ -27,32 +27,23 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.io.InputStream;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-import org.apache.http.entity.ContentType;
 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.InjectMocks;
 import org.mockito.Mock;
 
+import de.ozgcloud.nachrichten.file.AttachmentFileTestFactory;
 import de.ozgcloud.nachrichten.postfach.GrpcPostfachMailTestFactory;
 import de.ozgcloud.nachrichten.postfach.PostfachAddressTestFactory;
 import de.ozgcloud.nachrichten.postfach.PostfachNachrichtTestFactory;
-import de.ozgcloud.nachrichten.postfach.osi.MessageAttachmentTestFactory;
-import de.ozgcloud.nachrichten.postfach.osi.MessageTestFactory;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemService;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory;
-import de.ozgcloud.vorgang.files.FileId;
-import de.ozgcloud.vorgang.files.FileService;
-import de.ozgcloud.vorgang.files.OzgFile;
-import de.ozgcloud.vorgang.files.UploadedFilesReference;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 
 class PersistPostfachNachrichtByCommandServiceTest {
@@ -61,8 +52,6 @@ class PersistPostfachNachrichtByCommandServiceTest {
 	private PersistPostfachNachrichtByCommandService service;
 	@Mock
 	private VorgangAttachedItemService attachedItemService;
-	@Mock
-	private FileService fileService;
 
 	@DisplayName("Persist nachricht")
 	@Nested
@@ -165,160 +154,16 @@ class PersistPostfachNachrichtByCommandServiceTest {
 		}
 	}
 
-	@Nested
-	class TestUploadedFilesReferenceCreation {
-		@Test
-		void shouldCreateUploadedFilesReference() {
-			UploadedFilesReference ref = service.createUploadedFilesReference(MessageTestFactory.VORGANG_ID);
-
-			assertThat(ref.getVorgangId()).isEqualTo(MessageTestFactory.VORGANG_ID);
-			assertThat(ref.getClient()).isEqualTo(PersistPostfachNachrichtByCommandService.CLIENT);
-			assertThat(ref.getName()).isEqualTo(PersistPostfachNachrichtByCommandService.ATTACHMENT_NAME);
-		}
-	}
-
-	@Nested
-	class TestOzgFileCreation {
-		@Test
-		void shouldCreateOzgFile() {
-			OzgFile file = service.createOzgFile(MessageAttachmentTestFactory.FILENAME, ContentType.APPLICATION_OCTET_STREAM.toString(),
-					MessageAttachmentTestFactory.SIZE);
-
-			assertThat(file.getContentType()).isEqualTo(ContentType.APPLICATION_OCTET_STREAM.toString());
-			assertThat(file.getSize()).isEqualTo(MessageAttachmentTestFactory.SIZE);
-			assertThat(file.getName()).isEqualTo(MessageAttachmentTestFactory.FILENAME);
-		}
-	}
-
-	@DisplayName("On Persisting Attachement")
+	@DisplayName("On Persisting Attachment")
 	@Nested
 	class TestPersistAttachment {
-		@BeforeEach
-		void init() {
-			when(fileService.uploadFileStream(any(), any(), any(), any())).thenReturn(FileId.from("42"));
-		}
-
-		@DisplayName("Upload files Reference")
-		@Nested
-		class TestUploadFilesReference {
-
-			@Captor
-			private ArgumentCaptor<UploadedFilesReference> refCaptor;
-
-			@Test
-			void shouldHaveClientName() {
-				var ref = callService();
-
-				assertThat(ref.getClient()).isEqualTo(PersistPostfachNachrichtByCommandService.CLIENT);
-			}
-
-			@Test
-			void shouldHaveName() {
-				var ref = callService();
-
-				assertThat(ref.getName()).isEqualTo(PersistPostfachNachrichtByCommandService.ATTACHMENT_NAME);
-			}
-
-			@Test
-			void shouldHaveVorgangId() {
-				var ref = callService();
-
-				assertThat(ref.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
-			}
-
-			private UploadedFilesReference callService() {
-				service.persistAttachment(VorgangTestFactory.ID, AttachmentFileTestFactory.create());
-
-				verify(fileService).uploadFileStream(refCaptor.capture(), any(), any(), any());
-
-				return refCaptor.getValue();
-			}
-		}
-
-		@DisplayName("OZG-File")
-		@Nested
-		class TestOzgFile {
-			@Captor
-			private ArgumentCaptor<OzgFile> ozgFileCaptor;
-
-			@Test
-			void shouldHaveName() {
-				var file = callService();
-
-				assertThat(file.getName()).isEqualTo(MessageAttachmentTestFactory.FILENAME);
-			}
-
-			@Test
-			void shouldHaveSize() {
-				var file = callService();
-
-				assertThat(file.getSize()).isEqualTo(MessageAttachmentTestFactory.SIZE);
-			}
-
-			@Test
-			void shouldHaveContentType() {
-				var file = callService();
-
-				assertThat(file.getContentType()).isEqualTo("text/plain");
-			}
-
-			private OzgFile callService() {
-				var attachmentFile = AttachmentFileTestFactory.createBuilder().content(AttachmentFileTestFactory.BASE64_CONTENT_SUPPLIER).build();
-
-				service.persistAttachment(VorgangTestFactory.ID, attachmentFile);
-
-				verify(fileService).uploadFileStream(any(), ozgFileCaptor.capture(), any(), any());
-
-				return ozgFileCaptor.getValue();
-			}
-		}
-
-		@Test
-		void shouldHaveContent() {
-			ArgumentCaptor<InputStream> contentCaptor = ArgumentCaptor.forClass(InputStream.class);
-			var attachment = AttachmentFileTestFactory.createBuilder().content(AttachmentFileTestFactory.BASE64_CONTENT_SUPPLIER).build();
-
-			service.persistAttachment(VorgangTestFactory.ID, attachment);
-
-			verify(fileService).uploadFileStream(any(), any(), any(), contentCaptor.capture());
-			assertThat(contentCaptor.getValue()).hasContent(AttachmentFileTestFactory.CONTENT);
-		}
-
-		@Test
-		void shouldReturnFileId() {
-			var id = service.persistAttachment(VorgangTestFactory.ID, AttachmentFileTestFactory.create());
-
-			assertThat(id).isEqualTo("42");
-		}
-	}
-
-	@Nested
-	class TestGetTypeByFileName {
-		@Test
-		void shouldReturnDocxType() {
-			var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.docx").contentType("word-file").build();
-
-			var type = service.getTypeByFile(attachmentFile);
-
-			assertThat(type).isEqualTo("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
-		}
 
 		@Test
-		void shouldReturnOctedStreamAsUnkown() {
-			var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.qtsch").contentType("lalal").build();
-
-			var type = service.getTypeByFile(attachmentFile);
-
-			assertThat(type).isEqualTo(ContentType.APPLICATION_OCTET_STREAM.toString());
-		}
-
-		@Test
-		void shouldReturnOdtType() {
-			var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.odt").contentType("opendoc file").build();
-
-			var type = service.getTypeByFile(attachmentFile);
+		void shouldThrowException() {
+			var attachmentFile = AttachmentFileTestFactory.create();
 
-			assertThat(type).isEqualTo("application/vnd.oasis.opendocument.text");
+			assertThatThrownBy(() -> service.persistAttachment(VorgangTestFactory.ID, attachmentFile)).isInstanceOf(
+					UnsupportedOperationException.class);
 		}
 	}
 
@@ -345,7 +190,7 @@ class PersistPostfachNachrichtByCommandServiceTest {
 		void shouldThrowException() {
 			assertThatThrownBy(() -> service.findAnswers(serviceKontoType, PostfachAddressTestFactory.STRING_BASED_IDENTIFIER_POSTFACH_ID_VALUE,
 					GrpcPostfachMailTestFactory.REFERENCED_NACHRICHT_ID))
-							.isInstanceOf(UnsupportedOperationException.class);
+					.isInstanceOf(UnsupportedOperationException.class);
 		}
 	}
 }
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..36b17c3d6891bb628c0f6141a38b444bd27af62c
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.common.migration;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.UUID;
+
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.gridfs.GridFsTemplate;
+import org.springframework.data.mongodb.gridfs.GridFsUpload;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.common.test.DataITCase;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
+
+@DataITCase
+class M013_CreateOzgCloudFileCollectionTest { // NOSONAR
+
+	private static final String VORGANG_ID = UUID.randomUUID().toString();
+	private static final String CLIENT = LoremIpsum.getInstance().getWords(1);
+	private static final String FIELD_NAME = LoremIpsum.getInstance().getWords(1);
+	private static final String FILE_NAME = LoremIpsum.getInstance().getWords(1);
+	private static final String CONTENT_TYPE = LoremIpsum.getInstance().getWords(1);
+	private static final String CREATED_BY = UUID.randomUUID().toString();
+	private static final byte[] CONTENT = LoremIpsum.getInstance().getWords(11).getBytes();
+	private static final long SIZE = CONTENT.length;
+
+	@InjectMocks
+	private M013_CreateOzgCloudFileCollection migration;
+
+	@Autowired
+	private MongoTemplate template;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
+	@Autowired
+	private GridFsTemplate gridFsTemplate;
+
+	private String fileId;
+	@BeforeEach
+	void init() {
+		ozgCloudFileTestUtil.dropFileCollections();
+		fileId = uploadFile();
+	}
+
+	@Test
+	void shouldMigrateMetadata() {
+		migration.doMigration(template);
+
+		assertThat(loadOzgCloudFile()).containsAllEntriesOf(createOzgCloudFileFromMetadata(fileId));
+	}
+
+	@Test
+	void shouldLoadContentByContentId() {
+		migration.doMigration(template);
+
+		var ozgCloudFileDocument = loadOzgCloudFile();
+		assertThat(getFileContentByContentId(ozgCloudFileDocument)).hasBinaryContent(CONTENT);
+	}
+
+	@Test
+	void shouldSkipExistingFiles() {
+		var ozgCloudFile = createNewOzgCloudFileFrom(fileId);
+		template.save(ozgCloudFile, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME);
+
+		migration.doMigration(template);
+
+		var ozgCloudFiles = template.findAll(Document.class, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME);
+		assertThat(ozgCloudFiles).hasSize(1).first().usingRecursiveComparison().isEqualTo(ozgCloudFile);
+	}
+
+	private String uploadFile() {
+		var upload = GridFsUpload.fromStream(new ByteArrayInputStream(CONTENT)).metadata(createMetadata()).filename("test").build();
+		return gridFsTemplate.store(upload).toHexString();
+	}
+
+	private Document createNewOzgCloudFileFrom(String fileId) {
+		var ozgCloudFile = createMetadata();
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_ID, new ObjectId(fileId));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID, fileId);
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_VORGANG_ID, UUID.randomUUID().toString());
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CLIENT, LoremIpsum.getInstance().getWords(1));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_FIELD_NAME, LoremIpsum.getInstance().getWords(1));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_TYPE, LoremIpsum.getInstance().getWords(1));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_FILE_NAME, LoremIpsum.getInstance().getWords(1));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CREATED_BY, UUID.randomUUID().toString());
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_SIZE, new Random().nextLong());
+		return ozgCloudFile;
+	}
+
+	private Document createOzgCloudFileFromMetadata(String fileId) {
+		var ozgCloudFile = createMetadata();
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_ID, new ObjectId(fileId));
+		ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID, fileId);
+		return ozgCloudFile;
+	}
+
+	private Document createMetadata() {
+		var metadata = new Document();
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_VORGANG_ID, VORGANG_ID);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CLIENT, CLIENT);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_FIELD_NAME, FIELD_NAME);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_TYPE, CONTENT_TYPE);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_FILE_NAME, FILE_NAME);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CREATED_BY, CREATED_BY);
+		metadata.append(M013_CreateOzgCloudFileCollection.FIELD_SIZE, SIZE);
+		return metadata;
+	}
+
+	private Document loadOzgCloudFile() {
+		return template.findById(fileId, Document.class, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME);
+	}
+
+	private InputStream getFileContentByContentId(Document ozgCloudFile) {
+		return ozgCloudFileTestUtil.getFileContent(ozgCloudFile.getString(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID));
+	}
+
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java
index 76dc79013329c8b7ea0005cecaf19505ccf3a436..b0e25dfd3e1e32bf82d25c412792e08522ad407b 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java
@@ -41,6 +41,8 @@ import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory;
 import de.ozgcloud.vorgang.command.CommandTestFactory;
 import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
+import de.ozgcloud.vorgang.files.OzgCloudFile;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
 import de.ozgcloud.vorgang.files.GridFsTestFactory;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
@@ -53,10 +55,14 @@ class PolicyRepositoryITCase {
 	private PolicyRepository repository;
 	@Autowired
 	private MongoOperations mongoOperations;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
 
 	@BeforeEach
 	void prepareDatabase() {
 		mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
+		mongoOperations.dropCollection(OzgCloudFile.class);
+
 	}
 
 	@DisplayName("Match organisationEinheitenId")
@@ -145,18 +151,18 @@ class PolicyRepositoryITCase {
 		@Nested
 		class TestByFileId {
 
+			private OzgCloudFile ozgCloudFile;
+
 			@BeforeEach
 			void initDatabase() {
-				mongoOperations.dropCollection("fs.files");
-
-				mongoOperations.save(GridFsTestFactory.create(), "fs.files");
+				ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile();
 			}
 
 			@Test
 			void shouldReturnTrueIfMatch() {
 				mongoOperations.save(VorgangTestFactory.create());
 
-				var result = repository.existsByFileId(GridFsTestFactory.ID.toHexString(),
+				var result = repository.existsByFileId(ozgCloudFile.getId(),
 						List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID));
 
 				assertThat(result).isTrue();
@@ -172,5 +178,6 @@ class PolicyRepositoryITCase {
 				assertThat(result).isFalse();
 			}
 		}
+
 	}
 }
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java
index 791ccd2e2b620953e8bb460fb525e97285c5fa7c..aa71570d74282dae1fd253712f2b90c9ad4a81b6 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java
@@ -51,7 +51,7 @@ import de.ozgcloud.vorgang.callcontext.CurrentUserService;
 import de.ozgcloud.vorgang.callcontext.UserTestFactory;
 import de.ozgcloud.vorgang.command.CommandTestFactory;
 import de.ozgcloud.vorgang.files.GridFsTestFactory;
-import de.ozgcloud.vorgang.files.OzgFileTestFactory;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory;
 import de.ozgcloud.vorgang.vorgang.Vorgang;
 import de.ozgcloud.vorgang.vorgang.VorgangService;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
@@ -175,14 +175,14 @@ class PolicyServiceTest {
 				void shouldDoNothingOnAllMatch() {
 					when(repository.existsByFileId(any(), any())).thenReturn(true);
 
-					assertDoesNotThrow(() -> service.checkPermissionByFile(OzgFileTestFactory.ID.toString()));
+					assertDoesNotThrow(() -> service.checkPermissionByFile(OzgCloudFileTestFactory.ID.toString()));
 				}
 
 				@Test
 				void shouldThrowAccessDeniedExceptionOnAllNonMatch() {
 					when(repository.existsByFileId(any(), any())).thenReturn(false);
 
-					var vorgangId = OzgFileTestFactory.ID.toString();
+					var vorgangId = OzgCloudFileTestFactory.ID.toString();
 					assertThrows(AccessDeniedException.class, () -> service.checkPermissionByFile(vorgangId));
 				}
 			}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java
deleted file mode 100644
index e6dc5d8bc2b331972f81955daaddeedd4fbe802f..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-import static org.assertj.core.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Iterator;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.bson.types.ObjectId;
-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.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.gridfs.GridFsResource;
-import org.springframework.data.mongodb.gridfs.GridFsTemplate;
-import org.springframework.test.annotation.DirtiesContext;
-
-import com.google.protobuf.ByteString;
-import com.mongodb.client.gridfs.model.GridFSFile;
-
-import de.ozgcloud.common.test.DataITCase;
-import de.ozgcloud.vorgang.callcontext.TestCallContextAttachingInterceptor;
-import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
-import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
-import de.ozgcloud.vorgang.vorgang.Vorgang;
-import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
-import io.grpc.StatusRuntimeException;
-import io.grpc.stub.StreamObserver;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import net.devh.boot.grpc.client.inject.GrpcClient;
-
-@SpringBootTest(properties = {
-		"grpc.server.inProcessName=binary-file-test",
-		"grpc.server.port=-1",
-		"grpc.client.inProcess.address=in-process:binary-file-test"
-})
-@DirtiesContext
-@DataITCase
-public class BinaryFileITCase {
-
-	@GrpcClient("inProcess")
-	private BinaryFileServiceBlockingStub binaryFileServiceBlockingStub;
-	@GrpcClient("inProcess")
-	private BinaryFileServiceStub binaryFileServiceStub;
-
-	@Autowired
-	private GridFsTemplate gridFsTemplate;
-	@Autowired
-	private MongoOperations mongoOperations;
-
-	@DisplayName("Find binary files meta data")
-	@Nested
-	class TestFindBinaryFilesMetaData {
-		private ObjectId fileId;
-
-		@BeforeEach
-		void initDatabase() {
-			gridFsTemplate.delete(new Query());
-			fileId = gridFsTemplate.store(GridFsTestFactory.createUpload());
-		}
-
-		@Nested
-		class OnMatchingOrganisationEinheitIdAtVorgang {
-
-			@BeforeEach
-			void initDatabase() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.create());
-			}
-
-			@Test
-			void shouldReturnStoredFile() {
-				var response = callFindBinaryFilesMetaData();
-
-				assertThat(response.getFileList()).hasSize(1);
-
-				var file = response.getFileList().get(0);
-
-				assertThat(file.getId()).isEqualTo(fileId.toHexString());
-				assertThat(file.getName()).isEqualTo(OzgFileTestFactory.NAME);
-				assertThat(file.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
-			}
-		}
-
-		@Nested
-		class OnNonMatchingOrganisationEinheitIdOnVorgang {
-
-			@BeforeEach
-			void initDatabase() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
-			}
-
-			@Test
-			void shouldThrowStatusRuntimeException() {
-				assertThrows(StatusRuntimeException.class, () -> callFindBinaryFilesMetaData());
-			}
-		}
-
-		private GrpcFindFilesResponse callFindBinaryFilesMetaData() {
-			return prepareBinaryFileServiceBlockingStub().findBinaryFilesMetaData(
-					GrpcBinaryFilesRequestTestFactory.createBuilder().clearFileId().addFileId(fileId.toHexString()).build());
-		}
-
-	}
-
-	@Nested
-	@DisplayName("Download files of different sizes")
-	class TestDownloadFile {
-
-		@BeforeEach
-		void initDatabase() {
-			gridFsTemplate.delete(new Query());
-		}
-
-		@Nested
-		class OnMatchingOrganisationEinheitIdAtVorgang {
-
-			@BeforeEach
-			void initDatabase() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.create());
-			}
-
-			@Test
-			void shouldReturnStoredSmallFile() {
-				var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload());
-
-				var file = downloadBinaryFile(fileId);
-
-				assertThat(file).hasSameSizeAs(OzgFileTestFactory.CONTENT).isEqualTo(OzgFileTestFactory.CONTENT);
-			}
-
-			@Test
-			void shouldReturnStoredFileWithSizeEqualChunkSize() throws IOException {
-				var content = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE);
-				var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload(content));
-
-				var file = downloadBinaryFile(fileId);
-
-				assertThat(file).hasSameSizeAs(content).isEqualTo(content);
-			}
-
-			@Test
-			void shouldReturnStoredLargeFile() {
-				var input = OzgFileTestFactory.createContentInMiB(5);
-				var fileId = gridFsTemplate
-						.store(GridFsTestFactory.createUpload(input));
-
-				var downloadedFile = downloadBinaryFile(fileId);
-
-				assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input);
-			}
-		}
-
-		@Nested
-		class OnNonMatchingOrganisationEinheitIdAtVorgang {
-
-			@BeforeEach
-			void initDatabase() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
-			}
-
-			@Test
-			void shouldThrowStatusRuntimeException() {
-				var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload());
-
-				assertThrows(StatusRuntimeException.class, () -> downloadBinaryFile(fileId));
-			}
-		}
-
-		private byte[] downloadBinaryFile(ObjectId fileId) {
-			GrpcGetBinaryFileDataRequest request = GrpcGetBinaryFileDataRequest.newBuilder()
-					.setFileId(fileId.toHexString()).build();
-			Iterator<GrpcGetBinaryFileDataResponse> it = prepareBinaryFileServiceBlockingStub().getBinaryFileContent(request);
-			var content = new ByteArrayOutputStream();
-			while (it.hasNext()) {
-				ByteString chunkContent = it.next().getFileContent();
-				try {
-					content.write(chunkContent.toByteArray());
-				} catch (IOException e) {
-					fail(e);
-				}
-			}
-
-			return content.toByteArray();
-		}
-	}
-
-	private BinaryFileServiceBlockingStub prepareBinaryFileServiceBlockingStub() {
-		return binaryFileServiceBlockingStub.withInterceptors(new TestCallContextAttachingInterceptor());
-	}
-
-	@Nested
-	@DisplayName("Upload file")
-	class TestFileUpload {
-		private int onNextCount = 0;
-		private AtomicBoolean hasError = new AtomicBoolean(false);
-		private AtomicBoolean completed = new AtomicBoolean(false);
-		private GrpcUploadBinaryFileMetaData metaData = GrpcUploadBinaryFileMetaDataTestFactory.create();
-		private ByteString fileContent = ByteString.copyFrom(OzgFileTestFactory.CONTENT);
-		private StubStreamObserver responseObserver;
-
-		@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-		class StubStreamObserver implements StreamObserver<GrpcUploadBinaryFileResponse> {
-			private final CompletableFuture<FileId> fileIdFuture;
-			@Getter
-			private String fileId;
-
-			@Override
-			public void onNext(GrpcUploadBinaryFileResponse value) {
-				fileId = value.getFileId();
-				onNextCount += 1;
-			}
-
-			@Override
-			public void onError(Throwable t) {
-				hasError.set(true);
-				fileIdFuture.completeExceptionally(t);
-			}
-
-			@Override
-			public void onCompleted() {
-				completed.set(true);
-				fileIdFuture.complete(FileId.from(fileId));
-			}
-		};
-
-		@BeforeEach
-		void initDatabase() {
-			gridFsTemplate.delete(new Query());
-		}
-
-		@Nested
-		class OnMatchingOrganisationEinheitIdAtVorgang {
-			@BeforeEach
-			void init() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.create());
-			}
-
-			@Test
-			void shouldCallOnNext() {
-				var fileIdFuture = saveFileWithContent();
-
-				assertThat(fileIdFuture).isCompleted();
-				assertThat(onNextCount).isEqualTo(1);
-			}
-
-			@Test
-			void shouldCallOnComplete() {
-				var fileIdFuture = saveFileWithContent();
-
-				assertThat(fileIdFuture).isCompleted();
-				assertThat(completed).isTrue();
-			}
-
-			@Test
-			void shouldHaveFileId() {
-				var fileIdFuture = saveFileWithContent();
-
-				assertThat(fileIdFuture).isCompleted().isDone().isNotNull();
-			}
-
-			@Test
-			void shouldSaveVorgangId() {
-				var fileIdFuture = saveFileWithContent();
-
-				assertThat(fileIdFuture).isCompleted();
-				GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("metadata.vorgangId").is(VorgangTestFactory.ID)));
-				assertThat(file.getMetadata()).containsEntry("vorgangId", VorgangTestFactory.ID);
-			}
-
-			@Test
-			void shouldSaveFileContent() throws Exception {
-				var fileIdFuture = saveFileWithContent();
-
-				assertThat(fileIdFuture).isCompleted();
-				GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("metadata.vorgangId").is(VorgangTestFactory.ID)));
-				GridFsResource resource = gridFsTemplate.getResource(file);
-				try (InputStream fileStream = resource.getContent()) {
-					byte[] fileContentFromDb = fileStream.readAllBytes();
-					assertThat(fileContentFromDb).hasSameSizeAs(fileContent.toByteArray()).isEqualTo(fileContent.toByteArray());
-				}
-
-			}
-		}
-
-		@Nested
-		class OnMatchingNonOrganisationEinheitIdAtVorgang {
-			@BeforeEach
-			void init() {
-				mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
-				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
-			}
-
-			@Test
-			void shouldThowStatusRuntimeException() {
-				var fileIdFuture = saveFileWithContent();
-				assertThat(fileIdFuture).isCompletedExceptionally().isNotCancelled().failsWithin(1, TimeUnit.SECONDS)
-						.withThrowableOfType(ExecutionException.class).withCauseInstanceOf(StatusRuntimeException.class);
-			}
-		}
-
-		private CompletableFuture<FileId> saveFileWithContent() {
-			var fileIdFuture = new CompletableFuture<FileId>();
-			GrpcUploadBinaryFileRequest request1 = GrpcUploadBinaryFileRequest.newBuilder().setMetadata(metaData).build();
-			GrpcUploadBinaryFileRequest request2 = GrpcUploadBinaryFileRequest.newBuilder().setFileContent(fileContent).build();
-
-			responseObserver = new StubStreamObserver(fileIdFuture);
-
-			StreamObserver<GrpcUploadBinaryFileRequest> observer = prepareBinaryFileServiceStub().uploadBinaryFileAsStream(responseObserver);
-
-			observer.onNext(request1);
-			observer.onNext(request2);
-			observer.onCompleted();
-
-			try {
-				fileIdFuture.get(10, TimeUnit.SECONDS);
-			} catch (InterruptedException | ExecutionException | TimeoutException e) {
-				// ignored, the right exception will be checked in the test case
-			}
-
-			return fileIdFuture;
-
-		}
-
-		private BinaryFileServiceStub prepareBinaryFileServiceStub() {
-			return binaryFileServiceStub.withInterceptors(new TestCallContextAttachingInterceptor());
-		}
-	}
-}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java
deleted file mode 100644
index 13253b711bda2839832da3e90ecc1e9ca2c82de7..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-import static org.assertj.core.api.Assertions.*;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Stream;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.mongodb.core.MongoTemplate;
-
-import de.ozgcloud.common.test.DataITCase;
-import de.ozgcloud.vorgang.callcontext.UserTestFactory;
-import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
-import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
-
-@DataITCase
-class BinaryFileRepositoryITCase {
-
-	@Autowired
-	private BinaryFileRepository repository;
-
-	@Autowired
-	private MongoTemplate template;
-
-	@BeforeEach
-	void clearDB() {
-		template.dropCollection("fs.files");
-		template.dropCollection("fs.chunks");
-	}
-
-	@Nested
-	class TestUploadFile {
-		private UploadedFilesReference ref;
-		private OzgFile file;
-
-		@BeforeEach
-		void init() {
-			ref = UploadedFilesReference.builder()
-					.client("client")
-					.name("test.file")
-					.vorgangId(UUID.randomUUID().toString())
-					.build();
-			file = OzgFileTestFactory.create();
-		}
-
-		@Test
-		void shouldStoreFiles() throws IOException {
-			FileId id = storeFile(45);
-
-			assertThat(repository.getFileContent(id)).isNotEmpty();
-		}
-
-		@Test
-		void shouldLoadFile() throws IOException {
-			int savedFileMB = 25;
-			int fileSizeInBytes = savedFileMB * 1_000_000;
-
-			FileId id = storeFile(savedFileMB);
-
-			InputStream in = repository.getFileContent(id);
-			byte[] buffer = in.readAllBytes();
-
-			assertThat(buffer).hasSize(fileSizeInBytes);
-		}
-
-		private FileId storeFile(int sizeInMB) throws IOException {
-			InputStream stream = IncomingFileTestFactory.createContentStreamInMB(sizeInMB);
-
-			FileId id = repository.addContentStream(ref, file, Optional.empty(), stream);
-			stream.close();
-			return id;
-		}
-	}
-
-	@Nested
-	class TestFindAllMetaData {
-
-		private FileId fileId;
-
-		@Nested
-		class TestSingleMetaData {
-
-			@BeforeEach
-			void initData() {
-				fileId = initContentStream(OzgFileTestFactory.create());
-			}
-
-			@Test
-			void shouldReturnResult() {
-				var result = callRepository(List.of(fileId.toString()));
-
-				assertThat(result.count()).isEqualTo(1);
-			}
-
-			@Test
-			void shouldReturnOzgFileData() {
-				var result = callRepository(List.of(fileId.toString()));
-
-				var ozgFile = result.toList().get(0);
-				assertThat(ozgFile.getId()).isEqualTo(fileId);
-				assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME);
-				assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
-				assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
-			}
-		}
-
-		@Nested
-		class TestMultipleMetaData {
-
-			private FileId otherFileId;
-
-			@BeforeEach
-			void initData() {
-				fileId = initContentStream(OzgFileTestFactory.create());
-				otherFileId = initContentStream(OzgFileTestFactory.create());
-			}
-
-			@Test
-			void shouldReturnResult() {
-				var result = callRepository(List.of(fileId.toString(), otherFileId.toString()));
-
-				assertThat(result.count()).isEqualTo(2);
-			}
-
-			@Test
-			void shouldReturnOzgFileData() {
-				var result = callRepository(List.of(fileId.toString(), otherFileId.toString()));
-
-				var ozgFile = result.toList().get(1);
-				assertThat(ozgFile.getId()).isEqualTo(otherFileId);
-				assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME);
-				assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
-				assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
-			}
-
-		}
-
-		private FileId initContentStream(OzgFile ozgFile) {
-			return repository.addContentStream(UploadedFilesReferenceTestFactory.create(), ozgFile, Optional.of(UserTestFactory.ID),
-					new ByteArrayInputStream(OzgFileTestFactory.CONTENT));
-		}
-
-		private Stream<OzgFile> callRepository(Collection<String> fileIds) {
-			return repository.getAll(fileIds);
-		}
-	}
-
-	@Nested
-	class TestDeleteByVorgang {
-		@Test
-		void shouldDeleteFiles() {
-			var fileId = persistFile();
-
-			repository.deleteAllByVorgang(VorgangTestFactory.ID);
-
-			var loaded = repository.getFile(fileId);
-			assertThat(loaded).isNull();
-		}
-
-		@Test
-		void shouldNotDeleteOtherFiles() {
-			var fileId = persistFile();
-
-			repository.deleteAllByVorgang("other");
-
-			var loaded = repository.getFile(fileId);
-			assertThat(loaded).isNotNull();
-		}
-
-		private FileId persistFile() {
-			return repository.addContentStream(UploadedFilesReferenceTestFactory.create(),
-					OzgFileTestFactory.create(),
-					Optional.of(UserTestFactory.ID),
-					new ByteArrayInputStream(OzgFileTestFactory.CONTENT));
-		}
-	}
-}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java
deleted file mode 100644
index e7d6ebd8262a7cc639c68b995c233b3899e18cee..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bson.Document;
-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.data.mongodb.gridfs.GridFsTemplate;
-
-import static org.assertj.core.api.Assertions.*;
-
-import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
-import de.ozgcloud.vorgang.callcontext.UserTestFactory;
-import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
-import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
-
-class BinaryFileRepositoryTest {
-
-	@Spy
-	@InjectMocks
-	private BinaryFileRepository grisFsService = new BinaryFileRepository();
-	@Mock
-	private GridFsTemplate gridFsTemplate;
-
-	@Nested
-	class TestMetaDataCreation {
-
-		@Test
-		void shouldCreateMetaData() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).isNotNull();
-		}
-
-		@Test
-		void shouldHaveVorgangId() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.VORGANG_ID, UploadedFilesReferenceTestFactory.VORGANG_ID);
-		}
-
-		@Test
-		void shouldHaveClient() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.CLIENT, CallContextTestFactory.CLIENT);
-		}
-
-		@Test
-		void shouldHaveContentType() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.CONTENT_TYPE, OzgFileTestFactory.CONTENT_TYPE);
-		}
-
-		@Test
-		void shouldHaveCreatedBY() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.CREATED_BY, UserTestFactory.ID);
-		}
-
-		@Test
-		void shouldHaveFileName() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.FILE_NAME, OzgFileTestFactory.NAME);
-		}
-
-		@Test
-		void shouldHaveFieldName() {
-			var doc = callCreateMetadata();
-
-			assertThat(doc).containsEntry(BinaryFileRepository.FIELD_NAME, UploadedFilesReferenceTestFactory.NAME);
-		}
-
-		private Document callCreateMetadata() {
-			return grisFsService.createMetaData(UploadedFilesReferenceTestFactory.create(), OzgFileTestFactory.create(), UserTestFactory.ID);
-		}
-	}
-
-	@Nested
-	class TestFilePath {
-		@Test
-		void shouldCreatePath() {
-			String path = grisFsService.createFilePath(
-					UploadedFilesReferenceTestFactory.create(),
-					OzgFileTestFactory.create());
-
-			assertThat(path).isEqualTo(
-					UploadedFilesReferenceTestFactory.VORGANG_ID + "/" +
-							CallContextTestFactory.CLIENT + "/" +
-							UploadedFilesReferenceTestFactory.NAME + "/" +
-							OzgFileTestFactory.NAME);
-		}
-	}
-
-	@Nested
-	class TestFindAllMetaData {
-
-		@Test
-		void shouldCallGridFSTemplate() {
-			when(gridFsTemplate.findOne(any())).thenReturn(GridFsTestFactory.create());
-
-			grisFsService.getAll(Collections.singleton(IncomingFileTestFactory.ID.toString())).toList();
-
-			verify(gridFsTemplate).findOne(any());
-		}
-
-		@Test
-		void shouldCallGridFsTemplateFind() {
-			when(gridFsTemplate.findOne(any())).thenReturn(GridFsTestFactory.create());
-
-			var files = grisFsService.getAll(Collections.singleton(IncomingFileTestFactory.ID.toString())).toList();
-
-			assertThat(files).hasSize(1);
-		}
-
-		@Test
-		@DisplayName("should not map to OZG File when FileId is empty")
-		void shouldNotMapWhenFileIdEmpty() {
-			grisFsService.getAll(List.of(StringUtils.EMPTY)).toList();
-
-			verify(grisFsService, never()).mapToOzgFile(any());
-		}
-
-		@Test
-		@DisplayName("should not map to OZG File when FileId is null")
-		void shouldNotMapWhenFileIdNull() {
-			grisFsService.getAll(Collections.singletonList(null)).toList();
-
-			verify(grisFsService, never()).mapToOzgFile(any());
-		}
-
-		@Nested
-		class TestMapToOzgFile {
-
-			@Test
-			void shouldContainFileId() {
-				var ozgFile = callRepositoryMap();
-
-				assertThat(ozgFile.getId()).hasToString(GridFsTestFactory.ID.toHexString());
-			}
-
-			@Test
-			void shouldContainVorgangId() {
-				var ozgFile = callRepositoryMap();
-
-				assertThat(ozgFile.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
-			}
-
-			@Test
-			void shouldContainName() {
-				var ozgFile = callRepositoryMap();
-
-				assertThat(ozgFile.getName()).hasToString(OzgFileTestFactory.NAME);
-			}
-
-			@Test
-			void shouldContainSize() {
-				var ozgFile = callRepositoryMap();
-
-				assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
-			}
-
-			@Test
-			void shouldContainContentType() {
-				var ozgFile = callRepositoryMap();
-
-				assertThat(ozgFile.getContentType()).hasToString(OzgFileTestFactory.CONTENT_TYPE);
-			}
-
-			private OzgFile callRepositoryMap() {
-				return grisFsService.mapToOzgFile(GridFsTestFactory.create());
-			}
-		}
-	}
-}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java
index dc5fa3c197e1f58b41fd2274e561ad92bf0d5b20..513408b4ca51675df07163df98b8bd3093235872 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java
@@ -103,7 +103,7 @@ class EingangFilesRepositoryITCase {
 				assertThat(result).isEmpty();
 			}
 
-			private List<OzgFile> findAttachments(String vorgangId) {
+			private List<IncomingFile> findAttachments(String vorgangId) {
 				return repository.findAttachments(vorgangId, Optional.empty());
 			}
 		}
@@ -187,7 +187,7 @@ class EingangFilesRepositoryITCase {
 				assertThat(result).isEmpty();
 			}
 
-			private List<OzgFile> findRepresentations(String vorgangId) {
+			private List<IncomingFile> findRepresentations(String vorgangId) {
 				return repository.findRepresentations(vorgangId, Optional.empty());
 			}
 		}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java
index 598d57dd26460240fce6949aef4ec9e59efb9a94..04015d85e9c98f4cbd89ca0b4de4d57af95c73bd 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java
@@ -33,6 +33,8 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -44,8 +46,10 @@ import org.mockito.Spy;
 
 import de.ozgcloud.vorgang.common.security.PolicyService;
 import de.ozgcloud.vorgang.vorgang.EingangTestFactory;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
 import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
+import lombok.SneakyThrows;
 
 class FileServiceTest {
 
@@ -58,103 +62,120 @@ class FileServiceTest {
 	@Mock
 	private EingangFilesRepository repository;
 	@Mock
-	private BinaryFileRepository binaryFileRepository;
+	private OzgCloudFileRepository ozgCloudFileRepository;
 	@Mock
 	private FileIdMapper fileIdMapper;
 
 	private final UploadedFilesReference ref = UploadedFilesReferenceTestFactory.create();
-	private final Optional<String> user = Optional.empty();
-	private final OzgFile file = OzgFileTestFactory.create();
+	private final OzgCloudFile file = OzgCloudFileTestFactory.create();
 	private final InputStream contentStream = new ByteArrayInputStream(IncomingFileTestFactory.CONTENT);
 
-	@DisplayName("Upload filestream")
+	@DisplayName("Upload file")
 	@Nested
-	class TestUploadFileStream {
+	class TestUploadFile {
+
+		private static final String SAVED_FILE_ID = UUID.randomUUID().toString();
+		private final OzgCloudFile savedFile = OzgCloudFileTestFactory.createBuilder().id(SAVED_FILE_ID).build();
+
+		@BeforeEach
+		void init() {
+			when(ozgCloudFileRepository.save(any())).thenReturn(savedFile);
+			when(fileIdMapper.toFileId(any())).thenReturn(FileId.from(SAVED_FILE_ID));
+		}
+
+		@Test
+		void shouldCallSave() {
+			service.saveOzgCloudFile(file);
+
+			verify(ozgCloudFileRepository).save(file);
+		}
 
 		@Test
-		void shouldCallUploadFile() {
-			service.uploadFileStream(ref, file, user, contentStream);
+		void shouldCallFileIdMapper() {
+			service.saveOzgCloudFile(file);
+
+			verify(fileIdMapper).toFileId(SAVED_FILE_ID);
+		}
 
-			verify(service).uploadFile(ref, file, user, contentStream);
+		@Test
+		void shouldReturnFileId() {
+			var result = service.saveOzgCloudFile(file);
+
+			assertThat(result).hasToString(SAVED_FILE_ID);
 		}
+
 	}
 
 	@DisplayName("upload filestream async")
 	@Nested
-	class TestUploadFileStreamAsync {
+	class TestUploadFileContentStreamAsync {
 
 		@Test
-		void shouldCallUploadFile() {
-			service.uploadFileStream(ref, file, user, contentStream);
+		void shouldCallUploadFileContent() {
+			service.uploadFileContentStreamAsync(ref, contentStream);
+
+			verify(service).uploadFileContent(ref, contentStream);
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldReturnFileId() {
+			doReturn(OzgCloudFileTestFactory.CONTENT_ID).when(service).uploadFileContent(any(), any());
+
+			var id = service.uploadFileContentStreamAsync(ref, contentStream);
 
-			verify(service).uploadFile(ref, file, user, contentStream);
+			assertThat(id.get()).isEqualTo(OzgCloudFileTestFactory.CONTENT_ID);
 		}
 	}
 
 	@DisplayName("Upload file")
 	@Nested
-	class TestUploadFile {
+	class TestUploadFileContent {
 
 		@BeforeEach
 		void init() {
-			when(binaryFileRepository.addContentStream(any(), any(), any(), any())).thenReturn(IncomingFileTestFactory.ID);
+			when(ozgCloudFileRepository.uploadContent(any(), any())).thenReturn(OzgCloudFileTestFactory.CONTENT_ID);
 		}
 
 		@Test
 		void shouldCallAddContentSream() {
-			service.uploadFileStream(ref, file, user, contentStream);
-
-			verify(binaryFileRepository).addContentStream(ref, file, user, contentStream);
-		}
+			service.uploadFileContent(ref, contentStream);
 
-		@Test
-		void shouldAddContent() {
-			service.uploadFileStream(ref, file, user, contentStream);
-
-			verify(binaryFileRepository).addContentStream(ref, file, user, contentStream);
+			verify(ozgCloudFileRepository).uploadContent(ref, contentStream);
 		}
 
 		@Test
 		void shouldReturnId() {
-			var id = service.uploadFileStream(ref, file, user, contentStream);
+			var id = service.uploadFileContent(ref, contentStream);
 
-			assertThat(id).isEqualTo(IncomingFileTestFactory.ID);
-		}
-	}
-
-	@Nested
-	class TestGetUploadedFileContent {
-
-		@Test
-		void shouldBinaryFileRepository() {
-			service.getUploadedFileStream(OzgFileTestFactory.ID);
-
-			verify(binaryFileRepository).getFileContent(OzgFileTestFactory.ID);
+			assertThat(id).isEqualTo(OzgCloudFileTestFactory.CONTENT_ID);
 		}
 	}
 
 	@Nested
 	class TestFindFilesMetaData {
 
-		private final Collection<FileId> ids = Collections.singleton(OzgFileTestFactory.ID);
-
-		@BeforeEach
-		void mockFileMapper() {
-			when(fileIdMapper.toString(any())).thenReturn(OzgFileTestFactory.ID.toString());
-		}
+		private final Collection<FileId> ids = Collections.singleton(OzgCloudFileTestFactory.ID);
 
 		@Test
-		void shouldCallFileIdMapper() {
-			service.findFilesMetaData(ids);
+		void shouldCallRepository() {
+			findFilesMetaData();
 
-			verify(fileIdMapper).toString(OzgFileTestFactory.ID);
+			verify(ozgCloudFileRepository).findAll(ids);
 		}
 
 		@Test
-		void shouldCallRepository() {
-			service.findFilesMetaData(ids);
+		void shouldReturnFilesMetaData() {
+			var file = OzgCloudFileTestFactory.create();
+			when(ozgCloudFileRepository.findAll(any())).thenReturn(Stream.of(file));
+
+			var result = findFilesMetaData();
+
+			assertThat(result).containsExactly(file);
+		}
 
-			verify(binaryFileRepository).getAll(Collections.singletonList(OzgFileTestFactory.ID.toString()));
+		private List<OzgCloudFile> findFilesMetaData() {
+			return service.findOzgCloudFiles(ids).toList();
 		}
 	}
 
@@ -164,7 +185,7 @@ class FileServiceTest {
 		void shouldCallRepository() {
 			service.deleteAllByVorgang(VorgangTestFactory.ID);
 
-			verify(binaryFileRepository).deleteAllByVorgang(VorgangTestFactory.ID);
+			verify(ozgCloudFileRepository).deleteAllByVorgang(VorgangTestFactory.ID);
 		}
 	}
 
@@ -187,7 +208,7 @@ class FileServiceTest {
 
 		@Test
 		void shouldReturnAttachmentsByVorgangIdAndEingangId() {
-			var attachment = OzgFileTestFactory.create();
+			var attachment = IncomingFileTestFactory.create();
 			when(repository.findAttachments(any(), any())).thenReturn(List.of(attachment));
 
 			var result = getAttachments();
@@ -195,7 +216,7 @@ class FileServiceTest {
 			assertThat(result).containsExactly(attachment);
 		}
 
-		private List<OzgFile> getAttachments() {
+		private List<IncomingFile> getAttachments() {
 			return service.getAttachments(VorgangTestFactory.ID, Optional.of(EingangTestFactory.ID));
 		}
 	}
@@ -219,7 +240,7 @@ class FileServiceTest {
 
 		@Test
 		void shouldReturnRepresentationsByVorgangIdAndEingangId() {
-			var representation = OzgFileTestFactory.create();
+			var representation = IncomingFileTestFactory.create();
 			when(repository.findRepresentations(any(), any())).thenReturn(List.of(representation));
 
 			var result = getRepresentations();
@@ -227,7 +248,7 @@ class FileServiceTest {
 			assertThat(result).containsExactly(representation);
 		}
 
-		private List<OzgFile> getRepresentations() {
+		private List<IncomingFile> getRepresentations() {
 			return service.getRepresentations(VorgangTestFactory.ID, Optional.of(EingangTestFactory.ID));
 		}
 	}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java
index f8109c717128e6b45f04690efeba8c4477189f84..55ef756ee61e3c1d8041810867ddfaeb10dedd02 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java
@@ -34,7 +34,6 @@ import org.springframework.data.mongodb.util.BsonUtils;
 import com.mongodb.client.gridfs.model.GridFSFile;
 
 import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
-import de.ozgcloud.vorgang.callcontext.UserTestFactory;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 
 public class GridFsTestFactory {
@@ -47,12 +46,12 @@ public class GridFsTestFactory {
 	public static final Integer CHUNK_SIZE = 1;
 
 	public static GridFSFile create() {
-		return new GridFSFile(BsonUtils.simpleToBsonValue(ID), OzgFileTestFactory.NAME, OzgFileTestFactory.SIZE, CHUNK_SIZE, UPLOAD_DATE,
-				createMetadata());
+		return new GridFSFile(BsonUtils.simpleToBsonValue(ID), OzgCloudFileTestFactory.NAME, OzgCloudFileTestFactory.SIZE, CHUNK_SIZE, UPLOAD_DATE,
+				new Document());
 	}
 
 	public static GridFsUpload<ObjectId> createUpload() {
-		return createUploadBuilder(OzgFileTestFactory.CONTENT).build();
+		return createUploadBuilder(OzgCloudFileTestFactory.CONTENT).build();
 	}
 
 	public static GridFsUpload<ObjectId> createUpload(byte[] content) {
@@ -63,24 +62,11 @@ public class GridFsTestFactory {
 
 	public static GridFsUpload.GridFsUploadBuilder<ObjectId> createUploadBuilder(byte[] content) {
 		return GridFsUpload.fromStream(new ByteArrayInputStream(content))
-				.metadata(createMetadata())
 				.filename(createFilePath());
 	}
 
-	private static Document createMetadata() {
-		Document metadata = new Document();
-		metadata.append(BinaryFileRepository.VORGANG_ID, VorgangTestFactory.ID);
-		metadata.append(BinaryFileRepository.CLIENT, CallContextTestFactory.CLIENT);
-		metadata.append(BinaryFileRepository.FIELD_NAME, UploadedFilesReferenceTestFactory.NAME);
-		metadata.append(BinaryFileRepository.CONTENT_TYPE, OzgFileTestFactory.CONTENT_TYPE);
-		metadata.append(BinaryFileRepository.FILE_NAME, OzgFileTestFactory.NAME);
-		metadata.append(BinaryFileRepository.CREATED_BY, UserTestFactory.ID);
-
-		return metadata;
-	}
-
 	private static String createFilePath() {
 		return String.format("%s/%s/%s/%s", VorgangTestFactory.ID, CallContextTestFactory.CLIENT,
-				UploadedFilesReferenceTestFactory.NAME, OzgFileTestFactory.NAME);
+				UploadedFilesReferenceTestFactory.NAME, OzgCloudFileTestFactory.NAME);
 	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java
similarity index 92%
rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java
rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java
index b75e9d032d61eef6ea75938d7fe4669f43d90f7d..ef222837306da6744869e5d2e626b34c79270f3d 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java
@@ -34,7 +34,7 @@ import org.mockito.Spy;
 
 import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile;
 
-class GrpcOzgFileMapperTest {
+class GrpcBinaryFileMapperTest {
 
 	@InjectMocks
 	private GrpcOzgFileMapper mapper = Mappers.getMapper(GrpcOzgFileMapper.class);
@@ -44,7 +44,7 @@ class GrpcOzgFileMapperTest {
 	@Test
 	void validateMapping() {
 
-		List<GrpcOzgFile> response = mapper.map(List.of(OzgFileTestFactory.create()));
+		List<GrpcOzgFile> response = mapper.map(List.of(OzgCloudFileTestFactory.create()));
 
 		assertThat(response).isNotNull().hasSize(1);
 	}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..daa09b8a312f2689bba0df1b730fae296af180b3
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata;
+import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
+
+public class GrpcBinaryFileMetadataTestFactory {
+
+	public static final String FILE_ID = OzgCloudFileTestFactory.ID.toString();
+	public static final String VORGANG_ID = VorgangTestFactory.ID;
+	public static final String CONTENT_TYPE = OzgCloudFileTestFactory.CONTENT_TYPE;
+	public static final String FILE_NAME = OzgCloudFileTestFactory.NAME;
+
+	public static GrpcBinaryFileMetadata create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcBinaryFileMetadata.Builder createBuilder() {
+		return GrpcBinaryFileMetadata.newBuilder()
+				.setFileId(FILE_ID)
+				.setContentType(CONTENT_TYPE)
+				.setFileName(FILE_NAME);
+	}
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java
index 4935ca6391a0afb95de3fa00f23cb8f08080358f..9e7a4208cea818c56cd7c1b0c3423ccb20a9eb20 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java
@@ -24,17 +24,31 @@
 package de.ozgcloud.vorgang.files;
 
 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.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicBoolean;
-
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
@@ -45,20 +59,37 @@ import org.mockito.Captor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
-
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.google.protobuf.ByteString;
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
+import de.ozgcloud.vorgang.callcontext.CallContextUserTestFactory;
+import de.ozgcloud.vorgang.callcontext.CurrentUserService;
+import de.ozgcloud.vorgang.callcontext.UserTestFactory;
+import de.ozgcloud.vorgang.command.GrpcCallContextTestFactory;
+import de.ozgcloud.vorgang.command.GrpcUserTestFactory;
+import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
 import de.ozgcloud.vorgang.common.security.PolicyService;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
 import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
+import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 import io.grpc.stub.CallStreamObserver;
-import io.grpc.stub.ServerCallStreamObserver;
 import io.grpc.stub.StreamObserver;
+import lombok.SneakyThrows;
 
 class GrpcBinaryFileServiceTest {
 
+	private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create();
+	private static final String NEW_CONTENT_FILE_ID = UUID.randomUUID().toString();
+
 	@InjectMocks
 	@Spy
 	private GrpcBinaryFileService grpcService;
@@ -68,9 +99,11 @@ class GrpcBinaryFileServiceTest {
 	private GrpcOzgFileMapper fileMapper;
 	@Mock
 	private PolicyService policyService;
+	@Mock
+	private CurrentUserService currentUserService;
 
 	@Nested
-	class TestFindBinaryFilesMetaData {
+	class TestFindOzgCloudFilesMetaData {
 
 		@Mock
 		private StreamObserver<GrpcFindFilesResponse> responseObserver;
@@ -90,8 +123,8 @@ class GrpcBinaryFileServiceTest {
 		void shouldCallService() {
 			findBinaryFilesMetaData();
 
-			verify(service).findFilesMetaData(fileIdsCaptor.capture());
-			assertThat(fileIdsCaptor.getValue()).contains(OzgFileTestFactory.ID);
+			verify(service).findOzgCloudFiles(fileIdsCaptor.capture());
+			assertThat(fileIdsCaptor.getValue()).contains(OzgCloudFileTestFactory.ID);
 		}
 
 		@Test
@@ -115,71 +148,270 @@ class GrpcBinaryFileServiceTest {
 		}
 	}
 
-	@DisplayName("Upload filestream async")
 	@Nested
-	class TestUploadFileStreamAsync {
-
-		private static final int FILE_SIZE = 5 * 1_000_000;
+	class TestUploadOzgCloudFile {
 
 		@Mock
-		private StreamObserver<GrpcFindFilesResponse> responseObserver;
+		private OzgCloudFile updatedOzgCloudFile;
+
+		@BeforeEach
+		void init() {
+			when(service.saveOzgCloudFile(any())).thenReturn(OzgCloudFileTestFactory.ID);
+			doReturn(updatedOzgCloudFile).when(grpcService).updateContentId(any(), any());
+		}
+
+		@Test
+		void shouldCallUpdateContentId() {
+			uploadBinaryFile();
+
+			verify(grpcService).updateContentId(BINARY_FILE, Optional.of(OzgCloudFileTestFactory.CONTENT_ID));
+		}
+
+		@Test
+		void shouldCallUploadFile() {
+			uploadBinaryFile();
+
+			verify(service).saveOzgCloudFile(updatedOzgCloudFile);
+		}
+
+		@Test
+		void shouldReturnFileId() {
+			var result = uploadBinaryFile();
+
+			assertThat(result).isEqualTo(OzgCloudFileTestFactory.ID);
+		}
+
+		private FileId uploadBinaryFile() {
+			return grpcService.uploadOzgCloudFile(BINARY_FILE, Optional.of(OzgCloudFileTestFactory.CONTENT_ID));
+		}
+
+	}
+
+	@Nested
+	class TestUploadOzgCloudFileAsStream {
 
-		@Spy
-		private ServerCallStreamObserver<GrpcUploadBinaryFileResponse> uploadFileResponseObserver;
+		private static final GrpcUploadBinaryFileRequest REQUEST = GrpcUploadBinaryFileRequestTestFactory.create();
 
 		@Mock
-		private StreamObserver<GrpcGetBinaryFileDataResponse> dataResponseObserver;
+		private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver;
+		@Mock
+		private GrpcUploadBinaryFileResponse response;
+		@Mock
+		private GrpcUploadBinaryFileRequest requestMock;
+		@Mock
+		private ByteString content;
+		@Captor
+		private ArgumentCaptor<InputStream> inputStreamCaptor;
 
-		@BeforeEach
-		void init() {
-			when(service.uploadFileStreamAsync(any(), any(), any(), any())).thenReturn(CompletableFuture.supplyAsync(() -> FileId.createNew()));
+		@Test
+		void shouldSetMetadataVerifier() {
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callMetadataVerifier(uploadObserver, REQUEST);
+			verify(policyService).checkPermission(VorgangTestFactory.ID);
+		}
+
+		@Test
+		void shouldSetHasMetadata() {
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callHasMetadata(uploadObserver, requestMock);
+			verify(requestMock).hasMetadata();
+		}
+
+		@Test
+		void shouldSetMetadataMapper() {
+			doReturn(UserTestFactory.ID).when(grpcService).getUserId(any());
+			doReturn(CallContextTestFactory.CLIENT).when(grpcService).getClient(any());
+
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callMetadataMapper(uploadObserver, REQUEST);
+			verify(grpcService).buildOzgCloudFile(REQUEST);
+		}
+
+		@Test
+		void shouldCallMetadataUploader() {
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callMetadataUploader(uploadObserver);
+			verify(grpcService).uploadOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
+		}
+
+		@Test
+		void shouldCallFileContentUploader() {
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callFileContentUploader(uploadObserver);
+			verify(grpcService).uploadFileContent(eq(BINARY_FILE), inputStreamCaptor.capture());
+			assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT);
+		}
+
+		@Test
+		void shouldCallGetFileContent() {
+			when(requestMock.getFileContent()).thenReturn(content);
+
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callGetFileContent(uploadObserver, requestMock);
+			verify(requestMock).getFileContent();
+			verify(content).toByteArray();
+		}
+
+		@Test
+		void shouldCallBuildResponse() {
+			var uploadObserver = uploadBinaryFileAsStream();
+
+			callResponseConsumer(uploadObserver);
+			verify(grpcService).buildResponse(OzgCloudFileTestFactory.ID);
 		}
 
 		@Test
-		void shouldCallService() throws IOException {
-			uploadFile(FILE_SIZE);
+		void shouldCallCompleteRequest() {
+			doReturn(response).when(grpcService).buildResponse(any());
+
+			var uploadObserver = uploadBinaryFileAsStream();
 
-			verify(service, timeout(500)).uploadFileStreamAsync(any(), any(), any(), any());
+			callResponseConsumer(uploadObserver);
+			verify(grpcService).completeRequest(response, responseObserver);
 		}
 
 		@Test
-		void shouldCallOnComplete() throws IOException {
-			uploadFile(FILE_SIZE);
+		void shouldCallDeleteContent() {
+			var uploadObserver = uploadBinaryFileAsStream();
 
-			verify(uploadFileResponseObserver, timeout(500)).onCompleted();
+			callCleanupFileContent(uploadObserver);
+			verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID);
 		}
 
+		private StreamObserver<GrpcUploadBinaryFileRequest> uploadBinaryFileAsStream() {
+			return grpcService.uploadBinaryFileAsStream(responseObserver);
+		}
+
+	}
+
+	@Nested
+	class TestBuildOzgCloudFile {
+
 		@Test
-		void shouldCallOnNext() throws IOException {
-			uploadFile(FILE_SIZE);
+		void shouldBuildBinaryFile() {
+			doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId(any());
+			doReturn(OzgCloudFileTestFactory.CLIENT).when(grpcService).getClient(any());
+
+			var result = buildBinaryFile();
+
+			assertThat(result).usingRecursiveComparison().ignoringFields("id", "version", "contentId", "size")
+					.isEqualTo(OzgCloudFileTestFactory.create());
+		}
 
-			verify(uploadFileResponseObserver, timeout(500)).onNext(any());
+		private OzgCloudFile buildBinaryFile() {
+			return grpcService.buildOzgCloudFile(GrpcUploadBinaryFileRequestTestFactory.create());
 		}
+	}
+
+	@Nested
+	class TestGetUserId {
+
+		@Nested
+		class TestWithUploadRequest {
+
+			private final GrpcUploadBinaryFileRequest fileUploadRequest = GrpcUploadBinaryFileRequestTestFactory.create();
+
+			@Nested
+			class TestContextUserId {
+
+				@BeforeEach
+				void init() {
+					when(currentUserService.findUser()).thenReturn(Optional.of(CallContextUserTestFactory.create()));
+				}
 
-		void uploadFile(int fileSize) {
-			var observer = grpcService.uploadBinaryFileAsStream(uploadFileResponseObserver);
+				@Test
+				void shouldCallFindUser() {
+					getUserId();
 
-			try {
-				uploadChunks(fileSize, observer).get();
-			} catch (InterruptedException | ExecutionException e) {
-				Thread.currentThread().interrupt();
+					verify(currentUserService).findUser();
+				}
+
+				@Test
+				void shouldReturnContextUserId() {
+					var result = getUserId();
+
+					assertThat(result).contains(CallContextUserTestFactory.ID);
+				}
+			}
+
+			@Nested
+			class TestRequestUserId {
+
+				@BeforeEach
+				void init() {
+					when(currentUserService.findUser()).thenReturn(
+							Optional.of(CallContextUserTestFactory.createBuilder().userId(Optional.empty()).build()));
+				}
+
+				@Test
+				void shouldCallFindUser() {
+					getUserId();
+
+					verify(currentUserService).findUser();
+				}
+
+				@Test
+				void shouldReturnRequestUserId() {
+					var result = getUserId();
+
+					assertThat(result).contains(GrpcUserTestFactory.ID);
+				}
+			}
+
+			private String getUserId() {
+				return grpcService.getUserId(fileUploadRequest);
 			}
 		}
 
-		private CompletableFuture<Boolean> uploadChunks(final int fileSize, final StreamObserver<GrpcUploadBinaryFileRequest> observer) {
-			return CompletableFuture.supplyAsync(() -> {
-				GrpcUploadBinaryFileRequest firstRequest = GrpcUploadBinaryFileRequest.newBuilder()
-						.setMetadata(GrpcUploadBinaryFileMetaData.newBuilder()
-								.setFileName("test")
-								.build())
-						.build();
+		@Nested
+		class TestWithoutRequest {
+
+			@Nested
+			class TestUserExists {
+
+				@BeforeEach
+				void init() {
+					when(currentUserService.getUser()).thenReturn(CallContextUserTestFactory.create());
+				}
+
+				@Test
+				void shouldCallGetUser() {
+					grpcService.getUserId();
+
+					verify(currentUserService).getUser();
+				}
+
+				@Test
+				void shouldReturnUserId() {
+					var result = grpcService.getUserId();
 
-				observer.onNext(firstRequest);
+					assertThat(result).isEqualTo(CallContextUserTestFactory.ID);
+				}
+			}
+
+			@Nested
+			class TestMissingUser {
+
+				@Test
+				void shouldThrowExceptionIfMissingUser() {
+					when(currentUserService.getUser()).thenThrow(new IllegalStateException());
+
+					Assertions.assertThrows(IllegalStateException.class, grpcService::getUserId);
+				}
 
-				observer.onCompleted();
+				@Test
+				void shouldThrowExceptionIfMissingUserId() {
+					when(currentUserService.getUser()).thenReturn(CallContextUserTestFactory.createBuilder().userId(Optional.empty()).build());
 
-				return Boolean.TRUE;
-			});
+					assertThrows(IllegalStateException.class, grpcService::getUserId);
+				}
+			}
 		}
 	}
 
@@ -197,13 +429,13 @@ class GrpcBinaryFileServiceTest {
 
 		@BeforeEach
 		void init() {
-			request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(OzgFileTestFactory.ID.toString()).build();
+			request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(OzgCloudFileTestFactory.ID.toString()).build();
 		}
 
 		@Test
 		void shouldCallService() {
 			when(service.getUploadedFileStream(any(FileId.class)))
-					.thenReturn(new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(FILE_SIZE_MB)));
+					.thenReturn(new ByteArrayInputStream(OzgCloudFileTestFactory.createContentInMiB(FILE_SIZE_MB)));
 
 			grpcService.getBinaryFileContent(request, dataResponseObserver);
 
@@ -219,61 +451,47 @@ class GrpcBinaryFileServiceTest {
 	}
 
 	@Nested
-	class TestGetBinaryFileData {
+	class TestGetBinaryFileContent {
+
+		private static final InputStream INPUT_STREAM = new BufferedInputStream(new ByteArrayInputStream(OzgCloudFileTestFactory.CONTENT),
+				FileService.CHUNK_SIZE);
 
 		@Mock
 		private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver;
+
 		@Captor
-		private ArgumentCaptor<Collection<FileId>> fileIdsCaptor;
-		@Captor
-		private ArgumentCaptor<GrpcGetBinaryFileDataResponse> responseCaptor;
+		private ArgumentCaptor<InputStream> inputStreamCaptor;
 
 		@BeforeEach
 		void mock() {
-			when(service.getUploadedFileStream(any(FileId.class))).thenReturn(new ByteArrayInputStream(OzgFileTestFactory.CONTENT));
+			when(service.getUploadedFileStream(any(FileId.class))).thenReturn(INPUT_STREAM);
 		}
 
 		@Test
 		void shouldCallPolicyService() {
-			getBinaryFileData();
+			getBinaryFileContent();
 
 			verify(policyService).checkPermissionByFile(anyString());
 		}
 
 		@Test
 		void shouldCallService() {
-			getBinaryFileData();
+			getBinaryFileContent();
 
 			verify(service).getUploadedFileStream(FileId.from(GrpcGetBinaryFileDataRequestTestFactory.FILE_ID));
 		}
 
 		@Test
-		void shouldCallSetOnReadyHandler() {
-			getBinaryFileData();
-
-			verify(responseObserver).setOnReadyHandler(any());
-		}
-
-		@Test
-		void shouldCallOnNext() {
-			getBinaryFileData();
-
-			verify(responseObserver, times(1)).onNext(any());
-		}
-
-		@Test
-		void shouldCallOnComplete() {
-			getBinaryFileData();
+		void shouldCallCallDownloadFile() {
+			getBinaryFileContent();
 
-			verify(responseObserver).onCompleted();
+			verify(grpcService).downloadFile(eq(responseObserver), inputStreamCaptor.capture());
+			assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT);
 		}
 
-		private void getBinaryFileData() {
-			AtomicBoolean doSendFile = new AtomicBoolean(true);
+		private void getBinaryFileContent() {
 			grpcService.getBinaryFileContent(GrpcGetBinaryFileDataRequestTestFactory.create(), responseObserver);
 
-			grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.CONTENT), doSendFile);
-
 		}
 	}
 
@@ -281,8 +499,8 @@ class GrpcBinaryFileServiceTest {
 	class TestBuildChunkResponse {
 		@Test
 		void shouldHaveFileContent() {
-			GrpcGetBinaryFileDataResponse response = grpcService.buildChunkResponse(OzgFileTestFactory.CONTENT,
-					OzgFileTestFactory.CONTENT.length);
+			GrpcGetBinaryFileDataResponse response = grpcService.buildChunkResponse(OzgCloudFileTestFactory.CONTENT,
+					OzgCloudFileTestFactory.CONTENT.length);
 
 			assertThat(response.getFileContent()).isNotEmpty();
 		}
@@ -290,116 +508,933 @@ class GrpcBinaryFileServiceTest {
 
 	@Nested
 	class TestBuildUploadResponse {
+
 		@Test
 		void shouldHaveFileId() {
-			GrpcUploadBinaryFileResponse response = GrpcBinaryFileService.buildResponse(FileId.createNew());
+			var fileId = "file-id";
 
-			assertThat(response.getFileId()).isNotNull();
+			var response = grpcService.buildResponse(FileId.from(fileId));
+
+			assertThat(response.getFileId()).isEqualTo(fileId);
 		}
 	}
 
 	@Nested
 	class TestDownloadFile {
+
 		@Mock
 		private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver;
+		@Mock
+		private InputStream contentStream;
+
+		@Captor
+		private ArgumentCaptor<Runnable> onReadyHandlerCaptor;
+		@Captor
+		private ArgumentCaptor<AtomicBoolean> doSendFileCaptor;
+
+		@BeforeEach
+		void init() {
+			doNothing().when(grpcService).sendChunks(any(), any(), any());
+		}
 
 		@Test
 		void shouldSetOnReadyHandler() {
-			grpcService.downloadFile(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)));
+			grpcService.downloadFile(responseObserver, contentStream);
 
-			verify(responseObserver).setOnReadyHandler(any());
+			verify(responseObserver).setOnReadyHandler(onReadyHandlerCaptor.capture());
+			onReadyHandlerCaptor.getValue().run();
+			verify(grpcService, times(2)).sendChunks(eq(responseObserver), eq(contentStream), doSendFileCaptor.capture());
+			assertThat(doSendFileCaptor.getValue()).isTrue();
+		}
+
+		@Test
+		void shouldCallSendChunks() {
+			grpcService.downloadFile(responseObserver, contentStream);
+
+			verify(grpcService).sendChunks(eq(responseObserver), eq(contentStream), doSendFileCaptor.capture());
+			assertThat(doSendFileCaptor.getValue()).isTrue();
 		}
 	}
 
 	@Nested
-	class TestOnReadyHandler {
+	class TestSendChunk {
+
+		private static final AtomicBoolean DO_SEND_FILE = new AtomicBoolean(true);
+
 		@Mock
 		private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver;
+		@Mock
+		private InputStream contentStream;
 
 		@Test
-		void shouldSetOnReadyHandler() {
-			grpcService.downloadFile(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)));
+		void shouldCallIsReady() {
+			sendChunks();
+
+			verify(responseObserver).isReady();
+		}
+
+		@Nested
+		class TestSending {
+
+			private static final int SENT_LAST_BYTES = new Random().nextInt(FileService.CHUNK_SIZE);
+
+			@SneakyThrows
+			@BeforeEach
+			void init() {
+				when(responseObserver.isReady()).thenReturn(true, false);
+			}
+
+			@Test
+			void shouldCallSendNextChunk() {
+				doReturn(FileService.CHUNK_SIZE).when(grpcService).sendNextChunk(any(), any());
+
+				sendChunks();
+
+				verify(grpcService).sendNextChunk(responseObserver, contentStream);
+				verify(grpcService, never()).handleFileEndReached(any(), any(), any());
+			}
+
+			@Test
+			void shouldCallHandleFileEndReached() {
+				doReturn(SENT_LAST_BYTES).when(grpcService).sendNextChunk(any(), any());
+
+				sendChunks();
+
+				verify(grpcService).handleFileEndReached(responseObserver, contentStream, DO_SEND_FILE);
+				verify(responseObserver, times(1)).isReady();
+			}
+
+		}
+
+		@Nested
+		class TestNotSending {
+
+			@SneakyThrows
+			@Test
+			void shouldNotSendIfDownloadCompleted() {
+				grpcService.sendChunks(responseObserver, contentStream, new AtomicBoolean(false));
+
+				verify(grpcService, never()).sendNextChunk(any(), any());
+			}
+
+			@Test
+			void shouldNotSendIfNotReady() {
+				grpcService.sendChunks(responseObserver, contentStream, new AtomicBoolean(true));
+
+				verify(grpcService, never()).sendNextChunk(any(), any());
+			}
+		}
 
-			verify(responseObserver).setOnReadyHandler(any());
+		private void sendChunks() {
+			grpcService.sendChunks(responseObserver, contentStream, DO_SEND_FILE);
 		}
 	}
 
 	@Nested
-	class TestSendChunk {
+	class TestSendNextChunk {
+
+		private static final int READ_BYTES = new Random().nextInt(1, FileService.CHUNK_SIZE);
+		private static final byte[] BUFFER = new byte[READ_BYTES];
+
+		@Mock
+		private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver;
+		@Mock
+		private GrpcGetBinaryFileDataResponse binaryFileDataGrpcResponse;
+		@Mock
+		private InputStream contentStream;
+
+		@Nested
+		class TestSendingSuccessfully {
+			@Captor
+			private ArgumentCaptor<byte[]> bufferCaptor;
+
+			@SneakyThrows
+			@BeforeEach
+			void init() {
+				doReturn(binaryFileDataGrpcResponse).when(grpcService).buildChunkResponse(any(), anyInt());
+				new Random().nextBytes(BUFFER);
+				when(contentStream.read(any())).thenAnswer(invocation -> {
+					byte[] buffer = invocation.getArgument(0);
+					System.arraycopy(BUFFER, 0, buffer, 0, READ_BYTES);
+					return READ_BYTES;
+				});
+			}
+
+			@Test
+			void shouldCallBuildChuckResponse() {
+				grpcService.sendNextChunk(responseObserver, contentStream);
+
+				verify(grpcService).buildChunkResponse(bufferCaptor.capture(), eq(READ_BYTES));
+				assertThat(bufferCaptor.getValue()).contains(BUFFER);
+			}
+
+			@Test
+			void shouldCallOnNext() {
+				grpcService.sendNextChunk(responseObserver, contentStream);
+
+				verify(responseObserver).onNext(binaryFileDataGrpcResponse);
+			}
+		}
+
+		@Nested
+		class TestOnException {
+
+			@SneakyThrows
+			@Test
+			void shouldThrowException() {
+				doThrow(IOException.class).when(contentStream).read(any());
+
+				assertThrows(TechnicalException.class, () -> grpcService.sendNextChunk(responseObserver, contentStream));
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCloseStreamOnException() {
+				try (var ioUtilsMock = mockStatic(IOUtils.class)) {
+					doThrow(IOException.class).when(contentStream).read(any());
+
+					try {
+						grpcService.sendNextChunk(responseObserver, contentStream);
+					} catch (TechnicalException e) {
+						// expected
+					}
+
+					ioUtilsMock.verify(() -> IOUtils.closeQuietly(eq(contentStream), any(Consumer.class)));
+				}
+			}
+		}
+	}
+
+	@Nested
+	class TestHandleFileEndReached {
+
+		private static final AtomicBoolean DO_SEND_FILE = new AtomicBoolean(true);
 		@Mock
 		private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver;
+		@Mock
+		private InputStream contentStream;
+
+		@Test
+		void shouldSetDoSendFile() {
+			handleFileEndReached();
+
+			assertThat(DO_SEND_FILE).isFalse();
+		}
 
 		@Test
-		void shouldComplete() throws IOException {
-			var res = OzgFileTestFactory.createContentInMiB(1);
-			var inputStream = new ByteArrayInputStream(res);
+		void shouldCallCloseQuietly() {
+			try (var ioUtilsMock = mockStatic(IOUtils.class)) {
+				handleFileEndReached();
+
+				ioUtilsMock.verify(() -> IOUtils.closeQuietly(eq(contentStream), any(Consumer.class)));
+			}
+		}
 
-			grpcService.downloadFile(responseObserver, inputStream);
+		@Test
+		void shouldCallOnCompleted() {
+			handleFileEndReached();
 
-			triggerSendingChunks(res, inputStream);
+			verify(responseObserver).onCompleted();
+		}
 
-			verify(responseObserver, timeout(500)).onCompleted();
+		private void handleFileEndReached() {
+			grpcService.handleFileEndReached(responseObserver, contentStream, DO_SEND_FILE);
 		}
+	}
+
+	@DisplayName("Upload filestream async")
+	@Nested
+	class TestUpdateOzgCloudFile {
+
+		private static final GrpcUpdateBinaryFileRequest REQUEST = GrpcUpdateBinaryFileRequestTestFactory.create();
+
+		@Mock
+		private StreamObserver<GrpcUpdateBinaryFileResponse> responseObserver;
+		@Mock
+		private GrpcUpdateBinaryFileRequest requestMock;
+		@Mock
+		private ByteString content;
+		@Captor
+		private ArgumentCaptor<InputStream> inputStreamCaptor;
 
 		@Test
-		void shoudSendChunks() {
-			grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)), new AtomicBoolean(true));
+		void shouldSetMetadataVerifier() {
+			var uploadObserver = updateBinaryFile();
 
-			verify(grpcService).sendChunks(any(), any(), any());
+			callMetadataVerifier(uploadObserver, REQUEST);
+			verify(grpcService).verifyUpdateRequest(REQUEST);
 		}
 
 		@Test
-		void shouldSendOneChunk() {
-			grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)), new AtomicBoolean(true));
+		void shouldSetHasMetadata() {
+			var uploadObserver = updateBinaryFile();
 
-			verify(responseObserver).onNext(any());
+			callHasMetadata(uploadObserver, requestMock);
+			verify(requestMock).hasMetadata();
 		}
 
 		@Test
-		void shouldSendOneChunkWhenFileSizeEqualsChunkSize() throws IOException {
-			byte[] res = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE);
-			var inputStream = new ByteArrayInputStream(res);
+		void shouldSetMetadataMapper() {
+			doReturn(BINARY_FILE).when(grpcService).buildOzgCloudFile(any(GrpcUpdateBinaryFileRequest.class));
+
+			var uploadObserver = updateBinaryFile();
+
+			callMetadataMapper(uploadObserver, REQUEST);
+			verify(grpcService).buildOzgCloudFile(REQUEST);
+		}
 
-			grpcService.downloadFile(responseObserver, inputStream);
+		@Test
+		void shouldCallMetadataUploader() {
+			doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId();
+			doReturn(OzgCloudFileTestFactory.CLIENT).when(grpcService).getClient();
 
-			triggerSendingChunks(res, inputStream);
+			var uploadObserver = updateBinaryFile();
 
-			verify(grpcService).buildChunkResponse(any(), anyInt());
-			verify(responseObserver).onNext(any());
-			verify(responseObserver, timeout(500)).onCompleted();
+			callMetadataUploader(uploadObserver);
+			verify(grpcService).patchOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
 		}
 
 		@Test
-		void shouldSendManyChunks() throws IOException {
-			byte[] res = OzgFileTestFactory.createContentInMiB(1);
-			var inputStream = new ByteArrayInputStream(res);
+		void shouldCallFileContentUploader() {
+			var uploadObserver = updateBinaryFile();
+
+			callFileContentUploader(uploadObserver);
+			verify(grpcService).uploadFileContent(eq(BINARY_FILE), inputStreamCaptor.capture());
+			assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT);
+		}
 
-			grpcService.downloadFile(responseObserver, inputStream);
+		@Test
+		void shouldCallGetFileContent() {
+			when(requestMock.getContent()).thenReturn(content);
 
-			int expected = triggerSendingChunks(res, inputStream);
+			var uploadObserver = updateBinaryFile();
 
-			verify(grpcService, times(expected)).buildChunkResponse(any(), anyInt());
-			verify(responseObserver, times(expected)).onNext(any());
+			callGetFileContent(uploadObserver, requestMock);
+			verify(requestMock).getContent();
+			verify(content).toByteArray();
 		}
 
-		private int triggerSendingChunks(byte[] res, ByteArrayInputStream inputStream) {
-			int expected = computeExpectedCunkCount(res);
-			AtomicBoolean doSendFile = new AtomicBoolean(true);
-			for (int i = 0; i < expected; i++) {
-				grpcService.sendChunks(responseObserver, inputStream, doSendFile);
-			}
-			if (res.length % FileService.CHUNK_SIZE == 0) {
-				grpcService.sendChunks(responseObserver, inputStream, doSendFile);
-			}
-			return expected;
+		@Test
+		void shouldCallCompleteRequest() {
+			var uploadObserver = updateBinaryFile();
+
+			callResponseConsumer(uploadObserver);
+			verify(grpcService).completeRequest(GrpcUpdateBinaryFileResponse.getDefaultInstance(), responseObserver);
 		}
 
-		int computeExpectedCunkCount(byte[] res) {
-			int expected = res.length / FileService.CHUNK_SIZE;
-			if (res.length % FileService.CHUNK_SIZE != 0) {
-				expected++;
-			}
-			return expected;
+		@Test
+		void shouldCallDeleteContent() {
+			var uploadObserver = updateBinaryFile();
+
+			callCleanupFileContent(uploadObserver);
+			verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID);
 		}
+
+		private StreamObserver<GrpcUpdateBinaryFileRequest> updateBinaryFile() {
+			return grpcService.updateBinaryFile(responseObserver);
+		}
+
+	}
+
+	@DisplayName("Verify update request")
+	@Nested
+	class TestVerifyUpdateRequest {
+
+		@BeforeEach
+		void init() {
+			doNothing().when(grpcService).validateMetadata(any());
+			doNothing().when(policyService).checkPermissionByFile(any());
+		}
+
+		@Test
+		void shouldCallValidateMetadata() {
+			var request = GrpcUpdateBinaryFileRequestTestFactory.create();
+
+			grpcService.verifyUpdateRequest(request);
+
+			verify(grpcService).validateMetadata(request);
+		}
+
+		@Test
+		void shouldCallVerifyPermissions() {
+			var request = GrpcUpdateBinaryFileRequestTestFactory.create();
+
+			grpcService.verifyUpdateRequest(request);
+
+			verify(policyService).checkPermissionByFile(OzgCloudFileTestFactory.ID_STR);
+		}
+	}
+
+	@DisplayName("Validate metadata of update binary file request")
+	@Nested
+	class TestValidateMetadata {
+
+		@DisplayName("should validate metadata")
+		@Test
+		void shouldValidateRequest() {
+			var request = GrpcUpdateBinaryFileRequestTestFactory.create();
+
+			assertDoesNotThrow(() -> grpcService.validateMetadata(request));
+		}
+
+		@DisplayName("should throw exception when")
+		@Nested
+		class TestThrowException {
+
+			@DisplayName("metadata missing")
+			@Test
+			void shouldThrowWhenMetadataMissing() {
+				var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder().clearMetadata().build();
+
+				assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request));
+			}
+
+			@DisplayName("fileID missing")
+			@Test
+			void shouldThrowWhenFileIdMissing() {
+				var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder()
+						.setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearFileId()).build();
+
+				assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request));
+			}
+
+			@DisplayName("file name missing")
+			@Test
+			void shouldThrowWhenFileNameMissing() {
+				var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder()
+						.setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearFileName()).build();
+
+				assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request));
+			}
+
+			@DisplayName("content type missing")
+			@Test
+			void shouldThrowWhenContentTypeMissing() {
+				var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder()
+						.setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearContentType()).build();
+
+				assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request));
+			}
+
+		}
+	}
+
+	@DisplayName("Build binary file from update request")
+	@Nested
+	class TestBuildOzgCloudFileUpdate {
+
+		private static final GrpcUpdateBinaryFileRequest REQUEST = GrpcUpdateBinaryFileRequestTestFactory.create();
+		private static final OzgCloudFile FOUND_BINARY_FILE = OzgCloudFileTestFactory.create();
+		private static final OzgCloudFile UPDATED_BINARY_FILE = OzgCloudFileTestFactory.create();
+
+		@Test
+		void shouldThrowExceptionIfFileNotFound() {
+			assertThrows(NotFoundException.class, this::buildBinaryFile);
+		}
+
+		@Nested
+		class TestFileExists {
+
+			@BeforeEach
+			void init() {
+				when(service.findOzgCloudFiles(anyList())).thenReturn(Stream.of(FOUND_BINARY_FILE));
+				doReturn(UPDATED_BINARY_FILE).when(grpcService).updateOzgCloudFile(any(), any());
+			}
+
+			@Test
+			void shouldCallFindFilesMetaData() {
+				var fileid = FileId.from(GrpcBinaryFileMetadataTestFactory.FILE_ID);
+
+				buildBinaryFile();
+
+				verify(service).findOzgCloudFiles(List.of(fileid));
+			}
+
+			@Test
+			void shouldCallUpdateBinaryFile() {
+				buildBinaryFile();
+
+				verify(grpcService).updateOzgCloudFile(FOUND_BINARY_FILE, REQUEST);
+			}
+
+			@Test
+			void shouldReturnUpdatedBinaryFile() {
+				var result = buildBinaryFile();
+
+				assertThat(result).isSameAs(UPDATED_BINARY_FILE);
+			}
+		}
+
+		private OzgCloudFile buildBinaryFile() {
+			return grpcService.buildOzgCloudFile(REQUEST);
+		}
+	}
+
+	@Nested
+	class TestUpdateOzgCloudFileFields {
+
+		private static final String NEW_FILE_NAME = LoremIpsum.getInstance().getWords(1);
+		private static final String NEW_CONTENT_TYPE = LoremIpsum.getInstance().getWords(1);
+
+		@Test
+		void shouldUpdateFileName() {
+			var result = updateBinaryFile();
+
+			assertThat(result.getName()).isEqualTo(NEW_FILE_NAME);
+		}
+
+		@Test
+		void shouldUpdateContentType() {
+			var result = updateBinaryFile();
+
+			assertThat(result.getContentType()).isEqualTo(NEW_CONTENT_TYPE);
+		}
+
+		@Test
+		void shouldNotChangeOtherFields() {
+			var result = updateBinaryFile();
+
+			assertThat(result).usingRecursiveComparison().ignoringFields("name", "contentType").isEqualTo(OzgCloudFileTestFactory.create());
+		}
+
+		private OzgCloudFile updateBinaryFile() {
+			return grpcService.updateOzgCloudFile(OzgCloudFileTestFactory.create(), GrpcUpdateBinaryFileRequestTestFactory.createBuilder()
+					.setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().setFileName(NEW_FILE_NAME).setContentType(NEW_CONTENT_TYPE))
+					.build());
+		}
+	}
+
+	@Nested
+	class TestPatchFileMetadata {
+
+		private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create();
+		private static final OzgCloudFile UPDATED_BINARY_FILE = OzgCloudFileTestFactory.createBuilder().contentId(NEW_CONTENT_FILE_ID)
+				.build();
+		private static final Map<String, Object> PATCH_MAP = Map.of("key", "value");
+
+		@BeforeEach
+		void init() {
+			doReturn(PATCH_MAP).when(grpcService).buildPatch(any());
+		}
+
+		@Test
+		void shouldCallUpdateContentId() {
+			patchBinaryFile();
+
+			verify(grpcService).updateContentId(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
+		}
+
+		@Test
+		void shouldCallBuildPatch() {
+			doReturn(UPDATED_BINARY_FILE).when(grpcService).updateContentId(any(), any());
+
+			patchBinaryFile();
+
+			verify(grpcService).buildPatch(UPDATED_BINARY_FILE);
+		}
+
+		@Test
+		void shouldCallPatch() {
+			patchBinaryFile();
+
+			verify(service).patch(OzgCloudFileTestFactory.ID, OzgCloudFileTestFactory.VERSION, PATCH_MAP);
+		}
+
+		@Test
+		void shouldCallDeleteContent() {
+			patchBinaryFile();
+
+			verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID);
+		}
+
+		@Test
+		void shouldNotCallDeleteContent() {
+			grpcService.patchOzgCloudFile(BINARY_FILE, Optional.empty());
+
+			verify(service, never()).deleteContent(any());
+		}
+
+		@Test
+		void shouldReturnFileId() {
+			var result = patchBinaryFile();
+
+			assertThat(result).isEqualTo(OzgCloudFileTestFactory.ID);
+		}
+
+		private FileId patchBinaryFile() {
+			return grpcService.patchOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
+		}
+	}
+
+	@Nested
+	class TestUpdateContentId {
+
+		private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create();
+
+		@Test
+		void shouldUpdateContentId() {
+			var result = updateContentId();
+
+			assertThat(result.getContentId()).isEqualTo(NEW_CONTENT_FILE_ID);
+		}
+
+		@Test
+		void shouldNotChangeOtherFields() {
+			var result = updateContentId();
+
+			assertThat(result).usingRecursiveComparison().ignoringFields("contentId").isEqualTo(BINARY_FILE);
+		}
+
+		@Test
+		void shouldReturnUnchangedBinaryFile() {
+			var result = grpcService.updateContentId(BINARY_FILE, Optional.empty());
+
+			assertThat(result).isSameAs(BINARY_FILE);
+		}
+
+		private OzgCloudFile updateContentId() {
+			return grpcService.updateContentId(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
+		}
+	}
+
+	@Nested
+	class TestBuildPatch {
+
+		private static final Map<String, Object> PATCH_MAP = Map.of(
+				OzgCloudFile.FIELD_FILE_NAME, OzgCloudFileTestFactory.NAME,
+				OzgCloudFile.FIELD_CONTENT_TYPE, OzgCloudFileTestFactory.CONTENT_TYPE,
+				OzgCloudFile.FIELD_CONTENT_ID, OzgCloudFileTestFactory.CONTENT_ID,
+				OzgCloudFile.FIELD_CREATED_BY, OzgCloudFileTestFactory.CREATED_BY,
+				OzgCloudFile.FIELD_CLIENT, OzgCloudFileTestFactory.CLIENT);
+
+		@BeforeEach
+		void init() {
+			doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId();
+			doReturn(CallContextTestFactory.CLIENT).when(grpcService).getClient();
+		}
+
+		@Test
+		void shouldBuildPatch() {
+			var result = grpcService.buildPatch(OzgCloudFileTestFactory.create());
+
+			assertThat(result).containsExactlyEntriesOf(PATCH_MAP);
+		}
+	}
+
+	@Nested
+	class TestUploadFileContent {
+
+		private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create();
+		private static final UploadedFilesReference UPLOADED_FILES_REFERENCE = UploadedFilesReferenceTestFactory.create();
+		private static final CompletableFuture<String> FILE_ID_FUTURE = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID);
+
+		@Mock
+		private InputStream inputStream;
+
+		@BeforeEach
+		void init() {
+			doReturn(UPLOADED_FILES_REFERENCE).when(grpcService).buildUploadFilesReferences(any());
+			when(service.uploadFileContentStreamAsync(any(), any())).thenReturn(FILE_ID_FUTURE);
+		}
+
+		@Test
+		void shouldCallBuildUploadFilesReferences() {
+			uploadFileContent();
+
+			verify(grpcService).buildUploadFilesReferences(BINARY_FILE);
+		}
+
+		@Test
+		void shouldCallUploadFileContent() {
+			uploadFileContent();
+
+			verify(service).uploadFileContentStreamAsync(UPLOADED_FILES_REFERENCE, inputStream);
+		}
+
+		@Test
+		void shouldReturnFutureFileId() {
+			var result = uploadFileContent();
+
+			assertThat(result).isSameAs(FILE_ID_FUTURE);
+		}
+
+		private CompletableFuture<String> uploadFileContent() {
+			return grpcService.uploadFileContent(BINARY_FILE, inputStream);
+		}
+	}
+
+	@Nested
+	class TestBuildUploadFilesReferences {
+
+		@Test
+		void shouldSetVorgangId() {
+			var uploadFilesReference = buildUploadFilesReferences();
+
+			assertThat(uploadFilesReference.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
+		}
+
+		@Test
+		void shouldSetName() {
+			var uploadFilesReference = buildUploadFilesReferences();
+
+			assertThat(uploadFilesReference.getName()).isEqualTo(UploadedFilesReferenceTestFactory.NAME);
+		}
+
+		@Test
+		void shouldSetClient() {
+			var uploadFilesReference = buildUploadFilesReferences();
+
+			assertThat(uploadFilesReference.getClient()).isEqualTo(CallContextTestFactory.CLIENT);
+		}
+
+		@Test
+		void shouldSetFileName() {
+			var uploadFilesReference = buildUploadFilesReferences();
+
+			assertThat(uploadFilesReference.getFileName()).isEqualTo(UploadedFilesReferenceTestFactory.FILE_NAME);
+		}
+
+		private UploadedFilesReference buildUploadFilesReferences() {
+			return grpcService.buildUploadFilesReferences(OzgCloudFileTestFactory.create());
+		}
+	}
+
+	@Nested
+	class TestGetClient {
+
+		@Nested
+		class TestWithRequest {
+
+			@Nested
+			class TestGetFromContext {
+
+				@BeforeEach
+				void init() {
+					doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient();
+				}
+
+				@Test
+				void shouldCallFindContextClient() {
+					getClient();
+
+					verify(grpcService).findContextClient();
+				}
+
+				@Test
+				void shouldReturnResult() {
+					var result = getClient();
+
+					assertThat(result).contains(CallContextTestFactory.CLIENT);
+				}
+			}
+
+			@Nested
+			class TestFromRequest {
+
+				@BeforeEach
+				void init() {
+					doReturn(Optional.empty()).when(grpcService).findContextClient();
+					doReturn(Optional.of(GrpcCallContextTestFactory.CLIENT)).when(grpcService).getRequestClient(any());
+				}
+
+				@Test
+				void shouldCallGetRequestClient() {
+					var grpcRequest = GrpcUploadBinaryFileRequestTestFactory.create();
+
+					grpcService.getClient(grpcRequest);
+
+					verify(grpcService).getRequestClient(grpcRequest);
+				}
+
+				@Test
+				void shouldReturnClientName() {
+					var result = getClient();
+
+					assertThat(result).isEqualTo(GrpcCallContextTestFactory.CLIENT);
+				}
+			}
+
+			@Nested
+			class TestNoClient {
+
+				@Test
+				void shouldThrowException() {
+					doReturn(Optional.empty()).when(grpcService).findContextClient();
+					doReturn(Optional.empty()).when(grpcService).getRequestClient(any());
+
+					assertThrows(IllegalStateException.class, TestWithRequest.this::getClient);
+				}
+			}
+
+			private String getClient() {
+				return grpcService.getClient(GrpcUploadBinaryFileRequestTestFactory.create());
+			}
+		}
+
+		@Nested
+		class TestFromContext {
+
+			@Test
+			void shouldCallFindUser() {
+				doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient();
+
+				grpcService.getClient();
+
+				verify(grpcService).findContextClient();
+			}
+
+			@Test
+			void shouldReturnClientName() {
+				doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient();
+
+				var result = grpcService.getClient();
+
+				assertThat(result).isEqualTo(CallContextTestFactory.CLIENT);
+			}
+
+			@Test
+			void shouldThrowException() {
+				doReturn(Optional.empty()).when(grpcService).findContextClient();
+
+				assertThrows(IllegalStateException.class, grpcService::getClient);
+			}
+		}
+	}
+
+	@Nested
+	class TestFindContextClient {
+
+		@Nested
+		class TestWithUser {
+
+			@BeforeEach
+			void init() {
+				when(currentUserService.findUser()).thenReturn(Optional.of(CallContextUserTestFactory.create()));
+			}
+
+			@Test
+			void shouldCallGetUser() {
+				findContextClient();
+
+				verify(currentUserService).findUser();
+			}
+
+			@Test
+			void shouldReturnClientName() {
+				var result = findContextClient();
+
+				assertThat(result).contains(CallContextTestFactory.CLIENT);
+			}
+		}
+
+		@Nested
+		class TestWithoutUser {
+
+			@BeforeEach
+			void init() {
+				when(currentUserService.findUser()).thenReturn(Optional.empty());
+			}
+
+			@Test
+			void shouldReturnEmpty() {
+				var result = findContextClient();
+
+				assertThat(result).isEmpty();
+			}
+		}
+
+		private Optional<String> findContextClient() {
+			return grpcService.findContextClient();
+		}
+	}
+
+	@Nested
+	class TestGetRequestClient {
+
+		@Test
+		void shouldReturnClientName() {
+			var result = grpcService.getRequestClient(GrpcUploadBinaryFileRequestTestFactory.create());
+
+			assertThat(result).contains(GrpcCallContextTestFactory.CLIENT);
+		}
+	}
+
+	@Nested
+	class TestCompleteRequest {
+
+		@Mock
+		private StreamObserver<TestResponse> responseObserver;
+		@Mock
+		private TestResponse response;
+
+		@Test
+		void shouldCallOnNext() {
+			completeRequest();
+
+			verify(responseObserver).onNext(response);
+		}
+
+		@Test
+		void shouldCallOnComplete() {
+			completeRequest();
+
+			verify(responseObserver).onCompleted();
+		}
+
+		private void completeRequest() {
+			grpcService.completeRequest(response, responseObserver);
+		}
+
+		private interface TestResponse {
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callMetadataVerifier(StreamObserver<T> uploadObserver, T request) {
+		var metadataVerifier = (Consumer<T>) ReflectionTestUtils.getField(uploadObserver, "requestVerifier");
+		metadataVerifier.accept(request);
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callHasMetadata(StreamObserver<T> uploadObserver, T request) {
+		var hasMetadata = (Predicate<T>) ReflectionTestUtils.getField(uploadObserver, "hasMetadata");
+		hasMetadata.test(request);
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callMetadataMapper(StreamObserver<T> uploadObserver, T request) {
+		var metadataMapper = (Function<T, OzgCloudFile>) ReflectionTestUtils.getField(uploadObserver, "metadataMapper");
+		metadataMapper.apply(request);
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callMetadataUploader(StreamObserver<T> uploadObserver) {
+		var metadataUploader = (BiFunction<OzgCloudFile, Optional<String>, FileId>) ReflectionTestUtils.getField(uploadObserver, "metadataUploader");
+		metadataUploader.apply(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID));
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callFileContentUploader(StreamObserver<T> uploadObserver) {
+		var fileContentUploader = (BiFunction<OzgCloudFile, InputStream, CompletableFuture<FileId>>)
+				ReflectionTestUtils.getField(uploadObserver, "fileContentUploader");
+		fileContentUploader.apply(BINARY_FILE, new ByteArrayInputStream(OzgCloudFileTestFactory.CONTENT));
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callGetFileContent(StreamObserver<T> uploadObserver, T request) {
+		var getFileContent = (Function<T, byte[]>) ReflectionTestUtils.getField(uploadObserver, "getFileContent");
+		getFileContent.apply(request);
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callResponseConsumer(StreamObserver<T> uploadObserver) {
+		var responseConsumer = (Consumer<FileId>) ReflectionTestUtils.getField(uploadObserver, "responseConsumer");
+		responseConsumer.accept(OzgCloudFileTestFactory.ID);
+	}
+
+	@SuppressWarnings("unchecked")
+	private <T> void callCleanupFileContent(StreamObserver<T> uploadObserver) {
+		var cleanupFileContent = (Consumer<String>) ReflectionTestUtils.getField(uploadObserver, "cleanupFileContent");
+		cleanupFileContent.accept(OzgCloudFileTestFactory.CONTENT_ID);
 	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java
index 18265b4546b412c899a86346e5730a7cfad8fc45..e4c50fc234b7248c32f23602d9074e758282ab6b 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java
@@ -33,6 +33,6 @@ public class GrpcBinaryFilesRequestTestFactory {
 
 	public static GrpcBinaryFilesRequest.Builder createBuilder() {
 		return GrpcBinaryFilesRequest.newBuilder()
-				.addFileId(OzgFileTestFactory.ID.toString());
+				.addFileId(OzgCloudFileTestFactory.ID.toString());
 	}
 }
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java
index abedba973a572b2661a4b6025ad1144821600f56..10a6235256d67363f7d884600cf8ca013525aa90 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java
@@ -32,20 +32,23 @@ import java.util.Optional;
 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.Spy;
 
 import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsResponse;
 import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsResponse;
 import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile;
+import de.ozgcloud.vorgang.vorgang.IncomingFile;
+import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
 import io.grpc.stub.StreamObserver;
 
 class GrpcFileServiceTest {
 
 	private static final GrpcOzgFile GRPC_OZG_FILE = GrpcOzgFileTestFactory.create();
+	private static final IncomingFile INCOMING_FILE = IncomingFileTestFactory.create();
 
+	@Spy
 	@InjectMocks
 	private GrpcFileService grpcFileService;
 
@@ -55,19 +58,18 @@ class GrpcFileServiceTest {
 	@Mock
 	private GrpcOzgFileMapper fileMapper;
 
-	@BeforeEach
-	void init() {
-		when(fileMapper.map(anyList())).thenReturn(List.of(GRPC_OZG_FILE));
-	}
-
 	@Nested
 	class TestGetAttachments {
 
+		@Mock
+		private GrpcGetAttachmentsResponse getAttachmentsResponse;
 		@Mock
 		private StreamObserver<GrpcGetAttachmentsResponse> responseObserver;
 
-		@Captor
-		private ArgumentCaptor<GrpcGetAttachmentsResponse> responseCaptor;
+		@BeforeEach
+		void init() {
+			doReturn(getAttachmentsResponse).when(grpcFileService).buildAttachmentsResponse(anyList());
+		}
 
 		@Test
 		void shouldCallGetAttachments() {
@@ -78,21 +80,20 @@ class GrpcFileServiceTest {
 		}
 
 		@Test
-		void shouldCallFileMapper() {
-			var ozgFiles = List.of(OzgFileTestFactory.create());
-			when(fileService.getAttachments(any(), any())).thenReturn(ozgFiles);
+		void shouldCallBuildAttachmentsResponse() {
+			var files = List.of(INCOMING_FILE);
+			when(fileService.getAttachments(any(), any())).thenReturn(files);
 
 			getAttachments();
 
-			verify(fileMapper).map(ozgFiles);
+			verify(grpcFileService).buildAttachmentsResponse(files);
 		}
 
 		@Test
 		void shouldAddFilesToResponse() {
 			getAttachments();
 
-			verify(responseObserver).onNext(responseCaptor.capture());
-			assertThat(responseCaptor.getValue().getFileList()).containsExactly(GRPC_OZG_FILE);
+			verify(responseObserver).onNext(getAttachmentsResponse);
 		}
 
 		@Test
@@ -107,14 +108,47 @@ class GrpcFileServiceTest {
 		}
 	}
 
+	@Nested
+	class TestBuildAttachmentsResponse {
+
+		@BeforeEach
+		void init() {
+			when(fileMapper.map(any(IncomingFile.class))).thenReturn(GRPC_OZG_FILE);
+		}
+
+		@Test
+		void shouldCallFileMapper() {
+			when(fileMapper.map(INCOMING_FILE)).thenReturn(GRPC_OZG_FILE);
+
+			buildAttachmentsResponse();
+
+			verify(fileMapper).map(INCOMING_FILE);
+		}
+
+		@Test
+		void shouldSetGrpcFile() {
+			var result = buildAttachmentsResponse();
+
+			assertThat(result.getFileList()).containsExactly(GRPC_OZG_FILE);
+		}
+
+		private GrpcGetAttachmentsResponse buildAttachmentsResponse() {
+			return grpcFileService.buildAttachmentsResponse(List.of(INCOMING_FILE));
+		}
+	}
+
 	@Nested
 	class TestGetRepresentations {
 
+		@Mock
+		private GrpcGetRepresentationsResponse getRepresentationsResponse;
 		@Mock
 		private StreamObserver<GrpcGetRepresentationsResponse> responseObserver;
 
-		@Captor
-		private ArgumentCaptor<GrpcGetRepresentationsResponse> responseCaptor;
+		@BeforeEach
+		void init() {
+			doReturn(getRepresentationsResponse).when(grpcFileService).buildRepresentationsResponse(any());
+		}
 
 		@Test
 		void shouldCallGetRepresentations() {
@@ -125,21 +159,20 @@ class GrpcFileServiceTest {
 		}
 
 		@Test
-		void shouldCallFileMapper() {
-			var ozgFiles = List.of(OzgFileTestFactory.create());
-			when(fileService.getRepresentations(any(), any())).thenReturn(ozgFiles);
+		void shouldCallBuildRepresentationsResponse() {
+			var file = IncomingFileTestFactory.create();
+			when(fileService.getRepresentations(any(), any())).thenReturn(List.of(file));
 
 			getRepresentations();
 
-			verify(fileMapper).map(ozgFiles);
+			verify(grpcFileService).buildRepresentationsResponse(List.of(file));
 		}
 
 		@Test
 		void shouldAddFilesToResponse() {
 			getRepresentations();
 
-			verify(responseObserver).onNext(responseCaptor.capture());
-			assertThat(responseCaptor.getValue().getFileList()).containsExactly(GRPC_OZG_FILE);
+			verify(responseObserver).onNext(getRepresentationsResponse);
 		}
 
 		@Test
@@ -153,4 +186,33 @@ class GrpcFileServiceTest {
 			grpcFileService.getRepresentations(GrpcGetRepresentationsRequestTestFactory.create(), responseObserver);
 		}
 	}
+
+	@Nested
+	class TestBuildRepresentationsResponse {
+
+		@BeforeEach
+		void init() {
+			when(fileMapper.map(any(IncomingFile.class))).thenReturn(GRPC_OZG_FILE);
+		}
+
+		@Test
+		void shouldCallFileMapper() {
+			when(fileMapper.map(INCOMING_FILE)).thenReturn(GRPC_OZG_FILE);
+
+			buildRepresentationsResponse();
+
+			verify(fileMapper).map(INCOMING_FILE);
+		}
+
+		@Test
+		void shouldSetGrpcFile() {
+			var result = buildRepresentationsResponse();
+
+			assertThat(result.getFileList()).containsExactly(GRPC_OZG_FILE);
+		}
+
+		private GrpcGetRepresentationsResponse buildRepresentationsResponse() {
+			return grpcFileService.buildRepresentationsResponse(List.of(INCOMING_FILE));
+		}
+	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java
index e2dec587074c6fa5d6a13ef1be50e9bbddaa1bbd..df2fd4feaf07b916f397c6561c66acd4a9f25ce1 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java
@@ -33,9 +33,9 @@ public class GrpcOzgFileTestFactory {
 
 	public static GrpcOzgFile.Builder createBuilder() {
 		return GrpcOzgFile.newBuilder()
-				.setId(OzgFileTestFactory.ID.toString())
-				.setName(OzgFileTestFactory.NAME)
-				.setSize(OzgFileTestFactory.SIZE)
-				.setContentType(OzgFileTestFactory.CONTENT_TYPE);
+				.setId(OzgCloudFileTestFactory.ID.toString())
+				.setName(OzgCloudFileTestFactory.NAME)
+				.setSize(OzgCloudFileTestFactory.SIZE)
+				.setContentType(OzgCloudFileTestFactory.CONTENT_TYPE);
 	}
 }
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c2212f9f71977b56f2b40dfa93af0db1e6fc7db
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import com.google.protobuf.ByteString;
+
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest;
+import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
+
+public class GrpcUpdateBinaryFileRequestTestFactory {
+
+	static final ByteString FILE_CONTENT = ByteString.copyFrom(IncomingFileTestFactory.CONTENT);
+	static final GrpcBinaryFileMetadata METADATA = GrpcBinaryFileMetadataTestFactory.create();
+
+	public static GrpcUpdateBinaryFileRequest create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcUpdateBinaryFileRequest.Builder createBuilder() {
+		return GrpcUpdateBinaryFileRequest.newBuilder()
+				.setContent(FILE_CONTENT)
+				.setMetadata(METADATA);
+	}
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java
index 5b7086c3e395ad184031efe707ff86f96acec637..f779d55bc0eb7a836efb0b7bc7fc6449a8be8b4d 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java
@@ -36,10 +36,10 @@ public class GrpcUploadBinaryFileMetaDataTestFactory {
 	public static GrpcUploadBinaryFileMetaData.Builder createBuilder() {
 		return GrpcUploadBinaryFileMetaData.newBuilder()
 				.setVorgangId(VorgangTestFactory.ID)
-				.setContentType(OzgFileTestFactory.CONTENT_TYPE)
-				.setFileName(OzgFileTestFactory.NAME)
+				.setContentType(OzgCloudFileTestFactory.CONTENT_TYPE)
+				.setFileName(OzgCloudFileTestFactory.NAME)
 				.setContext(GrpcCallContextTestFactory.create())
-				.setSize(OzgFileTestFactory.SIZE)
+				.setSize(OzgCloudFileTestFactory.SIZE)
 				.setField(UploadedFilesReferenceTestFactory.NAME);
 	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f6333117bc70f54d72c955d009f83012b2bd6d3
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.test.annotation.DirtiesContext;
+
+import com.google.protobuf.ByteString;
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.common.test.DataITCase;
+import de.ozgcloud.vorgang.callcontext.TestCallContextAttachingInterceptor;
+import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
+import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
+import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
+import de.ozgcloud.vorgang.vorgang.Vorgang;
+import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
+import io.grpc.StatusRuntimeException;
+import io.grpc.stub.StreamObserver;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+
+@SpringBootTest(properties = {
+		"grpc.server.inProcessName=binary-file-test",
+		"grpc.server.port=-1",
+		"grpc.client.inProcess.address=in-process:binary-file-test"
+})
+@DirtiesContext
+@DataITCase
+public class OzgCloudFileITCase {
+
+	@GrpcClient("inProcess")
+	private BinaryFileServiceBlockingStub binaryFileServiceBlockingStub;
+	@GrpcClient("inProcess")
+	private BinaryFileServiceStub binaryFileServiceStub;
+
+	@Autowired
+	private MongoOperations mongoOperations;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
+
+	@BeforeEach
+	void init() {
+		mongoOperations.dropCollection(Vorgang.COLLECTION_NAME);
+		ozgCloudFileTestUtil.dropFileCollections();
+	}
+
+	@DisplayName("Find binary files meta data")
+	@Nested
+	class TestFindOzgCloudFilesMetaData {
+
+		private String fileId;
+
+		@BeforeEach
+		void initDatabase() {
+			fileId = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT).getId();
+		}
+
+		@Nested
+		class OnMatchingOrganisationEinheitIdAtVorgang {
+
+			@BeforeEach
+			void initDatabase() {
+				mongoOperations.save(VorgangTestFactory.create());
+			}
+
+			@Test
+			void shouldReturnStoredFile() {
+				var response = callFindBinaryFilesMetaData();
+
+				assertThat(response.getFileList()).hasSize(1).first().satisfies(file -> {
+					assertThat(file.getId()).isEqualTo(fileId);
+					assertThat(file.getName()).isEqualTo(OzgCloudFileTestFactory.NAME);
+					assertThat(file.getContentType()).isEqualTo(OzgCloudFileTestFactory.CONTENT_TYPE);
+				});
+			}
+		}
+
+		@Nested
+		class OnNonMatchingOrganisationEinheitIdOnVorgang {
+
+			@BeforeEach
+			void initDatabase() {
+				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
+			}
+
+			@Test
+			void shouldThrowStatusRuntimeException() {
+				assertThrows(StatusRuntimeException.class, () -> callFindBinaryFilesMetaData());
+			}
+		}
+
+		private GrpcFindFilesResponse callFindBinaryFilesMetaData() {
+			return prepareBinaryFileServiceBlockingStub().findBinaryFilesMetaData(
+					GrpcBinaryFilesRequestTestFactory.createBuilder().clearFileId().addFileId(fileId).build());
+		}
+
+	}
+
+	@Nested
+	@DisplayName("Download files of different sizes")
+	class TestDownloadFile {
+
+		@Nested
+		class OnMatchingOrganisationEinheitIdAtVorgang {
+
+			@BeforeEach
+			void initDatabase() {
+				mongoOperations.save(VorgangTestFactory.create());
+			}
+
+			@Test
+			void shouldReturnStoredSmallFile() {
+				var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId();
+
+				var file = downloadBinaryFile(fileId);
+
+				assertThat(file).hasSameSizeAs(OzgCloudFileTestFactory.CONTENT).isEqualTo(OzgCloudFileTestFactory.CONTENT);
+			}
+
+			@Test
+			void shouldReturnStoredFileWithSizeEqualChunkSize() {
+				var content = OzgCloudFileTestFactory.createContentInByte(FileService.CHUNK_SIZE);
+				var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(content).getId();
+
+				var file = downloadBinaryFile(fileId);
+
+				assertThat(file).hasSameSizeAs(content).isEqualTo(content);
+			}
+
+			@Test
+			void shouldReturnStoredLargeFile() {
+				var input = OzgCloudFileTestFactory.createContentInMiB(5);
+				var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(input).getId();
+
+				var downloadedFile = downloadBinaryFile(fileId);
+
+				assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input);
+			}
+		}
+
+		@Nested
+		class OnNonMatchingOrganisationEinheitIdAtVorgang {
+
+			@BeforeEach
+			void initDatabase() {
+				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
+			}
+
+			@Test
+			void shouldThrowStatusRuntimeException() {
+				var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId();
+
+				assertThrows(StatusRuntimeException.class, () -> downloadBinaryFile(fileId));
+			}
+		}
+
+		private byte[] downloadBinaryFile(String fileId) {
+			var request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(fileId).build();
+			var responseIterator = prepareBinaryFileServiceBlockingStub().getBinaryFileContent(request);
+			var content = new ByteArrayOutputStream();
+			while (responseIterator.hasNext()) {
+				ByteString chunkContent = responseIterator.next().getFileContent();
+				try {
+					content.write(chunkContent.toByteArray());
+				} catch (IOException e) {
+					fail(e);
+				}
+			}
+
+			return content.toByteArray();
+		}
+	}
+
+	private BinaryFileServiceBlockingStub prepareBinaryFileServiceBlockingStub() {
+		return binaryFileServiceBlockingStub.withInterceptors(new TestCallContextAttachingInterceptor());
+	}
+
+	@Nested
+	@DisplayName("Upload file")
+	class TestFileUpload {
+
+		private static final GrpcUploadBinaryFileMetaData REQUEST_METADATA = GrpcUploadBinaryFileMetaDataTestFactory.create();
+
+		private UploadTestStreamObserver responseObserver = new UploadTestStreamObserver();
+
+		@Nested
+		class OnMatchingOrganisationEinheitIdAtVorgang {
+
+			@BeforeEach
+			void init() {
+				mongoOperations.save(VorgangTestFactory.create());
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldSaveVorgangId() {
+				var fileId = uploadFileWithContent();
+
+				var file = mongoOperations.findById(fileId, OzgCloudFile.class);
+				assertThat(file.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldSaveFileContent() {
+				var fileId = uploadFileWithContent();
+
+				var file = mongoOperations.findById(fileId, OzgCloudFile.class);
+				try (var fileStream = ozgCloudFileTestUtil.getFileContent(file.getContentId())) {
+					byte[] fileContentFromDb = fileStream.readAllBytes();
+					assertThat(fileContentFromDb).isEqualTo(OzgCloudFileTestFactory.CONTENT);
+				}
+
+			}
+		}
+
+		@Nested
+		class OnMatchingNonOrganisationEinheitIdAtVorgang {
+
+			@BeforeEach
+			void init() {
+				mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73"));
+			}
+
+			@Test
+			void shouldThrowStatusRuntimeException() {
+				assertThatThrownBy(TestFileUpload.this::uploadFileWithContent).cause().isInstanceOf(StatusRuntimeException.class);
+			}
+		}
+
+		@SneakyThrows
+		private String uploadFileWithContent() {
+			GrpcUploadBinaryFileRequest metadataRequest = GrpcUploadBinaryFileRequest.newBuilder().setMetadata(REQUEST_METADATA).build();
+			GrpcUploadBinaryFileRequest contentRequest = GrpcUploadBinaryFileRequest.newBuilder()
+					.setFileContent(ByteString.copyFrom(OzgCloudFileTestFactory.CONTENT)).build();
+
+			StreamObserver<GrpcUploadBinaryFileRequest> observer = prepareBinaryFileServiceStub().uploadBinaryFileAsStream(responseObserver);
+
+			observer.onNext(metadataRequest);
+			observer.onNext(contentRequest);
+			observer.onCompleted();
+
+			return responseObserver.getFileIdFuture().get(10, TimeUnit.SECONDS);
+		}
+
+	}
+
+	@Nested
+	class TestUpdateOzgCloudFile {
+
+		private static final String NEW_NAME = "new_name_" + LoremIpsum.getInstance().getWords(1);
+		private static final String NEW_CONTENT_TYPE = "new_content_type_" + LoremIpsum.getInstance().getWords(1);
+		private static final byte[] NEW_CONTENT = LoremIpsum.getInstance().getWords(10).getBytes();
+
+		private UpdateTestStreamObserver responseStreamObserver = new UpdateTestStreamObserver();
+
+		private OzgCloudFile ozgCloudFile;
+
+		@BeforeEach
+		void init() {
+			mongoOperations.save(VorgangTestFactory.create());
+			ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT);
+		}
+
+		@Test
+		void shouldUpdateMetadata() {
+			updateFileMetadata(ozgCloudFile.getId());
+
+			var updatedFile = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(updatedFile.getName()).isEqualTo(NEW_NAME);
+			assertThat(updatedFile.getContentType()).isEqualTo(NEW_CONTENT_TYPE);
+			assertThat(ozgCloudFileTestUtil.getFileContent(ozgCloudFile.getContentId())).hasBinaryContent(OzgCloudFileTestFactory.CONTENT);
+		}
+
+		@Test
+		void shouldUpdateFileWithContent() {
+			updateFileWithContent(ozgCloudFile.getId());
+
+			var updatedFile = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(updatedFile.getName()).isEqualTo(NEW_NAME);
+			assertThat(updatedFile.getContentType()).isEqualTo(NEW_CONTENT_TYPE);
+			assertThat(ozgCloudFileTestUtil.getFileContent(updatedFile.getContentId())).hasBinaryContent(NEW_CONTENT);
+		}
+
+		@Test
+		void shouldThrowExceptionWhenFileNotFound() {
+			assertThatThrownBy(() -> updateFileMetadata("not_existing_file_id")).cause().isInstanceOf(StatusRuntimeException.class);
+		}
+
+		@SneakyThrows
+		private void updateFileMetadata(String fileId) {
+			var observer = prepareBinaryFileServiceStub().updateBinaryFile(responseStreamObserver);
+
+			observer.onNext(buildMetadataRequest(fileId));
+			observer.onCompleted();
+
+			responseStreamObserver.getFileIdFuture().get(10, TimeUnit.SECONDS);
+		}
+
+		@SneakyThrows
+		private void updateFileWithContent(String fileId) {
+			var contentRequest = GrpcUpdateBinaryFileRequest.newBuilder().setContent(ByteString.copyFrom(NEW_CONTENT)).build();
+
+			var observer = prepareBinaryFileServiceStub().updateBinaryFile(responseStreamObserver);
+
+			observer.onNext(buildMetadataRequest(fileId));
+			observer.onNext(contentRequest);
+			observer.onCompleted();
+
+			responseStreamObserver.getFileIdFuture().get(10, TimeUnit.SECONDS);
+		}
+
+		private GrpcUpdateBinaryFileRequest buildMetadataRequest(String fileId) {
+			var metadata = GrpcBinaryFileMetadata.newBuilder().setFileId(fileId).setFileName(NEW_NAME).setContentType(NEW_CONTENT_TYPE).build();
+			return GrpcUpdateBinaryFileRequest.newBuilder().setMetadata(metadata).build();
+		}
+	}
+
+	private BinaryFileServiceStub prepareBinaryFileServiceStub() {
+		return binaryFileServiceStub.withInterceptors(new TestCallContextAttachingInterceptor());
+	}
+
+	@Getter
+	@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+	static class UploadTestStreamObserver implements StreamObserver<GrpcUploadBinaryFileResponse> {
+
+		private final CompletableFuture<String> fileIdFuture = new CompletableFuture<>();
+		private String fileId;
+
+		@Override
+		public void onNext(GrpcUploadBinaryFileResponse value) {
+			fileId = value.getFileId();
+		}
+
+		@Override
+		public void onError(Throwable t) {
+			fileIdFuture.completeExceptionally(t);
+		}
+
+		@Override
+		public void onCompleted() {
+			fileIdFuture.complete(fileId);
+		}
+	}
+
+	@Getter
+	@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+	static class UpdateTestStreamObserver implements StreamObserver<GrpcUpdateBinaryFileResponse> {
+
+		private final CompletableFuture<Void> fileIdFuture = new CompletableFuture<>();
+
+		@Override
+		public void onNext(final GrpcUpdateBinaryFileResponse value) {
+		}
+
+		@Override
+		public void onError(Throwable t) {
+			fileIdFuture.completeExceptionally(t);
+		}
+
+		@Override
+		public void onCompleted() {
+			fileIdFuture.complete(null);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..6efa60a53808485f8b1ed5c8252887c1a10f6905
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoOperations;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.common.test.DataITCase;
+import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
+import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
+import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
+
+@DataITCase
+class OzgCloudFileRepositoryITCase {
+
+	@Autowired
+	private OzgCloudFileRepository repository;
+
+	@Autowired
+	private MongoOperations mongoOperations;
+	@Autowired
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
+
+	@BeforeEach
+	void clearDB() {
+		mongoOperations.dropCollection(OzgCloudFile.COLLECTION_NAME);
+		mongoOperations.dropCollection("fs.files");
+		mongoOperations.dropCollection("fs.chunks");
+	}
+
+	@Nested
+	class TestSave {
+
+		private OzgCloudFile ozgCloudFile;
+
+		@BeforeEach
+		void init() {
+			var contentId = ozgCloudFileTestUtil.storeFileContent(OzgCloudFileTestFactory.CONTENT);
+			ozgCloudFile = OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId(contentId).build();
+		}
+
+		@Test
+		void shouldSaveFile() {
+			var result = repository.save(ozgCloudFile);
+
+			assertThat(result).usingRecursiveComparison().ignoringFields("id", OzgCloudFile.FIELD_VERSION).isEqualTo(ozgCloudFile);
+			var savedOzgCloudFile = mongoOperations.findById(result.getId(), OzgCloudFile.class);
+			assertThat(result).usingRecursiveComparison().isEqualTo(savedOzgCloudFile);
+		}
+
+		@Test
+		void shouldIncreaseVersion() {
+			var result = repository.save(ozgCloudFile);
+
+			assertThat(result.getVersion()).isEqualTo(1);
+		}
+
+		@Test
+		void shouldSetContentSize() {
+			var ozgCloudFile = this.ozgCloudFile.toBuilder().size(new Random().nextLong()).build();
+
+			var result = repository.save(ozgCloudFile);
+
+			assertThat(result.getSize()).isEqualTo(OzgCloudFileTestFactory.SIZE);
+		}
+
+		@Test
+		void shouldFailIfContentMissing() {
+			var ozgCloudFile = OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId("missing").build();
+
+			assertThatThrownBy(() -> repository.save(ozgCloudFile)).isInstanceOf(NotFoundException.class);
+		}
+	}
+
+	@Nested
+	class TestUploadContent {
+
+		@Test
+		void shouldStoreFiles() {
+			var content = IncomingFileTestFactory.getFilledChunk(45);
+
+			uploadContent(content);
+
+			assertThat(ozgCloudFileTestUtil.getFileContentByFileName()).hasBinaryContent(content);
+		}
+
+		private void uploadContent(byte[] content) {
+			repository.uploadContent(UploadedFilesReferenceTestFactory.create(), new ByteArrayInputStream(content));
+		}
+	}
+
+	@Nested
+	class TestGetFileContent {
+
+		@Test
+		void shouldLoadFile() {
+			var fileContent = IncomingFileTestFactory.getFilledChunk(45);
+			var ozgCloudFileId = FileId.from(ozgCloudFileTestUtil.saveOzgCloudFile(fileContent).getId());
+
+			var result = repository.getFileContent(ozgCloudFileId);
+
+			assertThat(result).hasBinaryContent(fileContent);
+		}
+
+		@Test
+		void shouldThrowExceptionWhenFileNotFound() {
+			var fileId = FileId.from("not-existing");
+
+			assertThatThrownBy(() -> repository.getFileContent(fileId))
+					.isInstanceOf(NotFoundException.class)
+					.hasMessageContaining(fileId.toString());
+		}
+	}
+
+	@Nested
+	class TestFindAll {
+
+		private OzgCloudFile ozgCloudFile;
+
+		@BeforeEach
+		void initData() {
+			ozgCloudFile = mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).build());
+		}
+
+		@Test
+		void shouldReturnFile() {
+			var result = findAll(List.of(ozgCloudFile.getId()));
+
+			assertThat(result).hasSize(1).first().usingRecursiveComparison().isEqualTo(ozgCloudFile);
+		}
+
+		@Test
+		void shouldReturnAllFiles() {
+			var otherFileId = mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).build());
+
+			var result = findAll(List.of(ozgCloudFile.getId(), otherFileId.getId()));
+
+			assertThat(result).hasSize(2).usingRecursiveFieldByFieldElementComparator().containsExactly(ozgCloudFile, otherFileId);
+		}
+
+		private List<OzgCloudFile> findAll(Collection<String> fileIds) {
+			return repository.findAll(fileIds.stream().map(FileId::from).toList()).toList();
+		}
+	}
+
+	@Nested
+	class TestDeleteAllByVorgang {
+
+		@Test
+		void shouldDeleteFiles() {
+			ozgCloudFileTestUtil.saveOzgCloudFile();
+
+			repository.deleteAllByVorgang(VorgangTestFactory.ID);
+
+			var loaded = mongoOperations.findAll(OzgCloudFile.class);
+			assertThat(loaded).isEmpty();
+			assertThatThrownBy(ozgCloudFileTestUtil::getFileContentByFileName).cause().isInstanceOf(FileNotFoundException.class);
+		}
+
+		@Test
+		void shouldNotDeleteOtherFiles() {
+			var fileContent = IncomingFileTestFactory.getFilledChunk(1);
+			var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(fileContent);
+
+			repository.deleteAllByVorgang("other");
+
+			var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(loaded).usingRecursiveComparison().isEqualTo(ozgCloudFile);
+			assertThat(ozgCloudFileTestUtil.getFileContentByFileName()).hasBinaryContent(fileContent);
+		}
+	}
+
+	@Nested
+	class TestPatch {
+
+		private static final String NEW_NAME = "newName";
+		private static final Map<String, Object> PATCH_MAP = Map.of(OzgCloudFile.FIELD_FILE_NAME, NEW_NAME);
+
+		private OzgCloudFile ozgCloudFile;
+
+		@BeforeEach
+		void init() {
+			ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile();
+		}
+
+		@Test
+		void shouldPatchOzgCloudFile() {
+			repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), PATCH_MAP);
+
+			var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(loaded.getName()).isEqualTo(NEW_NAME);
+		}
+
+		@Test
+		void shouldIncreaseVersion() {
+			repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), PATCH_MAP);
+
+			var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(loaded.getVersion()).isEqualTo(2);
+		}
+
+		@Test
+		void shouldPatchContentSize() {
+			var content = LoremIpsum.getInstance().getWords(23).getBytes();
+			var newContentId = ozgCloudFileTestUtil.storeFileContent(content);
+			var patchMap = Map.<String, Object>of(OzgCloudFile.FIELD_CONTENT_ID, newContentId);
+
+			repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), patchMap);
+
+			var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class);
+			assertThat(loaded.getSize()).isEqualTo(content.length);
+		}
+
+		@Test
+		void shouldThrowConcurrentModificationException() {
+			var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile();
+
+			var id = FileId.from(ozgCloudFile.getId());
+			var version = ozgCloudFile.getVersion() - 1;
+			assertThatThrownBy(() -> repository.patch(id, version, PATCH_MAP)).isInstanceOf(ConcurrentModificationException.class);
+		}
+	}
+
+	@Nested
+	class TestDeleteContent {
+
+		@Test
+		void shouldDeleteContent() {
+			var contentId = ozgCloudFileTestUtil.storeFileContent(OzgCloudFileTestFactory.CONTENT);
+
+			repository.deleteContent(contentId);
+
+			assertThatThrownBy(() -> ozgCloudFileTestUtil.getFileContentByFileName()).cause().isInstanceOf(FileNotFoundException.class);
+		}
+	}
+}
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bf5641b33a42b912b273db06a79d648f74181e9
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.InputStream;
+
+import org.bson.types.ObjectId;
+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 org.springframework.data.mongodb.gridfs.GridFsTemplate;
+import org.springframework.data.mongodb.gridfs.GridFsUpload;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
+
+class OzgCloudFileRepositoryTest {
+
+	private static final UploadedFilesReference UPLOAD_REFERENCE = UploadedFilesReferenceTestFactory.create();
+
+	@Spy
+	@InjectMocks
+	private OzgCloudFileRepository repository;
+
+	@Mock
+	private GridFsTemplate gridFsTemplate;
+	@Mock
+	private InputStream contentStream;
+
+	@Nested
+	class TestAddContentStream {
+
+		private static final ObjectId OBJECT_ID = new ObjectId();
+
+		@Mock
+		private GridFsUpload<ObjectId> gridFsUpload;
+
+		@BeforeEach
+		void init() {
+			doReturn(gridFsUpload).when(repository).buildGridFsUpload(any(), any());
+			when(gridFsTemplate.store(any())).thenReturn(OBJECT_ID);
+		}
+
+		@Test
+		void shouldCallBuildGridFsUpload() {
+			addContentStream();
+
+			verify(repository).buildGridFsUpload(UPLOAD_REFERENCE, contentStream);
+		}
+
+		@Test
+		void shouldCallGridFsTemplateStore() {
+			addContentStream();
+
+			verify(gridFsTemplate).store(gridFsUpload);
+		}
+
+		@Test
+		void shouldReturnFileId() {
+
+			var result = addContentStream();
+
+			assertThat(result).hasToString(OBJECT_ID.toString());
+		}
+
+		private String addContentStream() {
+			return repository.uploadContent(UPLOAD_REFERENCE, contentStream);
+		}
+	}
+
+	@Nested
+	class TestBuildGridFsUpload {
+
+		@Test
+		void shouldSetContent() {
+
+			var result = buildGridFsUpload();
+
+			assertThat(result.getContent()).isSameAs(contentStream);
+		}
+
+		@Test
+		void shouldCallCreateFilePath() {
+			buildGridFsUpload();
+
+			verify(repository).createFilePath(UPLOAD_REFERENCE);
+		}
+
+		@Test
+		void shouldSetFilename() {
+			var fileName = LoremIpsum.getInstance().getName();
+			doReturn(fileName).when(repository).createFilePath(UPLOAD_REFERENCE);
+
+			var result = buildGridFsUpload();
+
+			assertThat(result.getFilename()).isEqualTo(fileName);
+		}
+
+		private GridFsUpload<ObjectId> buildGridFsUpload() {
+			return repository.buildGridFsUpload(UPLOAD_REFERENCE, contentStream);
+		}
+	}
+
+	@Nested
+	class TestCreateFilePath {
+		@Test
+		void shouldCreatePath() {
+			String path = repository.createFilePath(UploadedFilesReferenceTestFactory.create());
+
+			assertThat(path).isEqualTo(
+					UploadedFilesReferenceTestFactory.VORGANG_ID + "/" +
+							CallContextTestFactory.CLIENT + "/" +
+							UploadedFilesReferenceTestFactory.NAME + "/" +
+							OzgCloudFileTestFactory.NAME);
+		}
+	}
+
+}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java
similarity index 63%
rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java
rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java
index bfa02e7e244eba5a1465c4cf0b9193c097e5c9e3..124cfe2a44122a9d576477520e727226993ea5cb 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java
@@ -23,34 +23,47 @@
  */
 package de.ozgcloud.vorgang.files;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.util.Random;
+import java.util.UUID;
 
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
+import de.ozgcloud.vorgang.callcontext.UserTestFactory;
 import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 
-public class OzgFileTestFactory {
+public class OzgCloudFileTestFactory {
 
-	public static final FileId ID = FileId.createNew();
-	public static final String NAME = "Filename1.pdf";
+	public static final String ID_STR = UUID.randomUUID().toString();
+	public static final FileId ID = FileId.from(ID_STR);
+	public static final long VERSION = new Random().nextLong();
+	public static final String NAME = LoremIpsum.getInstance().getWords(1);
 	public static final String CONTENT_TYPE = IncomingFileTestFactory.CONTENT_TYPE_STR;
+	public static final String CONTENT_ID = UUID.randomUUID().toString();
+	public static final String CLIENT = CallContextTestFactory.CLIENT;
+	public static final String FIELD_NAME = LoremIpsum.getInstance().getWords(1);
+	public static final String CREATED_BY = UserTestFactory.ID;
 
 	public static final byte[] CONTENT = "juhu, ein bild".getBytes();
 	public static final long SIZE = CONTENT.length;
-	public static final InputStream CONTENT_STREAM = new ByteArrayInputStream(CONTENT);
 
-	public static OzgFile create() {
+	public static OzgCloudFile create() {
 		return createBuilder().build();
 	}
 
-	public static OzgFile.OzgFileBuilder createBuilder() {
-		return OzgFile.builder()
-				.id(ID)
+	public static OzgCloudFile.OzgCloudFileBuilder createBuilder() {
+		return OzgCloudFile.builder()
+				.id(ID_STR)
+				.version(VERSION)
 				.vorgangId(VorgangTestFactory.ID)
 				.name(NAME)
 				.contentType(CONTENT_TYPE)
-				.size(SIZE);
+				.size(SIZE)
+				.contentId(CONTENT_ID)
+				.client(CLIENT)
+				.createdBy(CREATED_BY)
+				.fieldName(FIELD_NAME);
 	}
 
 	public static byte[] createContentInMiB(int sizeInMiB) {
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..e09d825686f62ad82ce881f3f2ea44b512cf1d70
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.gridfs.GridFsTemplate;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class OzgCloudFileTestUtil {
+
+	private final MongoOperations mongoOperations;
+	private final GridFsTemplate gridFsTemplate;
+
+	public OzgCloudFile saveOzgCloudFile() {
+		return saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT);
+	}
+
+	public OzgCloudFile saveOzgCloudFile(byte[] content) {
+		var contentId = storeFileContent(content);
+		return mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId(contentId).build());
+	}
+
+	public String storeFileContent(byte[] content) {
+		return gridFsTemplate.store(new ByteArrayInputStream(content), UploadedFilesReferenceTestFactory.getGridFsFileName()).toString();
+	}
+
+	public InputStream getFileContentByFileName() {
+		return gridFsTemplate.getResource(UploadedFilesReferenceTestFactory.getGridFsFileName()).getContent();
+	}
+
+	public InputStream getFileContent(String contentId) {
+		var gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where(OzgCloudFileRepository.GRID_FS_FIELD_ID).is(contentId)));
+		return gridFsTemplate.getResource(gridFSFile).getContent();
+	}
+
+	public void  dropFileCollections() {
+		mongoOperations.dropCollection(OzgCloudFile.class);
+		mongoOperations.dropCollection("fs.files");
+		mongoOperations.dropCollection("fs.chunks");
+	}
+}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a39c5a120d0f07b363ae2e99489ae326aae8d5ce
--- /dev/null
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.vorgang.files;
+
+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.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+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.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import lombok.SneakyThrows;
+
+class UploadServerStreamObserverTest {
+
+	private static final String FIELD_CONTENT_ID_FUTURE = "contentIdFuture";
+	private static final String FIELD_FILE_METADATA = "fileMetadata";
+	private static final String FIELD_PIPED_OUTPUT = "pipedOutput";
+
+	@Mock
+	private Consumer<UploadTestRequest> requestVerifier;
+	@Mock
+	private Predicate<UploadTestRequest> hasMetadata;
+	@Mock
+	private BiFunction<MetadataTest, Optional<String>, FileId> metadataUploader;
+	@Mock
+	private BiFunction<MetadataTest, InputStream, CompletableFuture<String>> fileContentUploader;
+	@Mock
+	private Function<UploadTestRequest, byte[]> getFileContent;
+	@Mock
+	private Function<UploadTestRequest, MetadataTest> metadataMapper;
+	@Mock
+	private Consumer<FileId> responseConsumer;
+	@Mock
+	private Consumer<String> cleanupFileContent;
+	@Mock
+	private UploadTestRequest fileUploadRequest;
+
+	private UploadServerStreamObserver<MetadataTest, UploadTestRequest> uploadStreamObserver;
+
+	@BeforeEach
+	void init() {
+		// Ich habe es anders nicht hinbekommen den Observer zu initialisieren.
+		// Wenn @InjectMocks verwendet wird, schafft mockito nicht die Mocks richtig zu initialisieren und \
+		// wenn TestOnCompleted.shouldCallResponseConsumer gestartet wird, wird metadataVerifier als responseConsumer gesetzt.
+		// Und in TestStoreMetadata#shouldCallMetadataVerifier passiert es umgekehrt.
+		// In Debug-Modus funktioniert alles richtig.
+		uploadStreamObserver = spy(UploadServerStreamObserver.<MetadataTest, UploadTestRequest>builder()
+				.requestVerifier(requestVerifier)
+				.hasMetadata(hasMetadata)
+				.metadataUploader(metadataUploader)
+				.fileContentUploader(fileContentUploader)
+				.getFileContent(getFileContent)
+				.metadataMapper(metadataMapper)
+				.responseConsumer(responseConsumer)
+				.cleanupFileContent(cleanupFileContent)
+				.build());
+	}
+
+	private interface UploadTestRequest {
+	}
+
+	private interface MetadataTest {
+	}
+
+	@Nested
+	class TestOnNext {
+
+		@Nested
+		class TestWithMetadata {
+
+			@BeforeEach
+			void init() {
+				when(hasMetadata.test(any())).thenReturn(true);
+				doNothing().when(uploadStreamObserver).setMetadata(any());
+			}
+
+			@Test
+			void shouldCallSetMetadata() {
+				onNext();
+
+				verify(uploadStreamObserver).setMetadata(fileUploadRequest);
+			}
+
+			@Test
+			void shouldNotCallStoreFileContent() {
+				onNext();
+
+				verify(uploadStreamObserver, never()).storeFileContent(any());
+			}
+		}
+
+		@Nested
+		class TestWithFileContent {
+
+			private final CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID);
+
+			@BeforeEach
+			void init() {
+				when(hasMetadata.test(any())).thenReturn(false);
+				doNothing().when(uploadStreamObserver).storeFileContent(any());
+				doReturn(contentIdFuture).when(uploadStreamObserver).initContentUpload();
+			}
+
+			@Test
+			void shouldCallStoreFileContent() {
+				onNext();
+
+				verify(uploadStreamObserver).storeFileContent(fileUploadRequest);
+			}
+
+			@Test
+			void shouldNotCallStoreMetadata() {
+				onNext();
+
+				verify(uploadStreamObserver, never()).setMetadata(any());
+			}
+
+			@Test
+			void shouldCallInitContentUploadOnce() {
+				onNext();
+				onNext();
+
+				verify(uploadStreamObserver).initContentUpload();
+			}
+
+			@Test
+			void shouldSetContentIdFuture() {
+				onNext();
+
+				assertThat(uploadStreamObserver).extracting(FIELD_CONTENT_ID_FUTURE, as(COMPLETABLE_FUTURE)).isCompletedWithValue(OzgCloudFileTestFactory.CONTENT_ID);
+			}
+		}
+
+		private void onNext() {
+			uploadStreamObserver.onNext(fileUploadRequest);
+		}
+	}
+
+	@Nested
+	class TestInitContentUpload {
+
+		private final CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID);
+
+		@Mock
+		private PipedInputStream pipedInputStream;
+		@Mock
+		private MetadataTest metadata;
+
+		@BeforeEach
+		void init() {
+			when(fileContentUploader.apply(any(), any())).thenReturn(contentIdFuture);
+			doReturn(pipedInputStream).when(uploadStreamObserver).initPipedStreams();
+			ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata));
+		}
+
+		@Test
+		void shouldCallInitPipedStreams() {
+			initContentUpload();
+
+			verify(uploadStreamObserver).initPipedStreams();
+		}
+
+		@Test
+		void shouldCallFileContentUploader() {
+			initContentUpload();
+
+			verify(fileContentUploader).apply(metadata, pipedInputStream);
+		}
+
+		private CompletableFuture<String> initContentUpload() {
+			return uploadStreamObserver.initContentUpload();
+		}
+	}
+
+	@Nested
+	class TestSetMetadata {
+
+		@Mock
+		private PipedInputStream pipedInputStream;
+		@Mock
+		private CompletableFuture<FileId> fileIdFuture;
+		@Mock
+		private MetadataTest metadata;
+
+		@BeforeEach
+		void init() {
+			when(metadataMapper.apply(any())).thenReturn(metadata);
+		}
+
+		@Test
+		void shouldCallMetadataVerifier() {
+			setMetadata();
+
+			verify(requestVerifier).accept(fileUploadRequest);
+		}
+
+		@Test
+		void shouldCallMetadataMapper() {
+			setMetadata();
+
+			verify(metadataMapper).apply(fileUploadRequest);
+		}
+
+		@Test
+		void shouldCallSetFileMetadata() {
+			setMetadata();
+
+			assertThat(uploadStreamObserver).extracting(FIELD_FILE_METADATA, as(ATOMIC_REFERENCE)).hasValue(metadata);
+		}
+
+		@Test
+		void shouldThrowExceptionIfMetadataAlreadyStored() {
+			setMetadata();
+
+			assertThrows(OzgCloudFileUploadException.class, this::setMetadata);
+			assertThrows(OzgCloudFileUploadException.class, this::setMetadata);
+		}
+
+		private void setMetadata() {
+			uploadStreamObserver.setMetadata(fileUploadRequest);
+		}
+	}
+
+	@Nested
+	class TestStoreFileContent {
+
+		@Nested
+		class TestStore {
+
+			@Mock
+			private PipedOutputStream pipedOutputStream;
+			@Mock
+			private MetadataTest metadata;
+
+			@BeforeEach
+			void init() {
+				ReflectionTestUtils.setField(uploadStreamObserver, FIELD_PIPED_OUTPUT, pipedOutputStream);
+				ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata));
+			}
+
+			@Test
+			void shouldCallGetFileContent() {
+				storeFileContent();
+
+				verify(getFileContent).apply(fileUploadRequest);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldWriteFileContent() {
+				byte[] fileContent = new byte[] { 1, 2, 3 };
+				doReturn(fileContent).when(getFileContent).apply(any());
+
+				storeFileContent();
+
+				verify(pipedOutputStream).write(fileContent);
+			}
+		}
+
+		@Nested
+		class TestWithException {
+
+			@Test
+			void shouldThrowExceptionIfNoMetadata() {
+				assertThrows(OzgCloudFileUploadException.class, TestStoreFileContent.this::storeFileContent);
+			}
+		}
+
+		private void storeFileContent() {
+			uploadStreamObserver.storeFileContent(fileUploadRequest);
+		}
+	}
+
+	@Nested
+	class TestOnError {
+
+		@Mock
+		private Throwable throwable;
+		@Mock
+		private MetadataTest metadata;
+
+		@BeforeEach
+		void init() {
+			ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata));
+		}
+
+		@Test
+		void shouldCallCloseOutputPipe() {
+			uploadStreamObserver.onError(throwable);
+
+			verify(uploadStreamObserver).closeOutputPipe();
+		}
+
+		@Test
+		void shouldCloseInputPipe() {
+			uploadStreamObserver.onError(throwable);
+
+			verify(uploadStreamObserver).closeInputPipe();
+		}
+	}
+
+	@Nested
+	class TestOnCompleted {
+
+		@Mock
+		private MetadataTest metadata;
+
+		@BeforeEach
+		void init() {
+			ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata));
+			doReturn(Optional.of(OzgCloudFileTestFactory.CONTENT_ID)).when(uploadStreamObserver).getContentId();
+		}
+
+		@Test
+		void shouldCallGetContentId() {
+			uploadStreamObserver.onCompleted();
+
+			verify(uploadStreamObserver).getContentId();
+		}
+
+		@Test
+		void shouldCallMetadataUploader() {
+			uploadStreamObserver.onCompleted();
+
+			verify(metadataUploader).apply(metadata, Optional.of(OzgCloudFileTestFactory.CONTENT_ID));
+		}
+
+		@Test
+		void shouldCallResponseConsumer() {
+			when(metadataUploader.apply(any(), any())).thenReturn(OzgCloudFileTestFactory.ID);
+
+			uploadStreamObserver.onCompleted();
+
+			verify(responseConsumer).accept(OzgCloudFileTestFactory.ID);
+		}
+
+		@Test
+		void shouldThrowException() {
+			when(metadataUploader.apply(any(), any())).thenThrow(new RuntimeException());
+
+			assertThrows(TechnicalException.class, uploadStreamObserver::onCompleted);
+		}
+
+		@Test
+		void shouldCallCleanupFileContent() {
+			when(metadataUploader.apply(any(), any())).thenThrow(new RuntimeException());
+
+			try {
+				uploadStreamObserver.onCompleted();
+			} catch (TechnicalException e) {
+				// expected
+			}
+
+			verify(cleanupFileContent).accept(OzgCloudFileTestFactory.CONTENT_ID);
+		}
+
+		@Test
+		void shouldCallCloseOutputPipe() {
+			uploadStreamObserver.onCompleted();
+
+			verify(uploadStreamObserver).closeOutputPipe();
+		}
+
+	}
+
+	@Nested
+	class TestGetContentId {
+
+		@Nested
+		class TestWithContentId {
+
+			@Spy
+			private CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID);
+
+			@BeforeEach
+			@SneakyThrows
+			void init() {
+				ReflectionTestUtils.setField(uploadStreamObserver, FIELD_CONTENT_ID_FUTURE, contentIdFuture);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldCallGetContentIdFuture() {
+				uploadStreamObserver.getContentId();
+
+				verify(contentIdFuture).get(UploadServerStreamObserver.TIMEOUT_MINUTES, TimeUnit.MINUTES);
+			}
+
+			@Test
+			void shouldCallCloseInputPipe() {
+				uploadStreamObserver.getContentId();
+
+				verify(uploadStreamObserver).closeInputPipe();
+			}
+
+			@Test
+			void shouldReturnContentId() {
+				var result = uploadStreamObserver.getContentId();
+
+				assertThat(result).hasValue(OzgCloudFileTestFactory.CONTENT_ID);
+			}
+
+			@SneakyThrows
+			@DisplayName("should throw exception and close input pipe when")
+			@ParameterizedTest(name = "{0} thrown")
+			@ValueSource(classes = { ExecutionException.class, TimeoutException.class, InterruptedException.class })
+			void shouldThrowException(Class<? extends Exception> exceptionClass) {
+				doThrow(exceptionClass).when(contentIdFuture).get(anyLong(), any());
+
+				assertThrows(TechnicalException.class, uploadStreamObserver::getContentId);
+				verify(uploadStreamObserver).closeInputPipe();
+			}
+		}
+
+		@Test
+		void shouldReturnWhenNoContentId() {
+			var result = uploadStreamObserver.getContentId();
+
+			assertThat(result).isEmpty();
+		}
+
+	}
+}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java
deleted file mode 100644
index 3ea245760090dff1c7af20282f083503f6114748..0000000000000000000000000000000000000000
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.vorgang.files;
-
-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.io.IOException;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-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.Mock;
-
-import com.google.protobuf.ByteString;
-
-import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.vorgang.common.security.PolicyService;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
-import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
-import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory;
-import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
-import io.grpc.stub.StreamObserver;
-
-class UploadStreamObserverTest {
-
-	@Mock
-	private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver;
-	@Mock
-	private FileService service;
-	@Mock
-	private PolicyService policyService;
-
-	private GrpcUploadBinaryFileRequest fileUploadRequest;
-	private UploadStreamObserver uploadStreamObserver;
-
-	@Nested
-	class TestSteamObserver {
-
-		private PipedInputStream pipedInputStream = new PipedInputStream();
-		private PipedOutputStream pipedOutputStream = new PipedOutputStream();
-
-		@BeforeEach
-		void init() {
-			uploadStreamObserver = new UploadStreamObserver(responseObserver, service, policyService);
-			fileUploadRequest = GrpcUploadBinaryFileRequestTestFactory.createBuilder()
-					.setFileContent(ByteString.copyFrom(IncomingFileTestFactory.CONTENT)).build();
-		}
-
-		@Nested
-		class TestInitializing {
-
-			@Test
-			void shouldConnectPipedStreams() {
-				assertDoesNotThrow(() -> uploadStreamObserver.connectInputStreamToOutputStream(pipedInputStream, pipedOutputStream));
-			}
-
-			@Test
-			void shouldWriteToPipedOutputStream() {
-				uploadStreamObserver.connectInputStreamToOutputStream(pipedInputStream, pipedOutputStream);
-
-				assertDoesNotThrow(() -> pipedOutputStream.write(7574557));
-			}
-
-			@Test
-			void shouldInitPipedStreams() throws Exception {
-				uploadStreamObserver.initPipedStreams();
-
-				PipedOutputStream pipedOutputStream = uploadStreamObserver.getPipedOutput();
-				uploadStreamObserver.connectInputStreamToOutputStream(uploadStreamObserver.getPipedInput(), pipedOutputStream);
-
-				assertDoesNotThrow(() -> pipedOutputStream.write(7574557));
-			}
-
-			@Test
-			void shouldWriteFileContentToPipedOutputError() {
-				uploadStreamObserver.initPipedStreams();
-
-				uploadStreamObserver.storeFileContent(fileUploadRequest, uploadStreamObserver.getPipedOutput());
-
-				assertThatThrownBy(() -> uploadStreamObserver.getPipedInput().read()).isInstanceOf(IOException.class);
-			}
-
-			@Test
-			void shouldWriteFileContentToPipedOutput() throws IOException, InterruptedException, ExecutionException, TimeoutException {
-				initPipedStreams();
-
-				CompletableFuture<Integer> resFuture = writeFileContent();
-
-				assertThat(resFuture.get(30, TimeUnit.SECONDS) > 0).isTrue();
-
-			}
-
-			private void initPipedStreams() {
-				uploadStreamObserver.initPipedStreams();
-				uploadStreamObserver.connectInputStreamToOutputStream(uploadStreamObserver.getPipedInput(), uploadStreamObserver.getPipedOutput());
-			}
-
-			private CompletableFuture<Integer> writeFileContent() {
-				uploadStreamObserver.storeFileContent(fileUploadRequest, uploadStreamObserver.getPipedOutput());
-
-				CompletableFuture<Integer> resFuture = CompletableFuture.supplyAsync(() -> {
-					try {
-						byte[] buffer = new byte[IncomingFileTestFactory.CONTENT.length];
-						return uploadStreamObserver.getPipedInput().read(buffer);
-					} catch (IOException e) {
-						return -1;
-					}
-				});
-				return resFuture;
-			}
-
-			@Test
-			void shouldHandleException() {
-				final IOException iOException = new IOException("test");
-				assertThrows(TechnicalException.class, () -> uploadStreamObserver.handleException(iOException));
-
-				verify(responseObserver).onNext(any());
-			}
-		}
-
-		@Nested
-		class TestStoreMetaData {
-
-			@BeforeEach
-			void init() {
-				when(service.uploadFileStreamAsync(any(), any(), any(), any())).thenReturn(CompletableFuture.completedFuture(FileId.createNew()));
-			}
-
-			@Test
-			void shouldInitPipedStreamsAndStoreMetadata() throws TimeoutException, InterruptedException, ExecutionException {
-				var fileIdFuture = uploadStreamObserver.storeMetaData(fileUploadRequest);
-
-				assertThat(fileIdFuture.get(30, TimeUnit.SECONDS)).isNotNull();
-			}
-
-			@Test
-			void shouldStoreMetadata() throws InterruptedException, ExecutionException, TimeoutException {
-				var fileIdFuture = uploadStreamObserver.storeMetaData(fileUploadRequest);
-
-				assertThat(fileIdFuture.get(30, TimeUnit.SECONDS)).isNotNull();
-			}
-
-			@Test
-			void shouldStoreFileMetadata() throws InterruptedException, ExecutionException, TimeoutException {
-				uploadStreamObserver.initPipedStreams();
-				uploadStreamObserver.storeFileMetaData(fileUploadRequest, uploadStreamObserver.getPipedInput(),
-						uploadStreamObserver.getPipedOutput());
-
-				verify(service).uploadFileStreamAsync(any(), any(), any(), any());
-			}
-		}
-
-		@Nested
-		class TestBuildUploadFilesReferences {
-
-			@Test
-			void shouldContainsVorgangId() {
-				var uploadFilesReference = buildUploadFilesReferences();
-
-				assertThat(uploadFilesReference.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
-			}
-
-			@Test
-			void shouldContainsName() {
-				var uploadFilesReference = buildUploadFilesReferences();
-
-				assertThat(uploadFilesReference.getName()).isEqualTo(UploadedFilesReferenceTestFactory.NAME);
-			}
-
-			@Test
-			void shouldContainsClient() {
-				var uploadFilesReference = buildUploadFilesReferences();
-
-				assertThat(uploadFilesReference.getClient()).isEqualTo(GrpcBinaryFileService.CLIENT);
-			}
-
-			private UploadedFilesReference buildUploadFilesReferences() {
-				return uploadStreamObserver.buildUploadFilesReferences(GrpcUploadBinaryFileRequestTestFactory.create());
-			}
-		}
-
-		@Nested
-		class TestBuildOzgFile {
-
-			@Test
-			void shouldContainsName() {
-				var ozgFile = buildOzgFile();
-
-				assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME);
-			}
-
-			@Test
-			void shouldContainsSize() {
-				var ozgFile = buildOzgFile();
-
-				assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE);
-			}
-
-			@Test
-			void shouldContainsContentType() {
-				var ozgFile = buildOzgFile();
-
-				assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE);
-			}
-
-			private OzgFile buildOzgFile() {
-				return uploadStreamObserver.buildOzgFile(GrpcUploadBinaryFileRequestTestFactory.create());
-			}
-		}
-	}
-}
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java
index efbc02f46e98b0c5a4c5135fca1636c2326232de..0cb77e1b61fef08713d95aace0e0fd86fce88a6d 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java
@@ -29,7 +29,9 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 public class UploadedFilesReferenceTestFactory {
 
 	public static final String VORGANG_ID = VorgangTestFactory.ID;
-	public static final String NAME = "files";
+	public static final String NAME = OzgCloudFileTestFactory.FIELD_NAME;
+	public static final String FILE_NAME = OzgCloudFileTestFactory.NAME;
+	public static final String CLIENT = CallContextTestFactory.CLIENT;
 
 	public static UploadedFilesReference create() {
 		return createBuilder().build();
@@ -38,7 +40,12 @@ public class UploadedFilesReferenceTestFactory {
 	public static UploadedFilesReference.UploadedFilesReferenceBuilder createBuilder() {
 		return UploadedFilesReference.builder()
 				.vorgangId(VORGANG_ID)
-				.client(CallContextTestFactory.CLIENT)
+				.client(CLIENT)
+				.fileName(FILE_NAME)
 				.name(NAME);
 	}
+
+	public static String getGridFsFileName() {
+		return "%s/%s/%s/%s".formatted(VORGANG_ID, CallContextTestFactory.CLIENT, NAME, OzgCloudFileTestFactory.NAME);
+	}
 }
\ No newline at end of file
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java
index 9067f0456d2e2a647fb8073fde4a21504e687bd6..58e4b93245d3bfacb920d8e060c7ecef87ef53f3 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java
@@ -72,7 +72,7 @@ public class IncomingFileTestFactory {
 		return new ByteArrayInputStream(getFilledChunk(fileSize));
 	}
 
-	static byte[] getFilledChunk(int size) {
+	public static byte[] getFilledChunk(int size) {
 		Random r = new Random();
 		byte[] chunk = new byte[size];
 		for (int i = 0; i < chunk.length; i++) {
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java
index 6277d91558b5bd74aa921eebd83394f06802152b..568fcce86d175b22887017c8253bcb6b07f9ae43 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java
@@ -28,11 +28,8 @@ import static org.awaitility.Awaitility.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
-import org.bson.types.ObjectId;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -43,7 +40,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.gridfs.GridFsTemplate;
 import org.springframework.security.test.context.support.WithMockUser;
 
 import de.ozgcloud.command.Command;
@@ -56,9 +52,10 @@ import de.ozgcloud.nachrichten.email.MailService;
 import de.ozgcloud.vorgang.command.GrpcCommandService;
 import de.ozgcloud.vorgang.command.GrpcCreateCommandRequestTestFactory;
 import de.ozgcloud.vorgang.command.PersistedCommand;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory;
+import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil;
 import de.ozgcloud.vorgang.files.FileId;
 import de.ozgcloud.vorgang.files.GridFsTestFactory;
-import de.ozgcloud.vorgang.files.OzgFileTestFactory;
 import de.ozgcloud.vorgang.grpc.command.GrpcCommandResponse;
 import de.ozgcloud.vorgang.grpc.command.GrpcCreateCommandRequest;
 import de.ozgcloud.vorgang.grpc.command.GrpcRedirectRequest;
@@ -85,7 +82,7 @@ class ForwardVorgangITCase {
 	@Autowired
 	private MongoOperations mongoOperations;
 	@Autowired
-	private GridFsTemplate gridFsTemplate;
+	private OzgCloudFileTestUtil ozgCloudFileTestUtil;
 
 	@Mock
 	private StreamObserver<GrpcCommandResponse> responseObserver;
@@ -93,7 +90,7 @@ class ForwardVorgangITCase {
 	private ArgumentCaptor<GrpcCommandResponse> responseCapture;
 
 	@BeforeEach
-	void init() throws InterruptedException, ExecutionException, TimeoutException {
+	void init() {
 		cleanDatabase();
 		prepareVorgang();
 	}
@@ -104,7 +101,7 @@ class ForwardVorgangITCase {
 		mongoOperations.dropCollection(GridFsTestFactory.COLLECTION_NAME);
 	}
 
-	private void prepareVorgang() throws InterruptedException, ExecutionException, TimeoutException {
+	private void prepareVorgang() {
 		var attachment = IncomingFileGroupTestFactory.createBuilder().clearFiles().file(
 				IncomingFileTestFactory.createBuilder().id(storeNewFile()).build()).build();
 		var representation = IncomingFileTestFactory.createBuilder().id(storeNewFile()).build();
@@ -120,9 +117,8 @@ class ForwardVorgangITCase {
 	}
 
 	private FileId storeNewFile() {
-		var fileIdAttachmentId = new ObjectId();
-		gridFsTemplate.store(GridFsTestFactory.createUploadBuilder(OzgFileTestFactory.CONTENT).id(fileIdAttachmentId).build());
-		return FileId.from(fileIdAttachmentId.toHexString());
+		var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT);
+		return FileId.from(ozgCloudFile.getId());
 	}
 
 	@Nested