You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot图片上传临时文件清理失败问题排查与修复

问题:Spring Boot文件上传后无法清理临时文件的原因与解决方法

问题场景

开发了React前端+Spring Boot后端的图片上传应用,用于将图片保存到指定文件夹,核心后端代码如下:

@PostMapping(value = "/upload")
public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) {
    try {
        System.out.println("-------------------------------------------------------------------");
        this.process(IOUtils.toByteArray(file.getInputStream()), user, file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return new ResponseEntity<>("Uploaded", HttpStatus.OK);//userService.uploadImageFile(user, file);
}


@Async
public void process(byte[] bs, String user, String ext) throws IOException {

    String fName = user.substring(0, user.lastIndexOf("@")).concat(ext);
    // Full path
    String filePath = path + File.separator + fName;

    System.out.println(fName + " and " + filePath);

    File f = new File(path);
    if (!f.exists()) {
        f.mkdir();
    }

    File convertedFile = new File(filePath);

    FileOutputStream fOS = new FileOutputStream(convertedFile);
    System.out.println("-----Started------");
    
    fOS.write(bs);
    fOS.close();
    System.out.println("-----closed------");
}

上传过程中出现如下错误:

org.springframework.web.multipart.support.StandardServletMultipartResolver[0;39m: Failed to perform cleanup of multipart items
java.io.UncheckedIOException: Cannot delete D:\JS Projects\myapp\BackEnd\UserLoginSignUp\images\work\Tomcat-2\localhost\ROOT\upload_d31827cc_51e6_4901_b60e_6afdd0ee8a99_00000004.tmp
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.delete(DiskFileItem.java:431)
at org.apache.catalina.core.ApplicationPart.delete(ApplicationPart.java:53)
at org.springframework.web.multipart.support.StandardServletMultipartResolver.cleanupMultipart(StandardServletMultipartResolver.java:134)
at org.springframework.web.servlet.DispatcherServlet.cleanupMultipart(DispatcherServlet.java:1251)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1108)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
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:555)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
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:390)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)

错误原因

  • 异步处理导致资源冲突:process方法标记了@Async,主线程会直接返回响应并触发Spring的临时文件清理逻辑,但此时异步线程可能还在占用临时文件的资源,导致文件被锁定无法删除。
  • 输入流未正确释放:手动读取file.getInputStream()后未确保流被及时关闭,导致临时文件的文件句柄未释放,Tomcat无法完成清理。

解决方法

方法一:主线程中完成流读取与关闭,再传递字节数组给异步方法

使用try-with-resources自动关闭输入流,确保主线程中就释放临时文件资源,异步线程仅处理内存中的字节数组:

@PostMapping(value = "/upload")
public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) {
    try (InputStream inputStream = file.getInputStream()) {
        byte[] bs = IOUtils.toByteArray(inputStream);
        String ext = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        this.process(bs, user, ext);
    } catch (IOException e) {
        e.printStackTrace();
        return new ResponseEntity<>("Upload failed", HttpStatus.INTERNAL_SERVER_ERROR);
    }
    return new ResponseEntity<>("Uploaded", HttpStatus.OK);
}

方法二:异步方法中直接使用MultipartFile的transferTo方法

Spring推荐使用transferTo处理文件上传,它会自动管理资源的打开与关闭,避免手动流操作的问题:

@PostMapping(value = "/upload")
public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) {
    try {
        String ext = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        this.process(file, user, ext);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>("Upload failed", HttpStatus.INTERNAL_SERVER_ERROR);
    }
    return new ResponseEntity<>("Uploaded", HttpStatus.OK);
}

@Async
public void process(MultipartFile file, String user, String ext) throws IOException {
    String fName = user.substring(0, user.lastIndexOf("@")).concat(ext);
    String filePath = path + File.separator + fName;

    File f = new File(path);
    if (!f.exists()) {
        f.mkdirs(); // 支持创建多级目录
    }

    file.transferTo(new File(filePath));
}

方法三:配置延迟解析Multipart文件(备选方案)

在application.properties中添加配置,延迟临时文件的清理时机,确保异步线程完成后再执行清理:

spring.servlet.multipart.resolve-lazily=true

内容的提问来源于stack exchange,提问作者Abhishek Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:03:09