5

I am trying to upload a byte[] that contains an image to my Spring rest service (running in Spring Boot, btw) as a MultipartFile with my client running Spring RestTemplate and am getting HttpClientErrorException: 400 Bad Request.

My endpoint:

@RequestMapping(value="/scale/{percent}", method= RequestMethod.POST)
public ResponseEntity scaleImage(@PathVariable("percent") float percent,
                                        @RequestParam("file") MultipartFile file) {

    try {
        if (!file.isEmpty()) {
            byte [] result = transformService.scaleImage(percent, file.getBytes());
            return getResponseEntityFromBytes(result);
        } else {
            return generateBadRequestError();
        }
    } catch (Exception e) {
        if (e instanceof InvalidOperationParameterException) {
            // TODO - populate message with bad parameters
            LOGGER.log(Level.FINE, "Invalid Parameters: ");
            return generateBadRequestError();
        } else {
            LOGGER.log(Level.SEVERE, "Exception caught: " + e.getMessage(), e);
            return generateServerError(e.getMessage());
        }
    }
}

My Spring RestTemplate client:

public void scaleImage(byte[] image, float percent) throws Exception {
    String url = "http://localhost:8080/scale/" + percent;
    this.testNumberThreads=10;
    this.testNumberThreads=10;

    MultiValueMap<String, Object> mvm = new LinkedMultiValueMap<>();
    mvm.add("file", image);
    TransformedResponse r = doPost(url, mvm);
}

private TransformedResponse doPost(String url, MultiValueMap<String, Object> mvm) {
    RestTemplate restTemplate = new RestTemplate();
    TransformedResponse xr = null;
    try {
        xr = restTemplate.postForObject(url, mvm, TransformedResponse.class);
    } catch (RestClientException e) {
        e.printStackTrace();
    }
    return xr;
}

...

public class TransformedResponse {
    byte[] image;

    public byte[] getImage() {
        return image;
    }

    public void setImage(byte[] image) {
        this.image = image;
    }
}

Here is the exception I'm seeing in the client (nothing hitting server yet):

org.springframework.web.client.HttpClientErrorException: 400 Bad Request
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
    at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:588)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:546)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:502)
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:330)
    at com.me.image.xform.LoadTest.doPost(LoadTest.java:110)
    at com.me.image.xform.LoadTest.loadTestScalePercent(LoadTest.java:75)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)

Why won't this request post correctly?

1
  • Is your percent value prefixed with /? You're making call to url like this: String url = "http://localhost:8080/scale" + percent;. Moreover, did you enabled support of multipart files in your servlet config? Commented May 15, 2014 at 22:18

3 Answers 3

10

I found my problem. I needed to add an AbstractResource (in this case a ByteArrayResource) to my MultiValueMap instead of the raw byte array. Here's the code that fixed it:

public void scaleImage(byte[] image, float percent) throws Exception {
    String url = "http://localhost:8080/scale/" + percent;

    final byte[] rawBytes = image.clone();

    MultiValueMap<String, Object> mvm = new LinkedMultiValueMap<>();
    ByteArrayResource bar = new ByteArrayResource(rawBytes) {
        @Override
        public String getFilename() {
            return "Test-"+rawBytes.length + ".jpg";
        }
    };

    mvm.add("file", bar);
    TransformedResponse r = doPost(url, mvm);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Why you need to clone?
3

First of all, when using Spring, make sure that you have proper MultiPartFile resolver defined in your servlet context:

<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="52428800"/>
    <property name="maxInMemorySize" value="52428800"/>
</bean>

If you're using maven, this resolver is located in spring-web artifact:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${your.spring.version}</version>
</dependency>

Then, create form and make sure you're using proper enctype:

<form method="post" action="upload.form" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit"/>
</form>

Finally, handle file upload in your controller

@RequestMapping(value="/path", method= RequestMethod.POST)
public StringscaleImage(@RequestParam("file") MultipartFile file) {
    //....
}

Remember that asynch file upload is supported only with HTML5, with others you'd need to use some workarounds (like flash or iframes).

If you're still facing 400 Error, add to your logging service this logger (or similar, depending on logging framework):

<appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out"/>
    <param name="threshold" value="TRACE"/>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %-5p [%c] %m%n"/>
    </layout>
</appender>

<logger name="org.springframework.web.method.HandlerMethod">
    <level value="TRACE"/>
</logger>

<root>
    <priority value="info"/>
    <appender-ref ref="console"/>
</root>

It should output exception thrown during request handling

1 Comment

I have the resolver in my spring boot config - that resolved another problem where resolver not found, but that was resolved before I encountered the 400 error: ` @Bean public CommonsMultipartResolver multipartResolver (){ System.out.println("=======>>>>> multipart resolver Called :"); CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(5000000); return multipartResolver; }`
0

Instead of trying to pass the MultipartFile in as a request parameter, try the following:

@RequestMapping(value="/scale/{percent}", method= RequestMethod.POST)
public ResponseEntity scaleImage(@PathVariable("percent") float percent,
        MultipartHttpServletRequest request) {
    Iterator<String> fileNames = request.getFileNames();
    MultipartFile uploadedFile = request.getFile(fileNames.next());
    String fileName = uploadedFile.getName();

This is the only way I could actually get my multipart file to be accepted.

If you are still getting an empty file from this process, it must be something to do with how you are POST'ing the data.

2 Comments

That helps a bunch. The request is accepted by the server, but I am getting: java.util.NoSuchElementException: null at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:396) at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:405) at com.me.image.xform.ws.v1.TransformRs.scaleImage(TransformRs.java:76) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
looks like it wants a filename

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.