If you came here like me looking for a solution that works for dynamic routes as a fallback solution for SPA use cases, there is an open spring issue here
The following WebExceptionHandler solved it for me
@Component
@Order(-2)
public class HtmlRequestNotFoundHandler implements WebExceptionHandler {
private final DispatcherHandler dispatcherHandler;
private final RequestPredicate PREDICATE = RequestPredicates.accept(MediaType.TEXT_HTML);
public HtmlRequestNotFoundHandler(DispatcherHandler dispatcherHandler) {
this.dispatcherHandler = dispatcherHandler;
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
if (
isNotFoundAndShouldBeForwarded(exchange, throwable)
) {
var forwardRequest = exchange.mutate().request(it -> it.path("/index.html"));
return dispatcherHandler.handle(forwardRequest.build());
}
return Mono.error(throwable);
}
private boolean isNotFoundAndShouldBeForwarded(ServerWebExchange exchange, Throwable throwable) {
if (throwable instanceof ResponseStatusException
&& ((ResponseStatusException) throwable).getStatusCode() == HttpStatus.NOT_FOUND
) {
var serverRequest = ServerRequest.create(exchange, Collections.emptyList());
return PREDICATE.test(serverRequest);
}
return false;
}
}