Spring Boot and custom 404 error page - java

In my Spring Boot application, I'm trying to configure custom error pages, for example for 404, I have added a following Bean to my application configuration:
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
}
};
}
Also, I have created a following simple Thymeleaf template:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>404 Not Found</title>
<meta charset="utf-8" />
</head>
<body>
<h3>404 Not Found</h3>
<h1 th:text="${errorCode}">404</h1>
<p th:utext="${errorMessage}">Error java.lang.NullPointerException</p>
Back to Home Page
</body>
</html>
and added it into /resources/templates/ folder. Right now on the 404 error I can see only white screen.
What am I doing wrong and how to correctly setup my 404 page? Also, is it possible to use templates and not only static pages for custom error pages?

In Spring Boot 1.4.x you can add a custom error page:
If you want to display a custom HTML error page for a given status code, you add a file to an /error folder. Error pages can either be static HTML (i.e. added under any of the static resource folders) or built using templates. The name of the file should be the exact status code or a series mask.
For example, to map 404 to a static HTML file, your folder structure would look like this:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>

You're using Thymeleaf, And Thymeleaf can handle error without a controller.
For a generic error page this Thymeleaf page need to be named as error.html
and should be placed under src/main/resources > templates > error.html
For specific error pages, you need to create files named as the http error code in a folder named error, like: src/main/resources/templates/error/404.html.

new ErrorPage(HttpStatus.NOT_FOUND, "/404.html")
That /404.html represents the URL Path to Redirect, not the template name. Since, you insist to use a template, you should create a controller that handles the /404.html and renders your 404.html resides in src/main/resources/templates:
#Controller
public class NotFoundController {
#RequestMapping("/404.html")
public String render404(Model model) {
// Add model attributes
return "404";
}
}
You could also replace these just view render-er controllers with a View Controller:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/404.html").setViewName("404");
}
}
Also, is it possible to use templates and not only static pages for
custom error pages ?
Yes, it's possible. But Not Found pages are usually static and using a template instead of Plain Old HTMLs wouldn't make that much of a sense.

There is no need for the EmbeddedServletContainerCustomizer bean. Simply putting the corresponding error page (such as 404.html in case of 404) in the public directory should be enough (as pointed by #brunoid).
Also note that you can also put a generic error.html page that'll get displayed whenever your application encounters an error or exception.
A simple example (in Freemarker) -
<html lang="en">
<head>
</head>
<body>
<div class="container">
<div class="jumbotron alert-danger">
<h1>Oops. Something went wrong</h1>
<h2>${status} ${error}</h2>
</div>
</div>
</body>
</html>
This'll display proper error status and corresponding error message. And since you are using Spring Boot, you can always override the status and error messages that get displayed on your error page.

If you are using Thymeleaf like suggested in the question, you could use a template similar to the one from the previous reply, but appropriate for Thymeleaf rather than Freemarker. I have also added bootstrap for the styling:
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Error Page</title>
<link href="/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css" rel="stylesheet" media="screen"/>
<script src="/webjars/jquery/3.2.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="jumbotron alert-danger">
<h1>Oops... something went wrong.</h1>
<h2 th:text="${status + ' ' + error}"></h2>
</div>
</div>
</body>
</html>
You should place this code in a file called error.html and place it in your thymeleaf templates directory. There is no need to add an additional controller for it to work.

Check if you have thymeleaf present in the classpath, or add below code and reimport the project.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

We have to add new
#Controller
public class MyErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error404";
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error500";
}
}
return "error";
}
#Override
public String getErrorPath() {
return "/error";
}
}

enter image description hereIt's very easy to do in Spring Boot Application.
Spring Boot all most done all thing for us.
1)In Path src/main/resources/templates
make folder which has name 'error'
2)Next create html file in this folder,
save it as '404.html'.
You done evey thing here.
Spring Boot Automatically show this page if it got 404 page not found error.
Note: Before going to this, make sure that you added Thymeleaf repository in your pom. xml

Thymeleaf can handle error without a controller.
create error.html to resources folder.

Related

Spring MVC Error Page Using Mustache - Print Stacktrace

I am trying to design a common error page for Spring MVC using Mustache. I am able to print out the exception type (example: Internal Server Error) using variable "error" and the exception message using the variable called "message".
My Sample Mustache Template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Common Error</title>
<h2>Unexpected Error: </h2>
<h3>Error: {{error}}</h3>
<h3>Message: {{message}}</h3>
</head>
<body>
</body>
</html>
This shows up as:
Unexpected Error:
Error: Internal Server Error
Message: Unexpected Runtime Error Message
Question 1: How do I print out the Exception Stacktrace?
Question 2: Is there a way to print all the model variable available to the mustache template?
Rather than resolving this at the Mustache Template engine layer. I stepped back to the have Spring MVC setup the Model properly for the Mustache Error View.
I did this by using #ControllerAdvice and #ExceptionHandler.
My Controller Advice with catch-all exception handler looks like below:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
#ExceptionHandler(Throwable.class)
public ModelAndView genericExceptionHandler(Exception ex){
String errorMessage = getMessage(ex);
String errorRootCauseMessage = getRootCauseMessage(ex);
String errorStacktrace = getStackTrace(ex);
ModelAndView mv = new ModelAndView("errors/error");
Map<String, Object> model = mv.getModelMap();
model.put("errorMessage", errorMessage);
model.put("rootCauseErrorMessage", errorRootCauseMessage);
model.put("stackTrace", errorStacktrace);
return mv;
}
}
And then my Mustache Template is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Generic Error</title>
<h3>Wahooo!!! You broke us! Well done. We will take a look at it and try to fix the issue.</h3>
<h4>What Happend?</h4><h5> {{errorMessage}}<h5>
<h4>What was the root cause? </h4><h5>{{rootCauseErrorMessage}}</h5>
<h4>Stacktrace:</h4>
<h6>{{stackTrace}}</h6>
</head>
<body>
</body>
</html>
This solved my issue.

Thymeleaf not displaying values from spring controller

I am practising thymeleaf template engine for the first time. I have followed the tutorial and so on but I have no idea where I am going wrong.
My controller:
public String mainPage(Model model){
model.addAttribute("data", "Hello Thymeleaf");
return "main";
}
and my html is as follows:
<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>th:text="${data}"</h1>
</body>
</html>
When I hit localhost it displays th:text="${data}" instead of Hello Thymeleaf
<h1>"${data}"</h1>
doesn't work either. View resolver config must be correct as it resolves main to main.html. I am using spring4 SpringTemplateEngine and spring4 thymeleaf view resolver.
thanks in advance
You have to use th:text
<h1 th:text="${data}"></h1>
or if you don't want to use the th:text attribute, then you have to use th:inline="text" and make the thymeleaf render the context inside the tag. But make sure you put the variable inside [[ and ]]
<h1 th:inline="text">[[${data}]]</h1>
Thymeleaf isn't Velocity or Freemarker and doesn't replace expressions blindly. You need the expression in an appropriate tag attribute, such as
<h1 data-th-text="${data}" />
remove quotes to "${data}" and just use ${data}. I also agree with #Faraj Farook

how to use css in jsp pages

I am trying to use css file from my jsp pages but page does not see the css codes.
this is my .jsp file;
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Foundation | Welcome</title>
<link rel="stylesheet" href="/sources/css/foundation.css" />
<script src="/sources/js/vendor/modernizr.js"></script>
</head>
<body>
this is my configuratin file
#Configuration
#ComponentScan("com.sprhib")
#EnableWebMvc
public class BaseTestConfig {
#Bean
public UrlBasedViewResolver setupViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/sources/**").addResourceLocations("/sources/");
}
}
The webapp is not deployed as the root webapp. So it has a "context path". The path to access the index.html file which is at the root of your webapp, for example, is in fact something like
http://localhost:8080/myfirstwebapp/index.html
This is what the location bar of your browser contains, and /myfirstwebapp is the context path of your application.
So, if you page contains href="/sources/css/foundation.css", the browser will try to load the css file from
http://localhost:8080/sources/css/foundation.css
and not from
http://localhost:8080/myfirstwebapp/sources/css/foundation.css
You thus need to prepend the context path to all the absolute URLs in your webapp. Using the JSTL:
href="<c:url value='/sources/css/foundation.css'/>"
Without the JSTL:
href="${pageContext.request.contextPath}/sources/css/foundation.css"
Remove the / in /sources. This will work
Try to call those css directly through your web browser with full URL path.
It depends on what's your application path defined in your Tomcat or web server.
If it's mapped like http://yourdomain.com/ ( root ), it should be http://yourdomain.com/sources/css/foundation.css, but if it's mapped like http://yourdomain.com/test or something, URL for CSS should be http://yourdomain.com/test/sources/css/fundations.css.
You may use either relative pass for those, or introduce PATH variable so that link always created like http://yourdomain.com/PATH/sources/css/foundations.css in the source code.

Sending dynamically generated javascript file

Background:
I have a servlet in which I am dynamically generating javascript and putting into a variable script. Then I set the response content type as text/javascript and send the script over to the client:
resp.setContentType("text/javascript");
resp.getWriter().println(script);
Problem:
The browser does download the javascript file but it doesn't recognize the functions inside the file. If I create a static javascript file and use it instead, it works fine.
Question:
What should be done so that browser treats response from the servlet as a regular javascript file?
Thank you for help.
It should work fine. I suspect that you're just including it the wrong way or calling the function too early or that the response content is malformed.
I just did a quick test:
<!DOCTYPE html>
<html lang="en">
<head>
<title>SO question 6156155</title>
<script src="javaScriptServlet"></script>
<script>test()</script>
</head>
<body>
</body>
</html>
with
#WebServlet(urlPatterns={"/javaScriptServlet"})
public class JavaScriptServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/javascript");
response.getWriter().write("function test() { alert('peek-a-boo'); }");
}
}
and I get
How do you refer to this servlet from your browser ?
If you want to include this with a HTML page (existing one), you should refer to it from the tag of your page.
Ex.
<html>
<head>
<script type='text/javascript' src='URL_TO_YOUR_SERVLET'></script>
</head>
</html>
Or if you want it to be executed as part of a Ajax call, just pass the response to eval function.
Or else, if you just want to send the output and get it executed in browser, you need to send the HTML segment as well. Then include your JS with in the body tags, as a script tag.
ex. Your servlet sends the following, using content type 'text/html' :
<html>
<body>
<script type='text/javascript'>
<!-- write your generated JS here -->
</script>
</body>
</html>
You could always write the script 'in-line' to the web page.
I think this way is better.
<%# page language="java" contentType="text/javascript; charset=UTF-8" pageEncoding="UTF-8"%>
alert('Pure JavaScript right here!');
Set content type in JSP:
contentType="text/javascript; charset=UTF-8"

advice on freemarker templating, want to create a master template

I want to create a master template that every other view page will inherit.
So the master template will have:
HEADER
--CONTENT--
FOOTER
the header will optionally show (if the user is logged in), the username and other user object properties.
the --CONTENT-- is a placeholder that other 'inheriting' view pages will inject their content into.
So my questions are, is this possible with freemarker? If so, any guidance?
How would I pass the user object to the header from my controller actions? ideally the object would be passed in somewhere OTHER than each and every view page (to avoid having to maintain this code on each and every view page).
Yes, it's possible. In our applications things like the user object exist in session scope, but this could be any scope freemarker has access to:
<#if Session.the_user?? && Session.the_user.loggedIn>
<#-- header code -->
</#if>
You can omit the Session. and Freemarker will search the various scopes for the given variable name.
To inject the content, include this at the point in the master template where you'd like the view page to put its content:
<#nested>
The view pages then declare their use of the master template as follows:
<#import "/WEB-INF/ftl/path/to/template/master.ftl" as com>
<#com.template>
View page content
</#com.template>
I made Freemarker template inheritance - https://github.com/kwon37xi/freemarker-template-inheritance
I think it's what you want. It is tested on freemarker 2.3.19.
I implemented something like this:
base.ftl
<#macro page_head>
<title>Page title!</title>
</#macro>
<#macro page_body></#macro>
<#macro display_page>
<!DOCTYPE html>
<html lang="en">
<head>
<#page_head/>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<#page_body/>
</body>
</html>
</#macro>
then index.ftl will inherit the boilerplate templates as:
<#include "base.ftl">
<#macro page_head>
<title>Welcome studs!</title>
</#macro>
<#macro page_body>
<h1> Welcome user</h1>
</#macro>
<#display_page/>
this site was helpful for the above code reference
https://nickfun.github.io/posts/2014/freemarker-template-inheritance.html
In newer Freemarker versions, the <#nested> element is extremely useful:
base.ftl:
<#macro layout>
<html>
<body>
<p>OptaPlanner AI</p>
<#nested>
</body>
</html>
</#macro>
baseWithDownloadButton.ftl:
<#import "base.ftl" as base>
<#base.layout>
${content.body}<#-- Only applicable for jbake -->
<p>Download button</p>
</#base.layout>

Categories