jeecgboot/JeecgBoot · error · NullPointerException

Specified file not found

Error message

Specified file not found

What it means

FileDownloadUtils.downloadFile constructs a File from storePath and checks exists() before streaming. If the file does not exist on disk, it throws NullPointerException('Specified file not found'). Notably it throws NPE (not FileNotFoundException) for a missing-file condition, which is an unusual exception-type choice that can confuse generic catch blocks. This is the single-file download entry point used by download endpoints.

Source

Thrown at jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileDownloadUtils.java:47

 * @date: 2019-05-24 16:34
 **/
@Slf4j
public class FileDownloadUtils {

    /**
     * 单文件下载
     *
     * @param response
     * @param storePath 下载文件储存地址
     * @param fileName  文件名称
     * @author: chenrui
     * @date: 2019/5/24 17:10
     */
    public static void downloadFile(HttpServletResponse response, String storePath, String fileName) {
        response.setCharacterEncoding("UTF-8");
        File file = new File(storePath);
        if (!file.exists()) {
            throw new NullPointerException("Specified file not found");
        }
        if (fileName == null || fileName.isEmpty()) {
            throw new NullPointerException("The file name can not null");
        }
        // 配置文件下载
        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        // 下载文件能正常显示中文
        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }
        // 实现文件下载
        byte[] buffer = new byte[1024];
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis);) {

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Verify the file physically exists at the resolved storePath before calling downloadFile.
  2. Ensure storePath resolution (relative vs absolute, base directory) matches how the file was stored.
  3. In docker, confirm the upload volume is mounted to the same path in all containers.
  4. Add a file-exists check in the controller and return a 404 with a clear message instead of letting NPE propagate.

Example fix

// before — download endpoint trusts DB path blindly
downloadFile(response, record.getStorePath(), record.getFileName());

// after — verify existence, return clean 404
File f = new File(record.getStorePath());
if (!f.exists()) { response.sendError(404, "file gone"); return; }
downloadFile(response, record.getStorePath(), record.getFileName());
Defensive patterns

Strategy: validation

Validate before calling

// Check existence and return a clean 404 instead of NPE
File f = new File(storePath);
if (!f.exists() || !f.isFile()) {
  response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found");
  return;
}
FileDownloadUtils.downloadFile(response, storePath, fileName);

Type guard

public static boolean isDownloadable(String storePath) {
  File f = new File(storePath);
  return f.exists() && f.isFile() && f.canRead();
}

Try / catch

try {
  FileDownloadUtils.downloadFile(response, storePath, fileName);
} catch (NullPointerException e) {
  response.sendError(404, "Specified file not found");
}

Prevention

When it happens

Trigger: A download request whose storePath points to a file that was deleted, never written, is on an unmounted volume, or whose path was constructed incorrectly (wrong base dir, missing subdirectory). Also when the file record exists in DB but the physical file was cleaned up.

Common situations: File was uploaded then the upload directory was migrated/cleaned; DB stores a relative path but storePath passed is absolute (or vice versa); docker volume not mounted in the container; file deleted manually but DB record remains.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/abf7604174208c2b. Report an issue: GitHub.