🧩 Java & Spring Web MVC — ServletUriComponentsBuilder Cheatsheet¶
Unified quick reference: from basic Java concepts of URI building to Spring’s
ServletUriComponentsBuilderand related utilities for safe, framework-integrated URL construction.
What it is (and why it’s useful)¶
ServletUriComponentsBuilder (Spring MVC) builds absolute URLs from the current HTTP request (scheme, host, port, context path). It saves you from manual string concatenation, handles encoding, and stays correct across environments (localhost, production, reverse proxies).
It is essentially a servlet-aware extension of UriComponentsBuilder, aware of your running request and servlet context.
Quick glossary¶
- Context path – Base path your app is mounted at (e.g.,
/myapp). - Request URI – Path of the current request (e.g.,
/api/users/42). - Builder – Fluent object you add parts to (path, query, fragment) to produce a URL.
- UriComponents – Immutable representation of a URI (scheme/host/port/path/query).
- Forwarded headers – Proxy headers (
X-Forwarded-*) telling the app the external URL. - Encode – Percent-encode unsafe characters (once—and only once).
Core package & location¶
- Class:
org.springframework.web.servlet.support.ServletUriComponentsBuilder - Module:
spring-webmvc - Also see:
org.springframework.web.util.UriComponentsBuilder
Typical use: building the Location header (201 Created)¶
@PostMapping("/contacts")
public ResponseEntity<ContactDto> create(@RequestBody CreateContact cmd) {
Contact saved = service.create(cmd);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest() // e.g., http://localhost:8080/contacts
.path("/{id}") // → http://.../contacts/{id}
.buildAndExpand(saved.getId())
.toUri(); // encodes safely
return ResponseEntity.created(location).body(ContactDto.from(saved));
}
This produces a fully qualified URI, correctly encoded and respecting forwarded headers when configured.
Common factory methods¶
| Factory method | Base | When to use |
|---|---|---|
fromCurrentRequest() |
Full current request URI (with query) | For related links within the same endpoint |
fromCurrentRequestUri() |
Current URI, but without query params | When adding new query params |
fromCurrentContextPath() |
Context root only (scheme://host:port/context) |
For top-level links like /health or /login |
fromRequest(request) |
Explicit HttpServletRequest |
When the request object is available |
UriComponentsBuilder.fromPath("/path") |
No servlet dependency | For tests or offline link building |
Examples — creating URLs¶
From current request or context¶
String a = ServletUriComponentsBuilder
.fromCurrentContextPath() // https://example.com/myapp
.path("/files/123.png")
.toUriString();
String b = ServletUriComponentsBuilder
.fromCurrentRequestUri() // https://example.com/myapp/api/users/42
.replacePath("/health") // path becomes /health
.replaceQuery(null) // drop query
.toUriString();
Output:
From explicit HttpServletRequest¶
String url = ServletUriComponentsBuilder
.fromRequest(request)
.replacePath("/docs")
.replaceQuery("v=1")
.toUriString();
// → https://example.com/myapp/docs?v=1
Building relative or templated URIs¶
String url = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/api")
.pathSegment("users", "{id}") // safe segment joining + encoding
.queryParam("verbose", "{v}") // adds ?verbose=1
.buildAndExpand(42, 1) // expand placeholders
.toUriString();
// → https://example.com/myapp/api/users/42?verbose=1
Advanced transformations¶
String url = ServletUriComponentsBuilder
.fromCurrentRequestUri()
.replacePath("/search")
.replaceQueryParam("q", "café") // encoded to caf%C3%A9
.fragment("top")
.toUriString();
Output:
Reading URI components¶
var comps = ServletUriComponentsBuilder
.fromCurrentRequestUri()
.build();
System.out.println(comps.getScheme()); // https
System.out.println(comps.getHost()); // example.com
System.out.println(comps.getPort()); // -1 (default)
System.out.println(comps.getPath()); // /myapp/api/users/42
System.out.println(comps.getQuery()); // active=true
Adding or replacing query parameters¶
URI uri = ServletUriComponentsBuilder
.fromCurrentRequestUri()
.queryParam("page", 2)
.queryParam("size", 20)
.queryParam("sort", "name,asc")
.build()
.toUri();
Output conversions¶
UriComponents comps = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
URI uri = comps.toUri(); // java.net.URI
String asString = comps.toUriString(); // String
Practical utilities¶
Pagination links (RFC‑5988 style):
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentRequestUri();
String first = base.replaceQueryParam("page", 0).toUriString();
String next = base.replaceQueryParam("page", page + 1).toUriString();
String prev = base.replaceQueryParam("page", Math.max(page - 1, 0)).toUriString();
return ResponseEntity.ok()
.header("Link",
"<" + first + ">; rel=\"first\", " +
"<" + next + ">; rel=\"next\", " +
"<" + prev + ">; rel=\"prev\"")
.body(body);
Self link assembler:
public static URI selfForId(Object id) {
return ServletUriComponentsBuilder
.fromCurrentRequestUri()
.path("/{id}")
.buildAndExpand(id)
.toUri();
}
Encoding behavior¶
- Path and query parts are encoded automatically.
- Never manually
URLEncoder.encodebefore passing. URI.create()does not encode and will throw for illegal chars.- Always rely on
pathSegment()orbuildAndExpand().
Proxy / deployment awareness¶
Behind reverse proxies (e.g. Nginx, Traefik):
Enable forwarded header support:
Or in application.properties:
Otherwise, you’ll see internal addresses like http://localhost:8080 in generated links.
Gotchas & anti-patterns¶
- Placeholders aren’t magic: must call
buildAndExpand(...). - Double slashes: avoid
path("/a/").path("/b")— usepathSegment(). - Off-request usage:
fromCurrent*()requires a servlet request context. - Null query values:
queryParam("x", (Object)null)produces?x; usereplaceQueryParam()to remove. - Double encoding: don’t call
.encode()twice.
Mini reference table¶
| Method | What it does | Example |
|---|---|---|
fromCurrentContextPath() |
Base = scheme + host + port + context | https://ex.com/app |
fromCurrentRequestUri() |
Full current URI | https://ex.com/app/api/u/42 |
path("/x") |
Append raw path | /app/x |
pathSegment("a", "b") |
Append encoded segments | /a%20b |
queryParam("k", v) |
Add query param | ?k=1 |
replacePath("/p") |
Replace path | /p |
replaceQueryParam("k", v) |
Replace param | ?k=9 |
fragment("top") |
Add fragment | #top |
buildAndExpand(...) |
Expand templates | /u/42 |
toUri() |
→ java.net.URI |
|
toUriString() |
→ String |
End-to-end example¶
// Incoming: https://api.example.com/myapp/api/users/42?active=true
String profile = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/profiles/{id}")
.buildAndExpand(42)
.toUriString();
String avatar = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/files/avatars/{id}.png")
.buildAndExpand(42)
.toUriString();
String search = ServletUriComponentsBuilder
.fromCurrentRequestUri()
.replacePath("/search")
.replaceQueryParam("q", "café")
.replaceQueryParam("page", 2)
.toUriString();
System.out.println(profile);
System.out.println(avatar);
System.out.println(search);
Output:
https://api.example.com/myapp/profiles/42
https://api.example.com/myapp/files/avatars/42.png
https://api.example.com/myapp/search?q=caf%C3%A9&page=2
Bottom line summary¶
- Use
fromCurrentContextPath()to build top-level or absolute URLs. - Use
fromCurrentRequestUri()to tweak the current endpoint into a related one. - Prefer
pathSegment()over manual slashes. - Enable
ForwardedHeaderFilterfor proxy correctness. - Outside servlet context → use
UriComponentsBuilder.