So answer is a bit late, but probably someone will find it useful.
You are right about ThreadLocal - if you generate hateoas links in different thread, then it fails with exception. I found some kind of workaround for this:
@RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
DeferredResult<ResponseEntity<ProductResource>> example(@PathVariable("id") final String productId, final HttpServletRequest request) {
final DeferredResult<ResponseEntity<ProductResource>> deferredResult = new DeferredResult<>();
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
final RequestAttributes requestAttributes = new ServletRequestAttributes(request);
productByIdQuery.byId(UUID.fromString(productId)).subscribe(productEntity -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
deferredResult.setResult(result, HttpStatus.OK))
}, deferredResult::setErrorResult);
return deferredResult;
}
So as you see, I save RequestAttributes so I can set them later in the callback. This solves just part of the problem - you'll get another exception because you'll loose contextPath attribute. To avoid this save it explicitly:
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
After those changes everything seems to work, but looks messy of course. I hope that somebody can provide more elegant solution.