diff --git a/common/src/main/java/de/ozgcloud/eingang/common/zip/ReadZipException.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ReadZipException.java
similarity index 82%
rename from common/src/main/java/de/ozgcloud/eingang/common/zip/ReadZipException.java
rename to semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ReadZipException.java
index 2a50bc6bc844a3d0a3f2aec157d1e93f1004f338..c3fcc85a910df48282884211d68617f4095248df 100644
--- a/common/src/main/java/de/ozgcloud/eingang/common/zip/ReadZipException.java
+++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ReadZipException.java
@@ -1,4 +1,4 @@
-package de.ozgcloud.eingang.common.zip;
+package de.ozgcloud.eingang.semantik.common;
 
 public class ReadZipException extends RuntimeException {
 
diff --git a/common/src/main/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReader.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReader.java
similarity index 99%
rename from common/src/main/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReader.java
rename to semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReader.java
index 8f060cb8fa68e7045e788920dc9d5e6364fed2c1..02b4e95012b0343aa44ebd79f6f0305135ad9234 100644
--- a/common/src/main/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReader.java
+++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReader.java
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-package de.ozgcloud.eingang.common.zip;
+package de.ozgcloud.eingang.semantik.common;
 
 import java.io.File;
 import java.io.FileInputStream;
diff --git a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java
index d63b1e4f5a502a5efce7f280b107422e0ee83a64..6de90deb9ebc890fba588b4f4ea60c983804c300 100644
--- a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java
+++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java
@@ -33,7 +33,7 @@ import org.springframework.stereotype.Component;
 import de.ozgcloud.eingang.common.formdata.FormData;
 import de.ozgcloud.eingang.common.formdata.IncomingFile;
 import de.ozgcloud.eingang.common.formdata.IncomingFileGroup;
-import de.ozgcloud.eingang.common.zip.ZipAttachmentReader;
+import de.ozgcloud.eingang.semantik.common.ZipAttachmentReader;
 import de.ozgcloud.eingang.semantik.enginebased.FilesMapperHelper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
@@ -110,4 +110,4 @@ class FormSolutionsFilesMapper implements FormSolutionsEngineBasedMapper {
 			return ZipAttachmentReader.from(zipFile.getFile(), zipFile.getName());
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/common/src/test/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReaderTest.java b/semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReaderTest.java
similarity index 99%
rename from common/src/test/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReaderTest.java
rename to semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReaderTest.java
index 8cd2ad76c5b602b4b4e9096211afca5621c8569b..3f36987c5cfe0ff4b4bb6777c7d4a595c7c43dc4 100644
--- a/common/src/test/java/de/ozgcloud/eingang/common/zip/ZipAttachmentReaderTest.java
+++ b/semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/common/ZipAttachmentReaderTest.java
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-package de.ozgcloud.eingang.common.zip;
+package de.ozgcloud.eingang.semantik.common;
 
 import static org.assertj.core.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -317,4 +317,4 @@ class ZipAttachmentReaderTest {
 	private static String getTmpDirectoryPath() {
 		return TMP_DIRECTORY_PATH.endsWith("/") ? TMP_DIRECTORY_PATH.substring(0, TMP_DIRECTORY_PATH.length() - 1) : TMP_DIRECTORY_PATH;
 	}
-}
\ No newline at end of file
+}
diff --git a/common/src/test/resources/zip-file-0.txt b/semantik-adapter/src/test/resources/zip-file-0.txt
similarity index 100%
rename from common/src/test/resources/zip-file-0.txt
rename to semantik-adapter/src/test/resources/zip-file-0.txt
diff --git a/common/src/test/resources/zip-file-1.txt b/semantik-adapter/src/test/resources/zip-file-1.txt
similarity index 100%
rename from common/src/test/resources/zip-file-1.txt
rename to semantik-adapter/src/test/resources/zip-file-1.txt
diff --git a/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapper.java b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapper.java
index e851176d883d602d13ddf2c04df730b2baa31d70..34a807f7001122a0561f319e767c06b08904f25b 100644
--- a/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapper.java
+++ b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapper.java
@@ -23,20 +23,26 @@
 
 package de.ozgcloud.eingang.xta;
 
-import de.ozgcloud.eingang.common.formdata.IncomingFile;
-import de.ozgcloud.eingang.common.zip.ZipAttachmentReader;
-import lombok.extern.log4j.Log4j2;
-import org.springframework.stereotype.Component;
-
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.eingang.common.formdata.IncomingFile;
+import de.ozgcloud.eingang.xta.zip.ZipFileExtractor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
 @Log4j2
 @Component
+@RequiredArgsConstructor
 class XtaIncomingFilesMapper {
+
+	private final ZipFileExtractor zipFileExtractor;
+
 	public static final String ZIP_CONTENT_TYPE = "application/zip";
 	static final Predicate<IncomingFile> IS_ZIP_FILE = contentType -> ZIP_CONTENT_TYPE.equals(contentType.getContentType());
 
@@ -65,14 +71,13 @@ class XtaIncomingFilesMapper {
 	Stream<IncomingFile> extractZip(IncomingFile incomingFile) {
 		if (IS_ZIP_FILE.test(incomingFile)) {
 			try {
-				List<IncomingFile> extractedZips = ZipAttachmentReader.from(incomingFile.getContentStream(), incomingFile.getName()).readContent();
+				List<IncomingFile> extractedZips = zipFileExtractor.extractIncomingFilesSafely(incomingFile);
 				return extractedZips.stream();
 			} catch (RuntimeException e) {
 				LOG.error("Cannot read source ZIP. Not extracting file", e);
 				return Stream.of(incomingFile);
 			}
-		}
-		else {
+		} else {
 			return Stream.of(incomingFile);
 		}
 	}
diff --git a/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ReadableZipEntry.java b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ReadableZipEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..f82f0090a248495ff4f46194b3e2344f807f5294
--- /dev/null
+++ b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ReadableZipEntry.java
@@ -0,0 +1,23 @@
+package de.ozgcloud.eingang.xta.zip;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import lombok.Builder;
+
+@Builder
+record ReadableZipEntry(ZipEntry zipEntry, ZipFile parentZip) {
+	public InputStream getInputStream() throws IOException {
+		return parentZip.getInputStream(zipEntry);
+	}
+
+	public Long getSize() {
+		return zipEntry.getSize();
+	}
+
+	public String getName() {
+		return zipEntry.getName();
+	}
+}
diff --git a/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractor.java b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..255f7eac2b3562f77724382c1216529b2c06ff0d
--- /dev/null
+++ b/xta-adapter/src/main/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractor.java
@@ -0,0 +1,100 @@
+package de.ozgcloud.eingang.xta.zip;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.springframework.stereotype.Component;
+import org.springframework.util.MimeTypeUtils;
+
+import de.ozgcloud.common.binaryfile.TempFileUtils;
+import de.ozgcloud.eingang.common.errorhandling.TechnicalException;
+import de.ozgcloud.eingang.common.formdata.IncomingFile;
+
+// TODO Resolve code duplication with ZipAttachmentReader in semantik-adapter common.zip
+// Note: In contrast to the ZipAttachmentReader, here, the zip file is not included in list of extracted files
+// Further, there is no ZIP_MAX_THRESHOLD detection.
+@Component
+public class ZipFileExtractor {
+
+	static final int ZIP_MAX_TOTAL_SIZE = 500 * 1024 * 1024;
+	static final int ZIP_MAX_ENTRIES = 100;
+
+	public List<IncomingFile> extractIncomingFilesSafely(IncomingFile zipIncomingFile) {
+		var zipFile = zipIncomingFile.getFile();
+		verifySizeLimit(zipFile);
+		return extractIncomingFiles(zipFile);
+	}
+
+	void verifySizeLimit(File zipFile) {
+		var totalSize = sumUncompressedEntrySizes(zipFile);
+		if (totalSize > ZIP_MAX_TOTAL_SIZE) {
+			throw new TechnicalException("Expect uncompressed size %s to be smaller than %d!".formatted(totalSize, ZIP_MAX_TOTAL_SIZE));
+		}
+	}
+
+	Long sumUncompressedEntrySizes(File zipFile) {
+		return mapZipEntries(zipFile, ReadableZipEntry::getSize)
+				.stream()
+				.mapToLong(Long::longValue)
+				.sum();
+	}
+
+	List<IncomingFile> extractIncomingFiles(File zipFile) {
+		return mapZipEntries(zipFile, this::mapReadableEntryToIncomingFile);
+	}
+
+	private IncomingFile mapReadableEntryToIncomingFile(ReadableZipEntry entry) {
+		File file;
+		try (var inputStream = entry.getInputStream()) {
+			file = TempFileUtils.writeTmpFile(inputStream);
+		} catch (IOException e) {
+			throw new TechnicalException("Failed reading zip file element %s!".formatted(entry.getName()), e);
+		}
+		return createIncomingFile(file, entry.zipEntry());
+	}
+
+	<T> List<T> mapZipEntries(File zipFile, Function<ReadableZipEntry, T> mappingFunction) {
+		try (ZipFile zip = new ZipFile(zipFile)) {
+			var index = new AtomicInteger();
+			var mappedElements = new ArrayList<T>();
+			zip.stream().forEach(element -> {
+				if (index.getAndIncrement() >= ZIP_MAX_ENTRIES) {
+					throw new TechnicalException("Expect zip files to have at most %d entries!".formatted(ZIP_MAX_ENTRIES));
+				}
+				mappedElements.add(
+						mappingFunction.apply(
+								ReadableZipEntry.builder()
+										.parentZip(zip)
+										.zipEntry(element)
+										.build()
+						)
+				);
+			});
+			return mappedElements;
+		} catch (IOException e) {
+			throw new TechnicalException("Failed reading zip file!", e);
+		}
+	}
+
+	IncomingFile createIncomingFile(File file, ZipEntry zipEntry) {
+		return IncomingFile.builder()
+				.name(zipEntry.getName())
+				.size(zipEntry.getSize())
+				.contentType(getContentType(zipEntry.getName()))
+				.file(file)
+				.build();
+	}
+
+	String getContentType(String name) {
+		Objects.requireNonNull(name);
+		return Objects.requireNonNullElse(URLConnection.guessContentTypeFromName(name), MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
+	}
+}
diff --git a/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapperTest.java b/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapperTest.java
index da3cdcceeefb8b0f298fb7166bd200fc28b335bc..0dfa769d688d5bab27fce58f4673282a7e86caac 100644
--- a/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapperTest.java
+++ b/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/XtaIncomingFilesMapperTest.java
@@ -24,27 +24,28 @@
 package de.ozgcloud.eingang.xta;
 
 import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
-import de.ozgcloud.eingang.common.formdata.IncomingFile;
-import de.ozgcloud.eingang.common.formdata.IncomingFileTestFactory;
-import de.ozgcloud.eingang.common.zip.ZipAttachmentReader;
-import lombok.SneakyThrows;
+import java.util.List;
+import java.util.stream.Stream;
+
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
 import org.mockito.Spy;
 
-import java.io.InputStream;
-import java.util.List;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
+import de.ozgcloud.eingang.common.formdata.IncomingFile;
+import de.ozgcloud.eingang.common.formdata.IncomingFileTestFactory;
+import de.ozgcloud.eingang.xta.zip.ZipFileExtractor;
 
 class XtaIncomingFilesMapperTest {
 	@Spy
-	private XtaIncomingFilesMapper mapper = new XtaIncomingFilesMapper();
+	@InjectMocks
+	private XtaIncomingFilesMapper mapper;
+
+	@Mock
+	private ZipFileExtractor extractor;
 
 	private static final String ZIP_CONTENT_TYPE = "application/zip";
 
@@ -106,23 +107,29 @@ class XtaIncomingFilesMapperTest {
 	@Nested
 	class TestExtractZip {
 
+		@Mock
+		IncomingFile outFile1;
+
+		@Mock
+		IncomingFile outFile2;
+
+
 		@Test
 		void shouldExtractZipFiles() {
-			try (var zipAttachment = Mockito.mockStatic(ZipAttachmentReader.class)) {
-				var zipAttachmentReaderMock = initZipRepresentationMocks(zipAttachment);
+			var expectedExtractedFiles = List.of(outFile1, outFile2);
+			var zipFile = createTestIncomingFile();
+			when(extractor.extractIncomingFilesSafely(zipFile)).thenReturn(expectedExtractedFiles);
 
-				var extractedFiles = mapper.extractZip(createTestIncomingFile()).toList();
+			var extractedFiles = mapper.extractZip(zipFile).toList();
 
-				verify(zipAttachmentReaderMock).readContent();
-				assertThat(extractedFiles).hasSize(2);
-			}
+			assertThat(extractedFiles).isEqualTo(expectedExtractedFiles);
 		}
 
 		IncomingFile createTestIncomingFile() {
 			return IncomingFileTestFactory.createBuilder()
-							.name("attachments.zip")
-							.contentType(ZIP_CONTENT_TYPE)
-							.build();
+					.name("attachments.zip")
+					.contentType(ZIP_CONTENT_TYPE)
+					.build();
 		}
 
 		@Test
@@ -141,12 +148,4 @@ class XtaIncomingFilesMapperTest {
 		assertThat(XtaIncomingFilesMapper.IS_ZIP_FILE.test(IncomingFileTestFactory.createBuilder().contentType(ZIP_CONTENT_TYPE).build()))
 				.isTrue();
 	}
-
-	@SneakyThrows
-	private static ZipAttachmentReader initZipRepresentationMocks(MockedStatic<ZipAttachmentReader> zipAttachmentMock) {
-		var contentEntries = IntStream.range(0, 2).boxed().map(i -> IncomingFileTestFactory.createBuilder().name(i.toString()).build()).toList();
-		ZipAttachmentReader mock = when(mock(ZipAttachmentReader.class).readContent()).thenReturn(contentEntries).getMock();
-		zipAttachmentMock.when(() -> ZipAttachmentReader.from(any(InputStream.class), any())).thenReturn(mock);
-		return mock;
-	}
 }
diff --git a/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractorTest.java b/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a4e0c3d8521576f48e1bcc2b8d51645eeba68f2
--- /dev/null
+++ b/xta-adapter/src/test/java/de/ozgcloud/eingang/xta/zip/ZipFileExtractorTest.java
@@ -0,0 +1,329 @@
+package de.ozgcloud.eingang.xta.zip;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.util.MimeTypeUtils;
+
+import de.ozgcloud.common.binaryfile.TempFileUtils;
+import de.ozgcloud.eingang.common.errorhandling.TechnicalException;
+import de.ozgcloud.eingang.common.formdata.IncomingFile;
+import lombok.Builder;
+import lombok.Getter;
+
+class ZipFileExtractorTest {
+
+	@Spy
+	@InjectMocks
+	private ZipFileExtractor extractor;
+
+	@DisplayName("extract incoming files safely")
+	@Nested
+	class TestExtractIncomingFilesWithSizeLimit {
+		@Mock
+		IncomingFile incomingZipFile;
+
+		@Mock
+		File zipFile;
+
+		@Mock
+		IncomingFile outIncomingFile;
+
+		List<IncomingFile> outIncomingFiles;
+
+		@BeforeEach
+		void mock() {
+			outIncomingFiles = List.of(outIncomingFile);
+
+			when(incomingZipFile.getFile()).thenReturn(zipFile);
+			doNothing().when(extractor).verifySizeLimit(zipFile);
+			doReturn(outIncomingFiles).when(extractor).extractIncomingFiles(zipFile);
+		}
+
+		@DisplayName("should call verify size limit")
+		@Test
+		void shouldCallVerifySizeLimit() {
+			extractor.extractIncomingFilesSafely(incomingZipFile);
+
+			verify(extractor).verifySizeLimit(zipFile);
+		}
+
+		@DisplayName("should return")
+		@Test
+		void shouldReturn() {
+			var output = extractor.extractIncomingFilesSafely(incomingZipFile);
+
+			assertThat(output).isEqualTo(outIncomingFiles);
+		}
+	}
+
+	@DisplayName("verify size limit")
+	@Nested
+	class TestVerifySizeLimit {
+		@Mock
+		File zipFile;
+
+		@DisplayName("should return")
+		@Test
+		void shouldReturn() {
+			doReturn((long) ZipFileExtractor.ZIP_MAX_TOTAL_SIZE).when(extractor).sumUncompressedEntrySizes(zipFile);
+
+			extractor.verifySizeLimit(zipFile);
+		}
+
+		@DisplayName("should throw if limit exceeded")
+		@Test
+		void shouldThrowIfLimitExceeded() {
+			doReturn((long) ZipFileExtractor.ZIP_MAX_TOTAL_SIZE + 1).when(extractor).sumUncompressedEntrySizes(zipFile);
+
+			assertThatThrownBy(() -> extractor.verifySizeLimit(zipFile))
+					.isInstanceOf(TechnicalException.class);
+		}
+	}
+
+	@DisplayName("extract incoming files")
+	@Nested
+	class TestExtractIncomingFiles {
+
+		private File zipFile;
+
+		@BeforeEach
+		void mock() {
+			zipFile = createTempZipFile(fromMap(Map.of(
+					"file1.pdf", "file content1",
+					"file2.xml", "<root></root>",
+					"file3.png", ""
+			)));
+		}
+
+		@DisplayName("should contain content")
+		@Test
+		void shouldContainContent() {
+			var extractedFiles = extractor.extractIncomingFiles(zipFile);
+
+			var contents = extractedFiles.stream().map(f -> {
+				try {
+					return Files.readString(f.getFile().toPath());
+				} catch (IOException e) {
+					throw new RuntimeException(e);
+				}
+			}).toList();
+			assertThat(contents).containsExactlyInAnyOrder("file content1", "<root></root>", "");
+		}
+
+		@DisplayName("should have names")
+		@Test
+		void shouldHaveNames() {
+			var extractedFiles = extractor.extractIncomingFiles(zipFile);
+
+			var names = extractedFiles.stream().map(IncomingFile::getName).toList();
+			assertThat(names).containsExactlyInAnyOrder("file1.pdf", "file2.xml", "file3.png");
+		}
+
+		@DisplayName("should have content types")
+		@Test
+		void shouldHaveContentTypes() {
+			var extractedFiles = extractor.extractIncomingFiles(zipFile);
+
+			var names = extractedFiles.stream().map(IncomingFile::getContentType).toList();
+			assertThat(names).containsExactlyInAnyOrder("application/pdf", "application/xml", "image/png");
+		}
+
+		private List<TestZipEntry> fromMap(Map<String, String> entries) {
+			return entries.entrySet().stream().map(kv -> TestZipEntry.builder()
+							.name(kv.getKey())
+							.content(kv.getValue())
+							.build())
+					.toList();
+		}
+	}
+
+	@DisplayName("create incoming file")
+	@Nested
+	class TestCreateIncomingFile {
+		@Mock
+		File file;
+
+		@Mock
+		ZipEntry zipEntry;
+
+		private static final String NAME = "filename.name";
+		private static final Long SIZE = 5L;
+		private static final String CONTENT_TYPE = "some/content";
+
+		@BeforeEach
+		void mock() {
+			when(zipEntry.getName()).thenReturn(NAME);
+			when(zipEntry.getSize()).thenReturn(SIZE);
+			doReturn(CONTENT_TYPE).when(extractor).getContentType(NAME);
+		}
+
+		@DisplayName("should have name")
+		@Test
+		void shouldHaveName() {
+			var incomingFile = create();
+
+			assertThat(incomingFile.getName()).isEqualTo(NAME);
+		}
+
+		@DisplayName("should have size")
+		@Test
+		void shouldHaveSize() {
+			var incomingFile = create();
+
+			assertThat(incomingFile.getSize()).isEqualTo(SIZE);
+		}
+
+		@DisplayName("should have content type")
+		@Test
+		void shouldHaveContentType() {
+			var incomingFile = create();
+
+			assertThat(incomingFile.getContentType()).isEqualTo(CONTENT_TYPE);
+		}
+
+		@DisplayName("should have file")
+		@Test
+		void shouldHaveFile() {
+			var incomingFile = create();
+
+			assertThat(incomingFile.getFile()).isEqualTo(file);
+		}
+
+		private IncomingFile create() {
+			return extractor.createIncomingFile(file, zipEntry);
+		}
+	}
+
+
+	@DisplayName("sum uncompressed entry size")
+	@Nested
+	class TestSumUncompressedEntrySize {
+		@DisplayName("should return size")
+		@Test
+		void shouldReturnSize() {
+			var sizes = IntStream.range(100, 110).boxed().toList();
+			var expectedSum = sizes.stream().mapToLong(Integer::longValue).sum();
+			var zipFile = createTempZipFile(sizes.stream()
+					.map(size -> TestZipEntry.builder()
+							.name("somefilewithsize%d".formatted(size))
+							.content("A".repeat(size))
+							.build()
+					).toList());
+
+			var sum = extractor.sumUncompressedEntrySizes(zipFile);
+
+			assertThat(sum).isEqualTo(expectedSum);
+		}
+	}
+
+
+	@Nested
+	class TestContentType {
+
+		@Test
+		void shouldReturnDefaultWhenNullString() {
+			assertThrows(NullPointerException.class, () -> extractor.getContentType(null));
+		}
+
+		@Test
+		void shouldReturnDefaultWhenEmptyString() {
+			var contentType = extractor.getContentType(StringUtils.EMPTY);
+
+			assertThat(contentType).isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
+		}
+
+		@Test
+		void shouldReturnDefaultWhenSpaceString() {
+			var contentType = extractor.getContentType(StringUtils.SPACE);
+
+			assertThat(contentType).isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
+		}
+
+		@Test
+		void shouldGetContentType() {
+			var fileNames = List.of("1.xml", "2.txt");
+
+			var contentTypes = fileNames.stream().map(extractor::getContentType).toList();
+
+			assertThat(contentTypes).containsExactlyInAnyOrder(MimeTypeUtils.APPLICATION_XML_VALUE, MimeTypeUtils.TEXT_PLAIN_VALUE);
+		}
+	}
+
+
+	@DisplayName("map zip entries")
+	@Nested
+	class TestMapZipEntries {
+
+		@DisplayName("should throw if max entries exceeded")
+		@Test
+		void shouldThrowIfMaxEntriesExceeded() {
+			var zipWithTooManyEntries = createTempZipFile(IntStream.range(0, ZipFileExtractor.ZIP_MAX_ENTRIES + 1)
+					.mapToObj(i -> TestZipEntry.builder()
+							.name("test%d.txt".formatted(i))
+							.content("test file %d".formatted(i))
+							.build()
+					).toList());
+
+			assertThatThrownBy(() -> extractor.mapZipEntries(zipWithTooManyEntries, entry -> null))
+					.isInstanceOf(TechnicalException.class);
+		}
+
+		@DisplayName("should map with mapping function")
+		@Test
+		void shouldMapWithMappingFunction() {
+			var expectedNumberList = IntStream.range(0, ZipFileExtractor.ZIP_MAX_ENTRIES).boxed().toList();
+			var zipFile = createTempZipFile(expectedNumberList.stream()
+					.map(i -> TestZipEntry.builder()
+							.name("%d".formatted(i))
+							.content("some content")
+							.build()
+					).toList());
+
+			var numberList = extractor.mapZipEntries(zipFile, entry -> Integer.parseInt(entry.getName()));
+
+			assertThat(numberList).isEqualTo(expectedNumberList);
+		}
+	}
+
+	@Builder
+	@Getter
+	static class TestZipEntry {
+		private String name;
+		private String content;
+	}
+
+	private File createTempZipFile(List<TestZipEntry> testZipEntries) {
+		var file = TempFileUtils.createTmpFile().toFile();
+		try (var zipOutputStream = new ZipOutputStream(new FileOutputStream(file))) {
+			for (TestZipEntry entry : testZipEntries) {
+				zipOutputStream.putNextEntry(new ZipEntry(entry.getName()));
+				zipOutputStream.write(entry.getContent().getBytes(StandardCharsets.UTF_8));
+				zipOutputStream.closeEntry();
+			}
+			return file;
+		} catch (IOException e) {
+			throw new RuntimeException("Failed to create temporary zip file", e);
+		}
+	}
+}