diff --git a/spring-5-webflux-2/pom.xml b/spring-5-webflux-2/pom.xml
index efb99e06d5..5422ed55c4 100644
--- a/spring-5-webflux-2/pom.xml
+++ b/spring-5-webflux-2/pom.xml
@@ -88,6 +88,20 @@
mockwebserver
4.12.0
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ io.r2dbc
+ r2dbc-h2
+ runtime
+
@@ -96,6 +110,14 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 16
+
+
diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java
new file mode 100644
index 0000000000..5c0e265e13
--- /dev/null
+++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java
@@ -0,0 +1,35 @@
+package com.baeldung.webflux.filerecord;
+
+import org.springframework.data.annotation.Id;
+
+import java.util.List;
+
+public class FileRecord {
+ @Id
+ private int id;
+
+ private List filenames;
+
+ public FileRecord(List filenames) {
+ this.filenames = filenames;
+ }
+
+ public FileRecord() {
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public List getFilenames() {
+ return filenames;
+ }
+
+ public void setFilenames(List filenames) {
+ this.filenames = filenames;
+ }
+}
diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java
new file mode 100644
index 0000000000..9c20e10040
--- /dev/null
+++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java
@@ -0,0 +1,28 @@
+package com.baeldung.webflux.filerecord;
+
+import io.r2dbc.spi.ConnectionFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
+import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
+
+@SpringBootApplication
+public class FileRecordApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(FileRecordApplication.class, args);
+ }
+
+ @Bean
+ ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
+
+ ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
+ initializer.setConnectionFactory(connectionFactory);
+ initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
+
+ return initializer;
+ }
+
+}
diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java
new file mode 100644
index 0000000000..943cec9a2d
--- /dev/null
+++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java
@@ -0,0 +1,45 @@
+package com.baeldung.webflux.filerecord;
+
+import org.springframework.http.codec.multipart.FilePart;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.file.Paths;
+
+@RestController
+public class FileRecordController {
+
+ private final FileRecordService fileRecordService;
+
+ public FileRecordController(FileRecordService fileRecordService) {
+ this.fileRecordService = fileRecordService;
+ }
+
+ @PostMapping("/upload-files")
+ public Mono uploadFileWithoutEntity(@RequestPart("files") Flux filePartFlux) {
+ return filePartFlux.flatMap(file -> file.transferTo(Paths.get(file.filename())))
+ .then(Mono.just("OK"))
+ .onErrorResume(error -> Mono.just("Error uploading files"));
+ }
+
+ @PostMapping("/upload-files-entity")
+ public Mono uploadFileWithEntity(@RequestPart("files") Flux filePartFlux) {
+ FileRecord fileRecord = new FileRecord();
+
+ return filePartFlux.flatMap(filePart -> filePart.transferTo(Paths.get(filePart.filename()))
+ .then(Mono.just(filePart.filename())))
+ .collectList()
+ .flatMap(filenames -> {
+ fileRecord.setFilenames(filenames);
+ return fileRecordService.save(fileRecord);
+ })
+ .onErrorResume(error -> Mono.error(error));
+ }
+
+ @GetMapping("/files/{id}")
+ public Mono geFilesById(@PathVariable("id") int id) {
+ return fileRecordService.findById(id)
+ .onErrorResume(error -> Mono.error(error));
+ }
+}
diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java
new file mode 100644
index 0000000000..b4cd4df627
--- /dev/null
+++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java
@@ -0,0 +1,8 @@
+package com.baeldung.webflux.filerecord;
+
+import org.springframework.data.r2dbc.repository.R2dbcRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface FileRecordRepository extends R2dbcRepository {
+}
diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java
new file mode 100644
index 0000000000..6e74f3577b
--- /dev/null
+++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java
@@ -0,0 +1,23 @@
+package com.baeldung.webflux.filerecord;
+
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+public class FileRecordService {
+
+ private FileRecordRepository fileRecordRepository;
+
+ public FileRecordService(FileRecordRepository fileRecordRepository) {
+ this.fileRecordRepository = fileRecordRepository;
+ }
+
+ public Mono save(FileRecord fileRecord) {
+ return fileRecordRepository.save(fileRecord);
+ }
+
+ public Mono findById(int id) {
+ return fileRecordRepository.findById(id);
+ }
+
+}
diff --git a/spring-5-webflux-2/src/main/resources/application.properties b/spring-5-webflux-2/src/main/resources/application.properties
new file mode 100644
index 0000000000..5e52669cdb
--- /dev/null
+++ b/spring-5-webflux-2/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.r2dbc.url=r2dbc:h2:file:///./testdb
\ No newline at end of file
diff --git a/spring-5-webflux-2/src/main/resources/schema.sql b/spring-5-webflux-2/src/main/resources/schema.sql
new file mode 100644
index 0000000000..76a70c8ff6
--- /dev/null
+++ b/spring-5-webflux-2/src/main/resources/schema.sql
@@ -0,0 +1,5 @@
+CREATE TABLE IF NOT EXISTS file_record (
+ id INT NOT NULL AUTO_INCREMENT,
+ filenames VARCHAR(255),
+ PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java b/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java
new file mode 100644
index 0000000000..c47d53632b
--- /dev/null
+++ b/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java
@@ -0,0 +1,98 @@
+package com.baeldung.webflux.filerecord;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.LinkedMultiValueMap;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = FileRecordController.class)
+@ContextConfiguration(classes = { FileRecordController.class })
+@ComponentScan("com.baeldung.webflux.filerecord")
+@AutoConfigureWebTestClient
+class FileRecordControllerIntegrationTest {
+
+ @Autowired
+ private WebTestClient webTestClient;
+
+ @MockBean
+ private FileRecordService fileRecordService;
+
+ @Test
+ public void givenUploadFilesWithEntity_whenRequestIsValid_thenReturnCreated() throws Exception {
+
+ FileRecord fileRecord = new FileRecord();
+ MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ List files = List.of(firstFile, secondFile);
+ fileRecord.setFilenames(files.stream()
+ .map(MockMultipartFile::getOriginalFilename)
+ .toList());
+
+ Mono fileRecordMono = Mono.just(fileRecord);
+ when(fileRecordService.save(any(FileRecord.class))).thenReturn(fileRecordMono);
+
+ webTestClient.post()
+ .uri("/upload-files-entity")
+ .body(Mono.just(fileRecord), FileRecord.class)
+ .exchange()
+ .expectBody(FileRecord.class);
+
+ }
+
+ @Test
+ public void givenUploadFiles_whenRequestIsValid_thenReturnOk() throws Exception {
+
+ MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ LinkedMultiValueMap multipartData = new LinkedMultiValueMap<>();
+ multipartData.add("file", firstFile);
+ multipartData.add("file", secondFile);
+
+ webTestClient.post()
+ .uri("/upload-files")
+ .bodyValue(multipartData)
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+ @Test
+ public void givenUploadFilesWithEntity_whenRequestFileIsFindById_thenReturnOk() throws Exception {
+
+ FileRecord fileRecord = new FileRecord();
+ MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
+ List files = List.of(firstFile, secondFile);
+ fileRecord.setId(1);
+ fileRecord.setFilenames(files.stream()
+ .map(MockMultipartFile::getOriginalFilename)
+ .toList());
+
+ Mono fileRecordMono = Mono.just(fileRecord);
+
+ when(fileRecordService.findById(1)).thenReturn(fileRecordMono);
+
+ webTestClient.get()
+ .uri("/files/{id}", 1)
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+}
\ No newline at end of file