Repository: vaadin/flow
Pull request #21400: feat: add factory method for UploadHandler
Author: login: caalador
Labels: name: +0.1.0
name: prerelease version for Vaadin 24.8.0
name: Released with Vaadin 24.8.0.alpha7
Created: 2025-05-08T10:21:55Z | Merged: 2025-05-09T07:46:15Z

Add factory methods for
in-memory, File and TemporaryFile
for easier handling of upload.

fixes #21235



Linked issue #21235: UploadHandler Factory methods
### Describe your motivation

A part of UploadHandler that can be done with a split efforts.

### Describe the solution you'd like

Add factory methods that create a UploadHandler callbacks for commons use cases, e.g. to upload to a temp dir.

Linked issue comments: user: login: vaadin-bot
created_at: 2025-05-14T17:57:00Z
body: This ticket/PR has been released with Vaadin 24.8.0.alpha7 and is also targeting the upcoming stable 24.8.0 version.
author_association: COLLABORATOR

Diff:
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
new file mode 100644
index 00000000000..e4cda59c02e
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
+        extends TransferProgressAwareHandler<UploadEvent, R>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, File> successHandler;
+    private final FileFactory fileFactory;
+
+    /**
+     * Creates an upload handler that stores the incoming stream into a
+     * {@link File} that is generated by the given {@link FileFactory}.
+     *
+     * @param successHandler
+     *            consumer to be called for successful upload
+     * @param fileFactory
+     *            factory that generates the {@link File} to store data
+     */
+    public AbstractFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        this.successHandler = successHandler;
+        this.fileFactory = fileFactory;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        File file;
+        try {
+            file = fileFactory.createFile(event.getFileName());
+            try (InputStream inputStream = event.getInputStream();
+                    FileOutputStream outputStream = new FileOutputStream(
+                            file)) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+            }
+        } catch (IOException e) {
+            notifyError(event, e);
+            throw new UncheckedIOException(e);
+        }
+        successHandler.accept(new UploadMetadata(event.getFileName(),
+                event.getContentType(), event.getFileSize()), file);
+    }
+
+    @Override
+    protected TransferContext getTransferContext(UploadEvent transferEvent) {
+        return new TransferContext(transferEvent.getRequest(),
+                transferEvent.getResponse(), transferEvent.getSession(),
+                transferEvent.getFileName(),
+                transferEvent.getOwningComponent().getElement(),
+                transferEvent.getFileSize());
+    }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/FileFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/FileFactory.java
new file mode 100644
index 00000000000..c073ae9572a
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/FileFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * File factory interface for generating file to store the uploaded data into.
+ *
+ * @since 24.8
+ */
+@FunctionalInterface
+public interface FileFactory extends Serializable {
+
+    /**
+     * Create a new file for given file name.
+     *
+     * @param fileName
+     *            file name to create file for
+     * @return {@link File} that should be used
+     */
+    File createFile(String fileName) throws IOException;
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/FileUploadHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/FileUploadHandler.java
new file mode 100644
index 00000000000..a5c6ccc6f2c
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/FileUploadHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+
+/**
+ * Upload handler that stores the data into a file generated by the given
+ * {@link FileFactory}.
+ *
+ * @since 24.8
+ */
+public class FileUploadHandler
+        extends AbstractFileUploadHandler<FileUploadHandler> {
+
+    /**
+     * Constructor for file upload handler.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @param fileFactory
+     *            factory for generating file to write to
+     */
+    public FileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        super(successHandler, fileFactory);
+    }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/InMemoryUploadHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/InMemoryUploadHandler.java
new file mode 100644
index 00000000000..442209d2833
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/InMemoryUploadHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Upload handler for storing the upload in-memory. Data is returned as a
+ * {@code byte[]} to the given successHandler.
+ *
+ * @since 24.8
+ */
+public class InMemoryUploadHandler
+        extends TransferProgressAwareHandler<UploadEvent, InMemoryUploadHandler>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, byte[]> successHandler;
+
+    public InMemoryUploadHandler(
+            SerializableBiConsumer<UploadMetadata, byte[]> successHandler) {
+        this.successHandler = successHandler;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        byte[] data;
+        try {
+            try (InputStream inputStream = event.getInputStream();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+                data = outputStream.toByteArray();
+            }
+        } catch (IOException e) {
+            notifyError(event, e);
+            throw new UncheckedIOException(e);
+        }
+        successHandler.accept(new UploadMetadata(event.getFileName(),
+                event.getContentType(), event.getFileSize()), data);
+    }
+
+    @Override
+    protected TransferContext getTransferContext(UploadEvent transferEvent) {
+        return new TransferContext(transferEvent.getRequest(),
+                transferEvent.getResponse(), transferEvent.getSession(),
+                transferEvent.getFileName(),
+                transferEvent.getOwningComponent().getElement(),
+                transferEvent.getFileSize());
+    }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileFactory.java
new file mode 100644
index 00000000000..7b62f423da2
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * File factory to generate a temporary file for given file name
+ *
+ * @since 24.8
+ */
+public class TemporaryFileFactory implements FileFactory {
+
+    /**
+     * Create a new temporary file for filename. Adds the suffix {@code .tmp}
+     */
+    @Override
+    public File createFile(String fileName) throws IOException {
+
+        Path tempDirPath;
+        try {
+            tempDirPath = Files.createTempDirectory("temp_dir");
+        } catch (IOException e) {
+            throw new IOException("Failed to create temp directory", e);
+        }
+
+        return Files.createTempFile(tempDirPath, fileName, ".tmp").toFile();
+    }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileUploadHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileUploadHandler.java
new file mode 100644
index 00000000000..9332c87c69b
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/TemporaryFileUploadHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+
+/**
+ * Upload handler that stores the data into a temporary file. Stored temporary
+ * file is returned in the successHandler for further use.
+ *
+ * @since 24.8
+ */
+public class TemporaryFileUploadHandler
+        extends AbstractFileUploadHandler<TemporaryFileUploadHandler> {
+
+    public TemporaryFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler) {
+        super(successHandler, new TemporaryFileFactory());
+    }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadHandler.java
index 7d2987d5fc7..054b803b1c0 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadHandler.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadHandler.java
@@ -16,12 +16,12 @@
 
 package com.vaadin.flow.server.streams;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
@@ -36,11 +36,10 @@
 import org.slf4j.LoggerFactory;
 
 import com.vaadin.flow.dom.Element;
-import com.vaadin.flow.internal.StateNode;
+import com.vaadin.flow.function.SerializableBiConsumer;
 import com.vaadin.flow.server.ElementRequestHandler;
-import com.vaadin.flow.server.ErrorEvent;
 import com.vaadin.flow.server.HttpStatusCode;
-import com.vaadin.flow.server.UploadException;
+import com.vaadin.flow.server.TransferProgressListener;
 import com.vaadin.flow.server.VaadinRequest;
 import com.vaadin.flow.server.VaadinResponse;
 import com.vaadin.flow.server.VaadinSession;
@@ -228,9 +227,106 @@ default long getFileSizeMax() {
      * <p>
      * Default is 10000.
      *
-     * @return the maxiumum numner of files allowed, -1 means no limit
+     * @return the maximum number of files allowed, -1 means no limit
      */
     default long getFileCountMax() {
         return DEFAULT_FILE_COUNT_MAX;
     }
+
+    /**
+     * Generate a upload handler for storing upload stream into a file.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @param fileFactory
+     *            factory for generating file to write to
+     * @return file upload handler
+     */
+    static FileUploadHandler toFile(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        return new FileUploadHandler(successHandler, fileFactory);
+    }
+
+    /**
+     * Generate a upload handler for storing upload stream into a file.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @param fileFactory
+     *            factory for generating file to write to
+     * @param listener
+     *            listener for transfer progress events
+     * @return file upload handler instance with progress listener
+     */
+    static FileUploadHandler toFile(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory, TransferProgressListener listener) {
+        FileUploadHandler fileUploadHandler = new FileUploadHandler(
+                successHandler, fileFactory);
+        fileUploadHandler.addTransferProgressListener(listener);
+        return fileUploadHandler;
+    }
+
+    /**
+     * Generate upload handler for storing upload stream into a temporary file.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @return temporary file upload handler instance
+     */
+    static TemporaryFileUploadHandler toTempFile(
+            SerializableBiConsumer<UploadMetadata, File> successHandler) {
+        return new TemporaryFileUploadHandler(successHandler);
+    }
+
+    /**
+     * Generate upload handler for storing upload stream into a temporary file.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @param listener
+     *            listener for transfer progress events
+     * @return temporary file upload handler instance with progress listener
+     */
+    static TemporaryFileUploadHandler toTempFile(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            TransferProgressListener listener) {
+        TemporaryFileUploadHandler temporaryFileUploadHandler = new TemporaryFileUploadHandler(
+                successHandler);
+        temporaryFileUploadHandler.addTransferProgressListener(listener);
+        return temporaryFileUploadHandler;
+    }
+
+    /**
+     * Generate upload handler for storing download into in-memory
+     * {@code byte[]}.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @return in-memory upoload handler
+     */
+    static InMemoryUploadHandler inMemory(
+            SerializableBiConsumer<UploadMetadata, byte[]> successHandler) {
+        return new InMemoryUploadHandler(successHandler);
+    }
+
+    /**
+     * Generate upload handler for storing download into in-memory
+     * {@code byte[]}.
+     *
+     * @param successHandler
+     *            consumer to be called when upload successfully completes
+     * @param listener
+     *            listener for transfer progress events
+     * @return in-memory upoload handler with progress listener
+     */
+    static InMemoryUploadHandler inMemory(
+            SerializableBiConsumer<UploadMetadata, byte[]> successHandler,
+            TransferProgressListener listener) {
+        InMemoryUploadHandler inMemoryUploadHandler = new InMemoryUploadHandler(
+                successHandler);
+        inMemoryUploadHandler.addTransferProgressListener(listener);
+        return inMemoryUploadHandler;
+    }
 }
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java b/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
new file mode 100644
index 00000000000..2a70d3c4d34
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+/**
+ * Metadata for successful upload.
+ */
+public record UploadMetadata(String fileName, String contentType,
+        long contentLength) {
+}
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/UploadHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/UploadHandlerTest.java
index f239eef297c..e85c41f0fe2 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/communication/UploadHandlerTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/UploadHandlerTest.java
@@ -1,9 +1,11 @@
 package com.vaadin.flow.server.communication;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -25,6 +27,7 @@
 
 import com.vaadin.flow.component.UI;
 import com.vaadin.flow.dom.Element;
+import com.vaadin.flow.function.SerializableBiConsumer;
 import com.vaadin.flow.internal.CurrentInstance;
 import com.vaadin.flow.internal.StateNode;
 import com.vaadin.flow.server.AbstractStreamResource;
@@ -37,8 +40,12 @@
 import com.vaadin.flow.server.VaadinResponse;
 import com.vaadin.flow.server.VaadinService;
 import com.vaadin.flow.server.VaadinServletRequest;
+import com.vaadin.flow.server.streams.FileUploadHandler;
+import com.vaadin.flow.server.streams.InMemoryUploadHandler;
+import com.vaadin.flow.server.streams.TemporaryFileUploadHandler;
 import com.vaadin.flow.server.streams.UploadEvent;
 import com.vaadin.flow.server.streams.UploadHandler;
+import com.vaadin.flow.server.streams.UploadMetadata;
 import com.vaadin.flow.shared.ApplicationConstants;
 import com.vaadin.tests.util.AlwaysLockedVaadinSession;
 import com.vaadin.tests.util.MockUI;
@@ -162,6 +169,110 @@ public void createUploadHandlerToCopyStream_streamMatchesInput()
         Assert.assertEquals("", testBytes.length, amount.get());
     }
 
+    @Test
+    public void createInMemoryUploadHandler_streamMatchesInput()
+            throws IOException {
+        String testString = "Test string for upload";
+
+        final byte[] testBytes = testString.getBytes();
+
+        final byte[] output = new byte[testBytes.length];
+
+        // No lambda as that would require strange things from output
+        InMemoryUploadHandler uploadHandler = UploadHandler
+                .inMemory(new SerializableBiConsumer<UploadMetadata, byte[]>() {
+                    @Override
+                    public void accept(UploadMetadata uploadMetadata,
+                            byte[] bytes) {
+                        Assert.assertEquals(output.length, bytes.length);
+                        System.arraycopy(bytes, 0, output, 0, bytes.length);
+                    }
+                });
+
+        StreamRegistration streamRegistration = streamResourceRegistry
+                .registerResource(uploadHandler);
+        AbstractStreamResource res = streamRegistration.getResource();
+
+        mockRequest(res, testString);
+
+        handler.handleRequest(session, request, response);
+
+        Assert.assertArrayEquals("Output differed from expected", testBytes,
+                output);
+    }
+
+    @Test
+    public void createTempFileUploadHandler_streamMatchesInput()
+            throws IOException {
+        String testString = "Test string for upload";
+
+        final byte[] testBytes = testString.getBytes();
+
+        List<File> outputFiles = new ArrayList<>(1);
+
+        TemporaryFileUploadHandler uploadHandler = UploadHandler
+                .toTempFile((uploadMetadata, file) -> outputFiles.add(file));
+
+        StreamRegistration streamRegistration = streamResourceRegistry
+                .registerResource(uploadHandler);
+        AbstractStreamResource res = streamRegistration.getResource();
+
+        mockRequest(res, testString);
+
+        try {
+            handler.handleRequest(session, request, response);
+
+            Assert.assertEquals("Only one uploaded file expected.", 1,
+                    outputFiles.size());
+            System.out.println(outputFiles.get(0).getPath());
+
+            Assert.assertArrayEquals("Output differed from expected", testBytes,
+                    Files.readAllBytes(outputFiles.get(0).toPath()));
+        } finally {
+            // Cleanup temp file after test
+            for (File file : outputFiles) {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void createFileUploadHandler_streamMatchesInput()
+            throws IOException {
+        String testString = "Test string for upload";
+
+        final byte[] testBytes = testString.getBytes();
+
+        List<File> outputFiles = new ArrayList<>(1);
+
+        FileUploadHandler uploadHandler = UploadHandler.toFile(
+                (uploadMetadata, file) -> outputFiles.add(file),
+                (fileName) -> new File(System.getProperty("java.io.tmpdir"),
+                        fileName));
+
+        StreamRegistration streamRegistration = streamResourceRegistry
+                .registerResource(uploadHandler);
+        AbstractStreamResource res = streamRegistration.getResource();
+
+        mockRequest(res, testString);
+
+        try {
+            handler.handleRequest(session, request, response);
+
+            Assert.assertEquals("Only one uploaded file expected.", 1,
+                    outputFiles.size());
+            System.out.println(outputFiles.get(0).getPath());
+
+            Assert.assertArrayEquals("Output differed from expected", testBytes,
+                    Files.readAllBytes(outputFiles.get(0).toPath()));
+        } finally {
+            // Cleanup temp file after test
+            for (File file : outputFiles) {
+                file.delete();
+            }
+        }
+    }
+
     @Test
     public void mulitpartData_forInputIterator_dataIsGottenCorrectly()
             throws IOException {
diff --git a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
index 46a3c216f81..fed48ef0a0b 100644
--- a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
+++ b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
@@ -223,6 +223,7 @@ protected Stream<String> getExcludedPatterns() {
                 "com\\.vaadin\\.flow\\.server\\.DownloadEvent",
                 "com\\.vaadin\\.flow\\.server\\.communication\\.StreamRequestHandler\\$PathData",
                 "com\\.vaadin\\.flow\\.server\\.streams\\.UploadEvent",
+                "com\\.vaadin\\.flow\\.server\\.streams\\.UploadMetadata",
 
                 "com\\.vaadin\\.flow\\.server\\.DevModeHandler(\\$.*)?",
                 // Frontend tasks classes which are not stored anywhere but used


Reviews:
user: login: mshabarov
body: LGTM.
A few comments, but these are not blocking. Feel free to merge, give comments or push changes.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:46:26Z
user: login: caalador
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:49:09Z
user: login: caalador
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:51:16Z
user: login: mshabarov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:53:25Z
user: login: caalador
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:53:44Z
user: login: caalador
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T07:25:52Z
user: login: caalador
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T07:26:33Z
user: login: mshabarov
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T07:27:13Z

Review comments:
diff_hunk: @@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: mshabarov
body: ```suggestion
public abstract class AbstractUploadHandler<R extends AbstractFileUploadHandler>
```
created_at: 2025-05-09T05:47:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: mshabarov
body: Not sure if the "File" word is better or worse than nothing, but just as a matter of unification I'd remove it.
created_at: 2025-05-09T05:48:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * File factory interface for generating file to store the uploaded data into.
+ *
+ * @since 24.8
+ */
+@FunctionalInterface
+public interface FileFactory extends Serializable {
path: flow-server/src/main/java/com/vaadin/flow/server/streams/FileFactory.java
user: login: mshabarov
body: We should remember to deprecate the same class on upload component side. Just a note.
created_at: 2025-05-09T06:20:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
+        extends TransferProgressAwareHandler<UploadEvent, R>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, File> successHandler;
+    private final FileFactory fileFactory;
+
+    /**
+     * Creates an upload handler that stores the incoming stream into a
+     * {@link File} that is generated by the given {@link FileFactory}.
+     *
+     * @param successHandler
+     *            consumer to be called for successful upload
+     * @param fileFactory
+     *            factory that generates the {@link File} to store data
+     */
+    public AbstractFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        this.successHandler = successHandler;
+        this.fileFactory = fileFactory;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        File file;
+        try {
+            file = fileFactory.createFile(event.getFileName());
+            try (InputStream inputStream = event.getInputStream();
+                    FileOutputStream outputStream = new FileOutputStream(
+                            file)) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: mshabarov
body: This should also call `notifyError()` method to fire the event for `onError`, but I'm going to create another PR with small changes so can add it there, doesn't block this PR.
created_at: 2025-05-09T06:38:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
+        extends TransferProgressAwareHandler<UploadEvent, R>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, File> successHandler;
+    private final FileFactory fileFactory;
+
+    /**
+     * Creates an upload handler that stores the incoming stream into a
+     * {@link File} that is generated by the given {@link FileFactory}.
+     *
+     * @param successHandler
+     *            consumer to be called for successful upload
+     * @param fileFactory
+     *            factory that generates the {@link File} to store data
+     */
+    public AbstractFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        this.successHandler = successHandler;
+        this.fileFactory = fileFactory;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        File file;
+        try {
+            file = fileFactory.createFile(event.getFileName());
+            try (InputStream inputStream = event.getInputStream();
+                    FileOutputStream outputStream = new FileOutputStream(
+                            file)) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: mshabarov
body: Same for other upload handler implementations.
created_at: 2025-05-09T06:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+/**
+ * Metadata for successful upload.
+ */
+public record UploadMetadata(String fileName, String contentType,
+        long fileSize) {
path: flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
user: login: mshabarov
body: How about `contentLength` instead of `fileSize` as it can be byte array data.
created_at: 2025-05-09T06:45:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: caalador
body: But it is actually only for FileUploads that this abstract is valid.

created_at: 2025-05-09T06:49:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+/**
+ * Metadata for successful upload.
+ */
+public record UploadMetadata(String fileName, String contentType,
+        long fileSize) {
path: flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
user: login: caalador
body: Or `uploadSize`?
created_at: 2025-05-09T06:51:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+/**
+ * Metadata for successful upload.
+ */
+public record UploadMetadata(String fileName, String contentType,
+        long fileSize) {
path: flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
user: login: mshabarov
body: `contentLength` is used in the `Upload` API already. I'm considering change `totalBytes` that I added to `contentLength` as well.
created_at: 2025-05-09T06:53:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
+        extends TransferProgressAwareHandler<UploadEvent, R>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, File> successHandler;
+    private final FileFactory fileFactory;
+
+    /**
+     * Creates an upload handler that stores the incoming stream into a
+     * {@link File} that is generated by the given {@link FileFactory}.
+     *
+     * @param successHandler
+     *            consumer to be called for successful upload
+     * @param fileFactory
+     *            factory that generates the {@link File} to store data
+     */
+    public AbstractFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        this.successHandler = successHandler;
+        this.fileFactory = fileFactory;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        File file;
+        try {
+            file = fileFactory.createFile(event.getFileName());
+            try (InputStream inputStream = event.getInputStream();
+                    FileOutputStream outputStream = new FileOutputStream(
+                            file)) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: caalador
body: That should be in the UploadHandler catch(Exception) I think, as these are called multiple times for multipart uploads
created_at: 2025-05-09T06:53:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+import com.vaadin.flow.function.SerializableBiConsumer;
+import com.vaadin.flow.server.TransferProgressListener;
+
+/**
+ * Abstract class for file upload handler.
+ *
+ * @param <R>
+ *            type of file upload handler
+ *
+ * @since 24.8
+ */
+public abstract class AbstractFileUploadHandler<R extends AbstractFileUploadHandler>
+        extends TransferProgressAwareHandler<UploadEvent, R>
+        implements UploadHandler {
+    private final SerializableBiConsumer<UploadMetadata, File> successHandler;
+    private final FileFactory fileFactory;
+
+    /**
+     * Creates an upload handler that stores the incoming stream into a
+     * {@link File} that is generated by the given {@link FileFactory}.
+     *
+     * @param successHandler
+     *            consumer to be called for successful upload
+     * @param fileFactory
+     *            factory that generates the {@link File} to store data
+     */
+    public AbstractFileUploadHandler(
+            SerializableBiConsumer<UploadMetadata, File> successHandler,
+            FileFactory fileFactory) {
+        this.successHandler = successHandler;
+        this.fileFactory = fileFactory;
+    }
+
+    @Override
+    public void handleUploadRequest(UploadEvent event) {
+        File file;
+        try {
+            file = fileFactory.createFile(event.getFileName());
+            try (InputStream inputStream = event.getInputStream();
+                    FileOutputStream outputStream = new FileOutputStream(
+                            file)) {
+                TransferProgressListener.transfer(inputStream, outputStream,
+                        getTransferContext(event), getListeners());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
path: flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java
user: login: caalador
body: Except that of doesn't have the transfer context. Added.
created_at: 2025-05-09T07:25:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.streams;
+
+/**
+ * Metadata for successful upload.
+ */
+public record UploadMetadata(String fileName, String contentType,
+        long fileSize) {
path: flow-server/src/main/java/com/vaadin/flow/server/streams/UploadMetadata.java
user: login: caalador
body: done
created_at: 2025-05-09T07:26:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-08T11:07:18Z
body: ## Test Results
1 226 files  ± 0  1 226 suites  ±0   1h 12m 56s ⏱️ -26s
8 422 tests + 3  8 366 ✅ + 3  56 💤 ±0  0 ❌ ±0 
8 828 runs  +14  8 763 ✅ +14  65 💤 ±0  0 ❌ ±0 

Results for commit f3c52cfb. ± Comparison against base commit cdaa7582.

[test-results]:data:application/gzip;base64,H4sIAJaxHWgC/03MSQ7DIAyF4atErLsAM/cyVeKAhJqhIrCKevdCmmn5Plv/SnwY3EKeDQNQj4YsOaT77nNsU5inIoLrKuWc6oMRAMd8LRmxGlfqsnf4FJOX+DYMRegJLsY57hLztGUNmH2dVa34Sf+okgfcmtu+J3Eex5DKIJ6jBPSdl+icFaxHoylXIDxYjVZQxih2lpLvD4DCgV4SAQAA

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-09T07:34:49Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=vaadin_flow&pullRequest=21400) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [13 New issues](https://sonarcloud.io/project/issues?id=vaadin_flow&pullRequest=21400&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=vaadin_flow&pullRequest=21400&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=vaadin_flow&pullRequest=21400&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=vaadin_flow&pullRequest=21400&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=vaadin_flow&pullRequest=21400&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=vaadin_flow&pullRequest=21400)


author_association: NONE
user: login: vaadin-bot
created_at: 2025-05-14T17:56:58Z
body: This ticket/PR has been released with Vaadin 24.8.0.alpha7 and is also targeting the upcoming stable 24.8.0 version.
author_association: COLLABORATOR