java.io.FileNotFoundException for a present MultipartFile
Asked Answered
A

3

6

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?

Adaptive answered 6/12, 2019 at 13:44 Comment(8)
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.Manrope
Did you try to check that file with name from stacktrace really exists? For examply with command type <filename>.Coster
@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.Adaptive
@Manrope I added the code where the exception was thrownAdaptive
Add the rest of the stacktrace please. :)Manrope
@Manrope finally I got to edit this again. Here you go with a simplified version and the full stack trace :)Adaptive
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).Lyonnaise
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.Ixion
I
5

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.

Ixion answered 18/12, 2019 at 9:27 Comment(1)
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: docs.spring.io/spring/docs/current/javadoc-api/org/…), so it wouldn't work even if you did have the opportunity.Manrope
E
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();
Eclosion answered 26/9, 2022 at 1:35 Comment(0)
C
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.

Confectioner answered 10/7, 2023 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.