Search code examples
javaspring-bootgradleserializationmultipartfile

Serialization issues on passing a MultipartFile to the server in Spring Boot


I'm trying to pass a file through the server to a database in Spring Boot. From the info I gathered, I came to the conclusion that passing it as a MultipartFile rather than a File would be the right way to do it. I'm getting this error though:

Caused by: jakarta.ws.rs.client.ResponseProcessingException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.mock.web.MockMultipartFile["inputStream"])
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.mock.web.MockMultipartFile["inputStream"])

So I did as the error code said and I disabled SerializationFeature.FAIL_ON_EMPTY_BEANS by adding this line in the application.properties:

spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false

Initially, when I was building the app using gradle, I was getting an exception regarding the StaleOutputCleaner. I don't remember changing anything, but now it builds successfully, but it's throwing the same serialization error as before.

I tested the server on postman and the upload feature works over there. How can I get over this issue and get it working in the app as well? I'll put all the relevant pieces of code down below. The first block of code is the call from the ServerUtils file on the client side. The rest of the classes are on the server side.

public void saveFile(File file) throws IOException {
        internalPostRequest("secure/" + store.accessStore().getUsername() + "/file/add",
                Entity.entity(convert(file), APPLICATION_JSON), new GenericType<>(){});
    }
public static MultipartFile convert(File file) throws IOException {
        String filename = file.getName();
        FileInputStream inputStream = new FileInputStream(file);
        return new MockMultipartFile(filename, inputStream);
    }
private <T> T internalPostRequest(String path, Entity send, GenericType<T> retType) {
        return ClientBuilder.newClient(new ClientConfig())
                .target(getServer()).path(path)
                .request(APPLICATION_JSON)
                .accept(APPLICATION_JSON)
                .post(send, retType);
    }

public class FileController {
    @Autowired
    private StorageService service;
    @PostMapping("/add")
    public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        String uploadFile = service.uploadFile(file);
        return ResponseEntity.status(HttpStatus.OK)
                .body(uploadFile);
    }
@Service
public class StorageService {
    @Autowired
    private StorageRepository repository;

    public String uploadFile(MultipartFile file) throws IOException {
        repository.save(new FileData(file.getOriginalFilename(),
                file.getContentType(), compressFile(file.getBytes())));
        return "File uploaded succesfully: "+ file.getOriginalFilename();
    }
    public String getType(String fileName){
        Optional<FileData> dbFileData = repository.findByName(fileName);
        if (dbFileData.isPresent()) {
            return dbFileData.get().getType();
        } else {
            return "File not found: " + fileName;
        }
    }
    public static byte[] compressFile(byte[] data) {
        Deflater deflater = new Deflater();
        deflater.setLevel(Deflater.BEST_COMPRESSION);
        deflater.setInput(data);
        deflater.finish();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
        byte[] tmp = new byte[4*1024];
        while (!deflater.finished()) {
            int size = deflater.deflate(tmp);
            outputStream.write(tmp, 0, size);
        }
        try {
            outputStream.close();
        } catch (Exception ignored) {
        }
        return outputStream.toByteArray();
    }

Solution

  • If this works with Postman, likely the issue is in the client part.

    Multipart is not APPLICATION_JSON ("application/json"). Try to use "multipart/form-data" or "application/octet-stream" when you prepare the entity and in request(). Also try to remove .accept(APPLICATION_JSON) completely.

    Consider using higher level http clients. Some references:
    https://www.baeldung.com/httpclient-multipart-upload
    https://www.baeldung.com/spring-rest-template-multipart-upload