Template engines
In addition to REST web services, you can also use Spring MVC to handle dynamic HTML content. Spring MVC supports various templating technologies, including Thymeleaf, FreeMarker, and JSP. In addition, many other templating engines provide their own integrations with Spring MVC.
Spring Boot provides autoconfiguration support for the following templating engines:
If one of these template engines is used with the default configuration, then templates are automatically selected from src/main/ resources/templates
.
Error Handling
By default, Spring Boot displays /error
that handles all errors appropriately, and it is logged as a "global" error page in the servlet container. For machine clients, it generates a JSON response with a detailed description of the error, an HTTP status code, and an exception message. For browser clients, there is a "whitelabel" error view that renders the same data in HTML (to customize this, add a View
that allows error
).
There are a number of server.error
properties that you can set if you want to customize the default error logic.
To completely replace the default logic, you can implement a ErrorController
and register a bean definition of that type, or add a bean of type ErrorAttributes
to use the existing mechanism but replace the content.
BasicErrorController
can be used as a base class for a custom
ErrorController
. This is especially practical if you need to add a handler for a new content type (the default handler only handles
text/html
and passes a fallback for everything else). To do this, extend
BasicErrorController
, add a public method with the
@RequestMapping
annotation that has the
produces
attribute, and create a bean of a new type.
In addition, you can define a class annotated with @ControllerAdvice
to customize the returned JSON document for a specific controller and/or exception type, as shown in the following example:
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
import javax.servlet.RequestDispatcher
import javax.servlet.http.HttpServletRequest
@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {
@ResponseBody
@ExceptionHandler(MyException::class)
fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
val status = getStatus(request)
return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
}
private fun getStatus(request: HttpServletRequest): HttpStatus {
val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
val status = HttpStatus.resolve(code)
return status ?: HttpStatus.INTERNAL_SERVER_ERROR
}
}
In the previous example, if MyException
is thrown by a controller defined in the same package as SomeController
, instead of the ErrorAttributes
view, the JSON representation of the POJO object MyErrorBody
is used .
In some cases, errors handled at the controller level are not captured by the metrics framework. Applications can ensure that such exceptions are recorded in request metrics by configuring the handled exception as a request attribute:
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
import org.springframework.boot.web.servlet.error.ErrorAttributes
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import javax.servlet.http.HttpServletRequest
@Controller
class MyController {
@ExceptionHandler(CustomException::class)
fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex)
return "errorView"
}
}
Custom error pages
If you want to display a custom HTML error page for a given status code, you can add the file to the /error
directory. Error pages can be either static HTML (that is, added to any of the static resource directories) or built using templates. The file name must be the exact status code or series mask.
For example, to display a 404
with a static HTML file, the directory structure should look like this:
src/ +- main/ +- java/ | + <source code> +- resources/ +- public/ +- error/ | +- 404.html +- <other public assets>
To display all 5xx
errors using the FreeMarker template, the directory structure should be as follows :
src/ +- main/ +- java/ | + <source code> +- resources/ +- templates/ +- error/ | +- 5xx.ftlh +- <other templates>
For more complex displays, you can also add beans that implement the ErrorViewResolver
interface, as shown in following example:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// Use the request or status to desire to return ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// Here we can add custom model values
new ModelAndView("myview");
}
return null;
}
}
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver
import org.springframework.http.HttpStatus
import org.springframework.web.servlet.ModelAndView
import javax.servlet.http.HttpServletRequest
class MyErrorViewResolver : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
model: Map<String, Any>): ModelAndView? {
// Use the request or status to desire to return ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// Here we can add custom model values
return ModelAndView("myview")
}
return null
}
}
You can also use regular Spring MVC features, such as methods marked with the @ExceptionHandler
annotation and the @ControllerAdvice
. ErrorController
then catches any unhandled exceptions.
Displaying error pages outside of Spring MVC
For applications not using Spring MVC, you can use the ErrorPageRegistrar
interface for direct registration of ErrorPages
. This abstraction works directly with the underlying built-in servlet container and works well even if you don't have DispatcherServlet
for Spring MVC.
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
import org.springframework.boot.web.server.ErrorPage
import org.springframework.boot.web.server.ErrorPageRegistrar
import org.springframework.boot.web.server.ErrorPageRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {
@Bean
fun errorPageRegistrar(): ErrorPageRegistrar {
return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
}
private fun registerErrorPages(registry: ErrorPageRegistry) {
registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
}
}
ErrorPage
using a path that is ultimately processed using a
Filter
( which is common in some non-Spring web frameworks such as Jersey and Wicket), the
Filter
must be explicitly registered as an
ERROR
manager, as shown in the following example:
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.EnumSet
import javax.servlet.DispatcherType
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun myFilter(): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(MyFilter())
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
return registration
}
}
Note that the FilterRegistrationBean
does not contain the ERROR
manager type by default.
Error handling during WAR deployment
When deployed to a servlet container, Spring Boot uses its error page filter to redirect a request with an error code to the appropriate error page. This is necessary because the servlet specification does not provide an API for logging error pages. Depending on the container you deploy your war file to and the technologies your application uses, additional configuration may be required.
The error page filter will only be able to redirect the request to the correct error page if the answer has not yet been recorded. By default, WebSphere Application Server 8.0 and later captures the response after the servlet service method completes successfully. You should disable this logic by setting the com.ibm.ws.webcontainer.invokeFlushAfterService
parameter to false
.
If you are using Spring Security and want to get accessing the principal on the error page, you need to configure the Spring Security filter to be called when errors are sent. To do this, set the spring.security.filter.dispatcher-types
property to async, error, forward, request
.
CORS support
Cross-Origin Resource Sharing (CORS) is a W3C specification implemented by most browsers, which allows flexibility in determining what types of cross-domain requests will be authorized, and is intended to replace some less secure and less efficient approaches , such as IFRAME or JSONP.
Since version 4.2, Spring MVC supports CORS. Using a controller method for CORS configuration with @CrossOrigin
annotations in a Spring Boot application does not require any special configuration. The global CORS configuration can only be defined by registering a WebMvcConfigurer
bean with the addCorsMappings(CorsRegistry)
method configured, as shown in the following example:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
GO TO FULL VERSION