skip to content

Compare classpath: and file: resource locations for static content. When would you serve from the filesystem, and what are the security concerns?

level: seniorimportance: should knowfreq 35%

answer

  1. classpath: = bundled/immutable; file: = disk/mutable
  2. file: for user uploads / external content
  3. PathResourceResolver blocks ../ traversal (canonical-path check)
  4. trailing slash on directory locations required
  5. separate low-priv upload dir + nosniff + CDN at scale

basics

~20 s

classpath: locations are bundled in the jar (immutable, deploy-time). file: locations point to a directory on disk (mutable at runtime), useful for user uploads. Both go through PathResourceResolver, which blocks path traversal outside the configured root.

solid answer

~40 s

addResourceLocations accepts location strings with prefixes: classpath:/static/ reads from the packaged classpath — immutable, versioned with the build, ideal for app assets. file:/var/data/uploads/ reads a filesystem directory — mutable at runtime, the right choice for user-uploaded or externally-managed content you don't want to redeploy for. You can list multiple locations; they're searched in order. Security: PathResourceResolver is the terminal resolver and verifies the resolved file stays within the configured location, defeating ../ traversal — but only if you rely on it rather than concatenating paths yourself. Additional concerns for filesystem serving: validate/normalize upload filenames, avoid serving executable or sensitive files, set correct Content-Type, consider a separate low-privilege directory, and prefer a CDN or object store for scale. Trailing slash on directory locations is required.

code

java · 11 lines
java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // bundled app assets
    registry.addResourceHandler("/assets/**")
            .addResourceLocations("classpath:/static/");

    // runtime user uploads served from a dedicated directory
    registry.addResourceHandler("/uploads/**")
            .addResourceLocations("file:/var/app/uploads/") // trailing slash!
            .setCachePeriod(600);
}

go deeper

for a junior

Knows classpath is where bundled assets live.

for a middle

Knows file: can serve a disk directory for uploads.

for a senior

Explains traversal protection via PathResourceResolver and filesystem hardening.

for a principal

Weighs origin isolation, CDN/object-store offload, override ordering, and jar vs exploded resource semantics.

## Location string prefixes `addResourceLocations(String...)` takes Spring `Resource` location patterns: - **`classpath:/static/`** — resolves against the classpath (inside the jar/war or build output). Content is fixed at build/deploy time, versioned with the artifact, and read-only. This is the normal home for application CSS/JS/images. - **`file:/opt/app/uploads/`** — resolves against the OS filesystem. The directory can change at runtime independently of deploys. Ideal for user-generated content, operator-managed files, or assets on a mounted volume. - Other `Resource` types work too (e.g. `ServletContextResource` in `war` deployments), but classpath and file are the common two. Multiple locations are searched **in order**; the first match wins. This lets you overlay, e.g., a filesystem override directory in front of bundled defaults. ## When to use filesystem - **User uploads** (avatars, documents) that must appear without redeploying. - **Externally managed content** dropped by an ops process or another service. - **Environment-specific assets** on a mounted volume/secret. For bundled application assets, always prefer classpath — it's immutable, reproducible, and travels with the artifact. ## Security: PathResourceResolver The terminal `PathResourceResolver` enforces that the resolved resource is **inside** one of the configured locations. It rejects paths that escape via `../`, encoded traversal sequences, or symlinks pointing outside (it checks the canonical path). This is your main defense against directory traversal — but it only protects the handler-managed lookup. If you write your own controller that does `new File(baseDir, userInput)`, you bypass this and must validate yourself. ## Filesystem-specific hardening - **Normalize and validate filenames** on upload; never trust client-supplied paths. - **Segregate** upload directories from application/config files; use a dedicated, low-privilege directory outside the app root. - **Content-Type**: ensure correct MIME types; avoid serving user content from the same origin as sensitive endpoints (or use a separate domain) to reduce XSS/content-sniffing risk. Consider `X-Content-Type-Options: nosniff`. - **Executable/dotfiles**: don't expose scripts, `.env`, etc. - **Scale**: for high volume, front with a CDN or serve from object storage (S3) rather than the app JVM. ## Gotchas - **Trailing slash** on a directory location is mandatory (`file:/data/uploads/`, not `.../uploads`). - On Windows, `file:` paths and drive letters need care. - classpath resources inside a jar are not `java.io.File`s — code that assumes `getFile()` breaks; use `getInputStream()`. - Ordering: a filesystem override listed before the classpath default shadows bundled files with the same name.

  • A colleague serves uploads by building new File(uploadDir, request.getParameter("name")) in a controller. Why is that riskier than a file: resource handler?
    It bypasses PathResourceResolver's containment check, so ../ or absolute paths in the parameter can escape the directory (path traversal). The resource-handler path validates the canonical resolved path stays inside the configured location; the hand-rolled File concatenation does not unless you add your own normalization/validation.
  • Why can code that calls Resource.getFile() break for classpath assets?
    Inside a packaged jar, a classpath resource is an entry in the archive, not a real filesystem File. getFile() throws; you must use getInputStream() (or getURL) to read it. It only works when running from an exploded/unpacked classpath directory.

saying these in an interview costs you the question

  • Serving user uploads by concatenating user input into a File path without traversal checks
  • Forgetting the trailing slash on file: directory locations
  • Assuming classpath resources are always java.io.File and calling getFile() in a jar
  • Serving uploads from the same directory as config/secrets
  • Thinking file: locations aren't supported by Spring's resource handling

context