Spring Boot에서 파일 업로드 기능을 구현하던 중, 비동기 처리를 위해 @Async 어노테이션을 사용했을 때 다음과 같은 에러가 발생했습니다.
java.io.FileNotFoundException: /tmp/tomcat.xxx/work/Tomcat/localhost/ROOT/upload_xxx.tmp (No such file or directory)
또는
java.lang.IllegalStateException: The temporary upload location is not valid
Spring에서 MultipartFile은 HTTP 요청의 생명주기에 의존합니다.
HTTP 요청 시작 → MultipartFile 임시 저장 → 요청 처리 → HTTP 응답 완료 → 임시 파일 자동 삭제
@Async 어노테이션이 붙은 메서드는 별도의 스레드 풀에서 실행되며, 호출 즉시 반환됩니다.
public void processFilesAsync(MultipartFile imageFile, MultipartFile videoFile) {
uploadAsync(imageFile, videoFile); // 비동기 호출 후 즉시 반환
} // HTTP 응답 완료 → MultipartFile 임시 파일 삭제
@Async
public void uploadAsync(MultipartFile imageFile, MultipartFile videoFile) {
// 실제 실행은 이후에 발생 (임시 파일이 이미 삭제된 상태)
}
public void processFilesAsync(MultipartFile imageFile, MultipartFile videoFile) throws IOException {
// HTTP 요청이 유효한 동안 File 객체로 변환
File savedImageFile = fileStorageHelper.saveToTemp(imageFile);
File savedVideoFile = fileStorageHelper.saveToTemp(videoFile);
// 이제 안전하게 비동기 처리
uploadAsync(savedImageFile, savedVideoFile);
}
@Async
public void uploadAsync(File imageFile, File videoFile) {
// File 객체로 안전하게 접근 가능
uploadToCloud(imageFile);
uploadToCloud(videoFile);
}
public File saveToTemp(MultipartFile multipartFile) throws IOException {
String uniqueFilename = UUID.randomUUID().toString() + getFileExtension(multipartFile.getOriginalFilename());
Path targetPath = Paths.get("/tmp/uploads").resolve(uniqueFilename);
multipartFile.transferTo(targetPath.toFile());
return targetPath.toFile();
}
Spring의 @Async와 MultipartFile을 함께 사용할 때는 HTTP 요청의 생명주기를 고려해야 합니다. 비동기 처리 전에 MultipartFile을 안전한 File 객체로 변환하는 것이 핵심 해결책입니다.
이 방법을 통해 파일 업로드의 안정성을 확보하고, 대용량 파일 처리 시에도 메모리 효율성을 유지할 수 있습니다.