1

I'm building a Spring Boot application where I'm trying to upload files or large content through a REST endpoint. The request is being rejected by Spring Boot with this warning:

HttpMessageNotReadableException: JSON parse error: String value length (20048537) exceeds the maximum allowed (20000000, from StreamReadConstraints.getMaxStringLength())

It looks like Jackson is blocking the request because the JSON string is too large. I'm not trying to catch this exception, I just want to understand:

  • Why this limit exists
  • Whether I'm uploading the file in the wrong way
  • What the correct approach is for handling large file uploads in Spring Boot

Has anyone dealt with the StreamReadConstraints max string length issue? What is the recommended way to upload large files without hitting this JSON size restriction?

New contributor
omar rharbu is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

3 Answers 3

1

Adjusting the jackson value might have worked but that is not the optimal way to send a file to a endpoint. Best is to use MultipartFile, using multipart/request type, which Spring Boot has support for.

Sample Controller

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Objects;

@Controller
public class FileUploadController {

    //  upload directory, should exist and be writable.
    private static final String UPLOAD_DIR = "./uploads/"; 

    @PostMapping("/upload")
    public ResponseEntity<?> handleFileUpload(@RequestParam("file") MultipartFile file,
                                              RedirectAttributes redirectAttributes) {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("Please select a file to upload.");
        }

        // sanitize original filename, to prevent path traversal, more secure

        String fileName = Objects.requireNonNull(file.getOriginalFilename());
        Path destinationFile = Paths.get(UPLOAD_DIR).resolve(
                Paths.get(fileName).getFileName()
        ).normalize();

        // ensure the file is saved within the designated UPLOAD_DIR
        if (!destinationFile.getParent().equals(Paths.get(UPLOAD_DIR).normalize().toAbsolutePath())) {
            return ResponseEntity.badRequest().body("Cannot store file outside current directory.");
        }

        try {
            
            
        
            try (InputStream inputStream = file.getInputStream()) {
                // copy, over write if exists, or make a unique save name for every upload and save it to a table/ persistence linked to user/ transaction 
                Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING);
            }

            redirectAttributes.addFlashAttribute("message",
                    " uploaded '" + fileName + "'");
            
            return ResponseEntity.ok("Uploaded successfully: " + fileName);

        } catch (IOException e) {
            e.printStackTrace();//todo logger
      
            return ResponseEntity.internalServerError().body("Failed file upld: " + e.getMessage());
        } catch (SecurityException e) {
            e.printStackTrace();
            return ResponseEntity.internalServerError().body("Permissions error: " + e.getMessage());
        }
    }
}

The client has to construct the message in the correct way. For testing can use a html form with correct attributes.

Curl to send multipart request

curl -X POST http://localhost:8080/api/uploadWithMeta \
  -H "Content-Type: multipart/form-data" \
  -F "file=@/path/to/your/local/file.txt" \
  -F "userId=123" \
  -F "description=A test uplad"

Or as HTML form, action is url of controller

<form method="POST" action="/upload" enctype="multipart/form-data">
        File to upload:</td><td><input type="file" name="file" /><input type="submit" value="Upload" />
    </form>

Java client to send multipart programmatially

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;

public class ApacheHttpClientFileUpload {

    public static void uploadFile(String url, String filePath, String parameterName) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            File fileToUpload = new File(filePath);

            // Build the multipart entity
            HttpEntity multipartEntity = MultipartEntityBuilder.create()
                    .addBinaryBody(parameterName, fileToUpload, ContentType.DEFAULT_BINARY, fileToUpload.getName())
                    // You can add text fields too:
                    // .addTextBody("description", "A sample file upload", ContentType.TEXT_PLAIN)
                    .build();

            httpPost.setEntity(multipartEntity);

            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                // Print the response status and body
                System.out.println("Status line: " + response.getStatusLine());
                HttpEntity responseEntity = response.getEntity();
                if (responseEntity != null) {
                    System.out.println("Response body: " + EntityUtils.toString(responseEntity));
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        String serverUrl = "http://localhost:8080/api/upload"; // The Spring Boot endpoint URL
        String fileLocation = "./sample.txt"; // The local file path
        String formFieldName = "file"; // Must match the @RequestParam name in Spring Boot

        uploadFile(serverUrl, fileLocation, formFieldName);
    }
}

This will work for even 500 MB, i have not tested more.

For extremely large files (gigabytes), the external client can split file into chunks and send each chunk as separate request. Need another endpoint to handle each chunk part and a final endpoint to reassemble them on the server (client calls 'done' end point with last chunk.

Sign up to request clarification or add additional context in comments.

2 Comments

For larger files it's probably better to use file.getInputStream() instead of file.getBytes(). Or if you only want to store it, file.transferTo(path). file.getBytes() requires the entire file to be stored in memory, whereas the other two allow Spring to write the uploaded file to a temporary file first, or use other techniques that prevent storing the file in memory.
yes thank you. edited answer
0

REFERENCE: https://docs.spring.io/spring-boot/docs/3.2.0/reference/html/howto.html#howto.spring-mvc.customize-jackson-objectmapper

Add config into your Spring Boot project.

JacksonConfig.java

package com.example;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamReadConstraints;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

/**
https://docs.spring.io/spring-boot/docs/3.2.0/reference/html/howto.html#howto.spring-mvc.customize-jackson-objectmapper
*/
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return new Jackson2ObjectMapperBuilderCustomizer() {
            @Override
            public void customize(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder builder) {
                // Set maximum string length 60 MB
                StreamReadConstraints constraints = StreamReadConstraints.builder()
                        .maxStringLength(60 * 1024 * 1024) // 60 MB
                        .build();

                JsonFactory factory = JsonFactory.builder()
                        .streamReadConstraints(constraints)
                        .build();

                builder.factory(factory);
            }
        };
    }
}

I have tested it and it works in Spring Boot 3.5.7 and Spring Boot 3.4.11.

2 Comments

This will override the default configured ObjectMapper which can lead to suprises. Instead use a JacksonCustomizer to add this to the default one.
Okay brother, I did that and it worked successfully. Thank you!
0

I ran into the same thing: Jackson is complaining because you're basically sending the whole file as one giant JSON string, and it has a built-in size cap. It’s not really meant for that!
If you switch to a normal multipart upload MultipartFile , Spring just streams the file and you never hit this limit.
Once I stopped sending the file in JSON, the problem disappeared.
You can increase Jackson’s limit, but honestly it’s not worth it unless you really have no other option...

1 Comment

ha i wrote the same but with code example :) at about the same time

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.