5

I am dealing with a strange behaviour regarding a MultipartFile.

My project is a Spring Boot backend that receives a text file. This text file comes in as a MultipartFile. I than want to send this file to a secondary Spring Boot backend which shall add some content to the file, before my primary backend reads the file. These content changes are not mandatory, the program does not crash if they are not present.

To send the MultipartFile to the other backend I have to convert the MultipartFile to a java.io.File. And while doing this somehow the MultipartFile gets destroyed.

After creating a java.io.File the original MultipartFile cannot be read by the BufferedReader.

Heavy edit:

My projects specifications changed and the extra backend was cancelled. However I am still curious what happens here. The following code reproduces the Exception I encountered:

@CrossOrigin
@RestController
@RequestMapping("/dragon")
public class TestController {

    @PostMapping("/killFile")
    public String sendInFileHere(@Valid @RequestBody MultipartFile multipartFile) {
        if (multipartFile == null) {
            throw new IllegalArgumentException("File has to be Present");
        }
        File file = new File(multipartFile.getOriginalFilename());
        try {
            multipartFile.transferTo(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        BufferedReader reader;
        try {
            InputStream is = multipartFile.getInputStream(); //exception is thrown here
            reader = new BufferedReader(new InputStreamReader(is));
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "something went wrong";
    }
}

The exception that is thrown is as follws:

java.io.FileNotFoundException: C:\Users\lucas.kahler\AppData\Local\Temp\tomcat.1947057742180166642.8080\work\Tomcat\localhost\ROOT\upload_51753fdf_0308_49d4_800c_bd95bd7760f3_00000001.tmp (Das System kann die angegebene Datei nicht finden)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)
    at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:194)
    at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100)
    at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getInputStream(StandardMultipartHttpServletRequest.java:251)
    at eu.molit.dragon.text.Test.sendInFileHere(Test.java:34)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:835)

When I comment out the part, where the file receives the content from the MultipartFile it perfectly works:

@CrossOrigin
@RestController
@RequestMapping("/dragon")
public class TestController {

    @PostMapping("/killFile")
    public String sendInFileHere(@Valid @RequestBody MultipartFile multipartFile) {
        if (multipartFile == null) {
            throw new IllegalArgumentException("File has to be Present");
        }
        File file = new File(multipartFile.getOriginalFilename());
//        try {
//            multipartFile.transferTo(file);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
        BufferedReader reader;
        try {
            InputStream is = multipartFile.getInputStream(); 
            reader = new BufferedReader(new InputStreamReader(is));
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "something went wrong";
    }
}

In the above example the first line of the sent text file is returned. That indicates that something is happening during the conversion from MultipartFile to File.

Any ideas?

Lord Luce
  • 63
  • 1
  • 7
  • Please add the rest of the exception stacktrace. As is, we can't tell which line of your code is causing the exception, and that seems quite pertinent. – JakeRobb Dec 06 '19 at 16:13
  • Did you try to check that file with name from stacktrace really exists? For examply with command `type `. – Alexey Usharovski Dec 06 '19 at 17:07
  • @AlexeyUsharovski I looked at the directory and the file was there. The file did not get removed when I restarted the program, so it already was there when I run the programm and the error was thrown. – Lord Luce Dec 11 '19 at 08:01
  • @JakeRobb I added the code where the exception was thrown – Lord Luce Dec 11 '19 at 08:02
  • Add _the rest of the stacktrace_ please. :) – JakeRobb Dec 11 '19 at 16:52
  • @JakeRobb finally I got to edit this again. Here you go with a simplified version and the full stack trace :) – Lord Luce Dec 18 '19 at 08:57
  • 3
    You can only read the inputstream once. So either buffer it, or read from the file you just written to the file system (the `File` you create). – M. Deinum Dec 18 '19 at 09:13
  • 2
    The comment from @M.Deinum is the correct answer. The implementation of ``transferTo`` internally calls ``getInputStream``, thus "consuming" the stream and leading to subsequent inability to re-use it. It's common practice to do so with streamable resources. – Filippo Possenti Dec 18 '19 at 09:24

3 Answers3

4

This is normal behaviour.

The call to transferTo consumes the associated InputStream. As a result, you can't re-use it.

If you want to re-read the data, you will have to do it against your newly created (and written) File, by opening a stream against it.

Filippo Possenti
  • 1,300
  • 8
  • 18
  • 2
    Yep. A somewhat annoying thing about InputStreams (and regular Streams too) is that you can only traverse them once. If you want to consume it again, you would have to reinstantiate a new stream. In the case of MultipartFile, you have no opportunity to do so -- and furthermore, the underlying temporary file is deleted (per the Javadoc: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html), so it wouldn't work even if you did have the opportunity. – JakeRobb Dec 19 '19 at 16:56
0

I got similar problem (java.io.FileNotFoundException) when using asynchronous thread to get InputStream, sample code as below:

@CrossOrigin
@RestController
@RequestMapping("/dragon")
public class TestController {

    // http thread
    @PostMapping("/killFile")
    public String sendInFileHere(@Valid @RequestBody MultipartFile multipartFile) {
        if (multipartFile == null) {
            throw new IllegalArgumentException("File has to be Present");
        }

        // create a new thread to handle InputStream
        new Thread(() -> {
            try {
                // simulate some time comsuming tasks...
                Thread.sleep(1000);

                InputStream stream = multipartFile.getInputStream(); // exception will be thrown when invoking this line after http thread returns.
                // do something with InputStream...
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // http thread returns
        return "Task submited.";
    }
}

Just as above example, when the http thread returns, it's impossible to get the InputStream from MultipartFile anymore. If you do need to handle stream asynchronously, please cache the InputStream before http thread ends, for later usage:

        // get the InputStream here
        InputStream stream = multipartFile.getInputStream();
        new Thread(() -> {
            try {
                // simulate some time comsuming tasks...
                Thread.sleep(1000);

                // stream is available here
                // do something with InputStream...
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
rocky chi
  • 31
  • 1
  • 4
0

transferTo has two implementations : transferTo(File dest) and transferTo(Path dest)

The second one uses FileCopyUtils.copy(InputStream in, OutputStream out) so In your case if you modify this part of your code as below :

    try {
        multipartFile.transferTo(Path.of(multipartFile.getOriginalFilename()));
    } catch (IOException e) {
        e.printStackTrace();
    }

your problem will be solved and you can call getInputStream() on the multipartFile again without any exceptions.