Spring WebFlux provides view resolution and rendering capabilities for server-side template engines. This includes support for FreeMarker templates, script templates (JavaScript engines), and custom view implementations. The view rendering system integrates with the reactive programming model to render views asynchronously.
The ViewResolver interface resolves view names to View instances.
@FunctionalInterface
public interface ViewResolver {
// Resolve view name to View instance
Mono<View> resolveViewName(String viewName, Locale locale);
}Usage:
import org.springframework.web.reactive.result.view.ViewResolver;
import reactor.core.publisher.Mono;
import java.util.Locale;
public class CustomViewResolver implements ViewResolver {
@Override
public Mono<View> resolveViewName(String viewName, Locale locale) {
if (viewName.startsWith("custom:")) {
return Mono.just(new CustomView(viewName));
}
return Mono.empty();
}
}The View interface renders a model to the HTTP response.
public interface View {
// Default content type
default List<MediaType> getSupportedMediaTypes() {
return Collections.emptyList();
}
// Check if view supports given media type
default boolean isRedirectView() {
return false;
}
// Render the view
Mono<Void> render(Map<String, ?> model,
MediaType contentType,
ServerWebExchange exchange);
}The UrlBasedViewResolver resolves view names using a configured prefix and suffix.
public class UrlBasedViewResolver extends ViewResolverSupport implements ViewResolver, Ordered {
public UrlBasedViewResolver() { ... }
// Set view class
public void setViewClass(Class<?> viewClass) { ... }
// Get view class
public Class<?> getViewClass() { ... }
// Set prefix for view names
public void setPrefix(String prefix) { ... }
// Get prefix
public String getPrefix() { ... }
// Set suffix for view names
public void setSuffix(String suffix) { ... }
// Get suffix
public String getSuffix() { ... }
// Set view names this resolver can handle
public void setViewNames(String... viewNames) { ... }
// Get view names
public String[] getViewNames() { ... }
// Set whether to cache views
public void setCache(boolean cache) { ... }
// Check if caching enabled
public boolean isCache() { ... }
// Set cache limit
public void setCacheLimit(int cacheLimit) { ... }
// Get cache limit
public int getCacheLimit() { ... }
// Set order for resolver chain
public void setOrder(int order) { ... }
@Override
public int getOrder() { ... }
@Override
public Mono<View> resolveViewName(String viewName, Locale locale) { ... }
// Check if view name can be handled
protected boolean canHandle(String viewName, Locale locale) { ... }
// Create view instance
protected Mono<View> createView(String viewName, Locale locale) { ... }
// Load view
protected Mono<View> loadView(String viewName, Locale locale) { ... }
}Usage:
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerView;
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".ftl");
resolver.setViewClass(FreeMarkerView.class);
resolver.setViewNames("user*", "product*");
resolver.setCache(true);
resolver.setOrder(1);
// Resolves "userList" to "/WEB-INF/views/userList.ftl"The Rendering interface provides a public API for view rendering with model and status.
public interface Rendering {
// Get view name
String view();
// Get model attributes
Map<String, Object> modelAttributes();
// Get HTTP status
HttpStatusCode status();
// Get response headers
HttpHeaders headers();
// Create rendering with view name
static Builder view(String viewName) { ... }
// Create redirect rendering
static Builder redirectTo(String url) { ... }
static Builder redirectTo(URI url) { ... }
interface Builder {
// Set model attribute
Builder modelAttribute(String name, Object value);
// Set model attributes
Builder modelAttributes(Object... attributes);
Builder modelAttributes(Map<String, ?> attributes);
// Set HTTP status
Builder status(HttpStatusCode status);
Builder status(int status);
// Set header
Builder header(String headerName, String... headerValues);
// Set headers
Builder headers(Consumer<HttpHeaders> headersConsumer);
// Set cookie
Builder cookie(ResponseCookie cookie);
// Set cookies
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
// Build rendering
Rendering build();
}
}Usage:
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.http.HttpStatus;
import reactor.core.publisher.Mono;
@Controller
public class ViewController {
@GetMapping("/users")
public Mono<Rendering> listUsers() {
return userService.findAll()
.collectList()
.map(users -> Rendering.view("userList")
.modelAttribute("users", users)
.modelAttribute("timestamp", Instant.now())
.status(HttpStatus.OK)
.build());
}
@GetMapping("/redirect")
public Mono<Rendering> redirect() {
return Mono.just(Rendering.redirectTo("/home").build());
}
}The FragmentsRendering interface supports rendering multiple view fragments.
public interface FragmentsRendering extends Rendering {
// Get fragments
Collection<Fragment> fragments();
// Create fragments rendering
static Builder with(String viewName) { ... }
static Builder with(String viewName, Object... modelAttributes) { ... }
static Builder with(Fragment fragment) { ... }
static Builder with(Collection<Fragment> fragments) { ... }
interface Builder extends Rendering.Builder {
// Add fragment
Builder fragment(String viewName, Object... modelAttributes);
Builder fragment(Fragment fragment);
// Add fragments
Builder fragments(Fragment... fragments);
Builder fragments(Collection<Fragment> fragments);
@Override
FragmentsRendering build();
}
}The Fragment interface represents a view fragment with name and model.
public interface Fragment {
// Get view name
String viewName();
// Get model attributes
Map<String, Object> model();
// Create fragment
static Fragment create(String viewName) { ... }
static Fragment create(String viewName, Map<String, Object> model) { ... }
static Fragment create(String viewName, Object... modelAttributes) { ... }
}Usage:
import org.springframework.web.reactive.result.view.FragmentsRendering;
import org.springframework.web.reactive.result.view.Fragment;
@Controller
public class FragmentController {
@GetMapping("/dashboard")
public Mono<FragmentsRendering> dashboard() {
return Mono.just(
FragmentsRendering
.with("header", "title", "Dashboard")
.fragment("sidebar", "activeMenu", "dashboard")
.fragment("content", "data", getDashboardData())
.fragment("footer")
.build()
);
}
@GetMapping("/page")
public Mono<FragmentsRendering> pageWithFragments() {
Fragment header = Fragment.create("header", "title", "My Page");
Fragment content = Fragment.create("content", Map.of("items", getItems()));
Fragment footer = Fragment.create("footer");
return Mono.just(
FragmentsRendering.with(List.of(header, content, footer))
.status(HttpStatus.OK)
.build()
);
}
}The ViewResolutionResultHandler handles return values that require view resolution.
public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
implements HandlerResultHandler, Ordered {
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
RequestedContentTypeResolver contentTypeResolver) { ... }
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) { ... }
// Set default view names
public void setDefaultViews(List<View> defaultViews) { ... }
// Get default views
public List<View> getDefaultViews() { ... }
// Set default model attribute name
public void setDefaultModelName(String defaultModelName) { ... }
// Get default model attribute name
public String getDefaultModelName() { ... }
@Override
public boolean supports(HandlerResult result) { ... }
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { ... }
@Override
public int getOrder() { ... }
}Base class for View implementations.
public abstract class AbstractView implements View, BeanNameAware, ApplicationContextAware {
// Default content type
public static final MediaType DEFAULT_CONTENT_TYPE = MediaType.parseMediaType("text/html;charset=UTF-8");
// Set supported media types
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) { ... }
// Get supported media types
@Override
public List<MediaType> getSupportedMediaTypes() { ... }
// Set default charset
public void setDefaultCharset(Charset defaultCharset) { ... }
// Get default charset
public Charset getDefaultCharset() { ... }
// Set bean name
@Override
public void setBeanName(String beanName) { ... }
// Get bean name
public String getBeanName() { ... }
// Set application context
@Override
public void setApplicationContext(ApplicationContext applicationContext) { ... }
// Get application context
public ApplicationContext getApplicationContext() { ... }
// Set request context attribute name
public void setRequestContextAttribute(String requestContextAttribute) { ... }
// Get request context attribute name
public String getRequestContextAttribute() { ... }
@Override
public Mono<Void> render(Map<String, ?> model,
MediaType contentType,
ServerWebExchange exchange) { ... }
// Render internal (abstract)
protected abstract Mono<Void> renderInternal(Map<String, Object> renderAttributes,
MediaType contentType,
ServerWebExchange exchange);
// Prepare model for rendering
protected Mono<Map<String, Object>> getModelAttributes(Map<String, ?> model,
ServerWebExchange exchange) { ... }
}Base class for URL-based views.
public abstract class AbstractUrlBasedView extends AbstractView {
// Set URL
public void setUrl(String url) { ... }
// Get URL
public String getUrl() { ... }
// Check if URL available
public boolean checkResourceExists(Locale locale) throws Exception { ... }
}The RedirectView handles HTTP redirects.
public class RedirectView extends AbstractUrlBasedView {
public RedirectView() { ... }
public RedirectView(String redirectUrl) { ... }
// Set whether to use context-relative redirect
public void setContextRelative(boolean contextRelative) { ... }
// Check if context-relative
public boolean isContextRelative() { ... }
// Set whether to use HTTP 1.0 compatible redirect (302 instead of 303)
public void setHttp10Compatible(boolean http10Compatible) { ... }
// Check if HTTP 1.0 compatible
public boolean isHttp10Compatible() { ... }
// Set whether to propagate query properties
public void setPropagateQuery(boolean propagateQuery) { ... }
// Check if propagating query
public boolean isPropagateQuery() { ... }
// Set hosts that are allowed for redirects
public void setHosts(String... hosts) { ... }
// Get allowed hosts
public String[] getHosts() { ... }
@Override
public boolean isRedirectView() { ... }
@Override
protected Mono<Void> renderInternal(Map<String, Object> model,
MediaType contentType,
ServerWebExchange exchange) { ... }
// Create target URL
protected String createTargetUrl(Map<String, Object> model, ServerWebExchange exchange) { ... }
}Usage:
import org.springframework.web.reactive.result.view.RedirectView;
import org.springframework.http.HttpStatus;
@Configuration
public class ViewConfig {
@Bean
public RedirectView homeRedirect() {
RedirectView view = new RedirectView("/home");
view.setContextRelative(true);
view.setHttp10Compatible(false);
view.setPropagateQuery(true);
return view;
}
}
// In controller
@GetMapping("/old-path")
public RedirectView oldPath() {
return new RedirectView("/new-path");
}The HttpMessageWriterView writes response using an HttpMessageWriter.
public class HttpMessageWriterView implements View {
public HttpMessageWriterView(HttpMessageWriter<?> writer) { ... }
public HttpMessageWriterView(HttpMessageWriter<?> writer, MediaType contentType) { ... }
@Override
public List<MediaType> getSupportedMediaTypes() { ... }
@Override
public Mono<Void> render(Map<String, ?> model,
MediaType contentType,
ServerWebExchange exchange) { ... }
// Set model keys to extract value from
public void setModelKeys(Set<String> modelKeys) { ... }
}The RequestContext holds request-specific state for view rendering.
public class RequestContext {
public RequestContext(ServerWebExchange exchange,
Map<String, Object> model,
MessageSource messageSource) { ... }
// Get server web exchange
public ServerWebExchange getExchange() { ... }
// Get model
public Map<String, Object> getModel() { ... }
// Get message source
public MessageSource getMessageSource() { ... }
// Get message
public String getMessage(String code) { ... }
public String getMessage(String code, String defaultMessage) { ... }
public String getMessage(String code, Object[] args) { ... }
public String getMessage(String code, Object[] args, String defaultMessage) { ... }
public String getMessage(MessageSourceResolvable resolvable) { ... }
// Get locale
public Locale getLocale() { ... }
// Get context path
public String getContextPath() { ... }
// Get request URI
public String getRequestUri() { ... }
// Get query string
public String getQueryString() { ... }
// Get URL for path
public String getContextUrl(String relativeUrl) { ... }
public String getContextUrl(String relativeUrl, Map<String, ?> params) { ... }
}The BindStatus holds status information for data binding in views.
public class BindStatus {
public BindStatus(RequestContext requestContext,
String path,
boolean htmlEscape) { ... }
// Get expression (path)
public String getExpression() { ... }
// Get value
public Object getValue() { ... }
// Get display value
public String getDisplayValue() { ... }
// Check if error
public boolean isError() { ... }
// Get error codes
public String[] getErrorCodes() { ... }
// Get error messages
public String[] getErrorMessages() { ... }
// Get error message
public String getErrorMessage() { ... }
// Get errors as single string
public String getErrorMessagesAsString(String delimiter) { ... }
}The RequestDataValueProcessor interface processes data values in forms and URLs.
public interface RequestDataValueProcessor {
// Process action URL
String processAction(ServerWebExchange exchange,
String action,
String httpMethod);
// Process form field value
String processFormFieldValue(ServerWebExchange exchange,
String name,
String value,
String type);
// Get extra hidden fields
Map<String, String> getExtraHiddenFields(ServerWebExchange exchange);
// Process URL
String processUrl(ServerWebExchange exchange, String url);
}public interface FreeMarkerConfig {
// Get FreeMarker configuration
freemarker.template.Configuration getConfiguration();
}public class FreeMarkerConfigurer implements FreeMarkerConfig, InitializingBean, ResourceLoaderAware {
public FreeMarkerConfigurer() { ... }
// Set template loader paths
public void setTemplateLoaderPaths(String... templateLoaderPaths) { ... }
// Set template loader path
public void setTemplateLoaderPath(String templateLoaderPath) { ... }
// Set prefer file system access
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) { ... }
// Set default encoding
public void setDefaultEncoding(String defaultEncoding) { ... }
// Set configuration
public void setConfiguration(freemarker.template.Configuration configuration) { ... }
// Set FreeMarker settings
public void setFreemarkerSettings(Properties settings) { ... }
// Set FreeMarker variables
public void setFreemarkerVariables(Map<String, Object> variables) { ... }
@Override
public freemarker.template.Configuration getConfiguration() { ... }
}public class FreeMarkerView extends AbstractUrlBasedView {
public FreeMarkerView() { ... }
// Set encoding
public void setEncoding(String encoding) { ... }
// Get encoding
public String getEncoding() { ... }
// Check template exists
@Override
public boolean checkResourceExists(Locale locale) throws Exception { ... }
@Override
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,
MediaType contentType,
ServerWebExchange exchange) { ... }
}public class FreeMarkerViewResolver extends UrlBasedViewResolver {
public FreeMarkerViewResolver() { ... }
public FreeMarkerViewResolver(String prefix, String suffix) { ... }
// Set cache
public void setCache(boolean cache) { ... }
}Usage:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
@Configuration
public class FreeMarkerConfig {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates/");
configurer.setDefaultEncoding("UTF-8");
return configurer;
}
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setCache(true);
return resolver;
}
}public interface ScriptTemplateConfig {
// Get script engine
ScriptEngine getEngine();
// Get engine supplier
Supplier<ScriptEngine> getEngineSupplier();
// Get engine name
String getEngineName();
// Check if shared engine
Boolean isSharedEngine();
// Get scripts
String[] getScripts();
// Get render object
String getRenderObject();
// Get render function
String getRenderFunction();
// Get content type
String getContentType();
// Get charset
Charset getCharset();
// Get resource loader path
String getResourceLoaderPath();
}public class ScriptTemplateConfigurer implements ScriptTemplateConfig, InitializingBean {
public ScriptTemplateConfigurer() { ... }
public ScriptTemplateConfigurer(String engineName) { ... }
// Set script engine
public void setEngine(ScriptEngine engine) { ... }
// Set engine supplier
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) { ... }
// Set engine name
public void setEngineName(String engineName) { ... }
// Set whether to use shared engine
public void setSharedEngine(Boolean sharedEngine) { ... }
// Set scripts to load
public void setScripts(String... scripts) { ... }
// Set render object
public void setRenderObject(String renderObject) { ... }
// Set render function
public void setRenderFunction(String renderFunction) { ... }
// Set content type
public void setContentType(String contentType) { ... }
// Set charset
public void setCharset(Charset charset) { ... }
// Set resource loader path
public void setResourceLoaderPath(String resourceLoaderPath) { ... }
@Override
public ScriptEngine getEngine() { ... }
// Additional getters...
}public class ScriptTemplateView extends AbstractUrlBasedView {
public static final String DEFAULT_CONTENT_TYPE = "text/html";
public static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";
public ScriptTemplateView() { ... }
public ScriptTemplateView(String url) { ... }
// Set engine
public void setEngine(ScriptEngine engine) { ... }
// Set engine supplier
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) { ... }
// Set engine name
public void setEngineName(String engineName) { ... }
// Set shared engine
public void setSharedEngine(Boolean sharedEngine) { ... }
// Set scripts
public void setScripts(String... scripts) { ... }
// Set render object
public void setRenderObject(String renderObject) { ... }
// Set render function
public void setRenderFunction(String renderFunction) { ... }
// Set resource loader path
public void setResourceLoaderPath(String resourceLoaderPath) { ... }
@Override
protected Mono<Void> renderInternal(Map<String, Object> model,
MediaType contentType,
ServerWebExchange exchange) { ... }
}public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
public ScriptTemplateViewResolver() { ... }
public ScriptTemplateViewResolver(String prefix, String suffix) { ... }
}Usage:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.result.view.script.ScriptTemplateConfigurer;
import org.springframework.web.reactive.result.view.script.ScriptTemplateViewResolver;
@Configuration
public class ScriptTemplateConfig {
@Bean
public ScriptTemplateConfigurer scriptTemplateConfigurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("scripts/polyfill.js", "scripts/render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
@Bean
public ScriptTemplateViewResolver scriptTemplateViewResolver() {
ScriptTemplateViewResolver resolver = new ScriptTemplateViewResolver();
resolver.setPrefix("templates/");
resolver.setSuffix(".html");
return resolver;
}
}public interface RenderingContext {
// Get application context
ApplicationContext getApplicationContext();
// Get locale
Locale getLocale();
// Get URL for path
String getUrl(String path);
}Base class for ViewResolver implementations.
public abstract class ViewResolverSupport {
// Set default content type
public void setDefaultContentType(MediaType contentType) { ... }
// Get default content type
public MediaType getDefaultContentType() { ... }
// Set supported media types
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) { ... }
// Get supported media types
public List<MediaType> getSupportedMediaTypes() { ... }
}