Vishal Anand Vishal Anand - 5 months ago 12
CSS Question

Adding external resources (CSS/JavaScript/images etc) in JSP

I added an external CSS stylesheet to my project and placed in the WEB-CONTENTS folder of my project in Eclipse. When I deployed it on the Tomcat, the stylesheet was not applied. When I debugged it in Chrome and opened it, it gave me

404 file not found
error. Why is that and how to fix it?

Here is the code:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>joined now </title>

<link href="globalCSS.css" rel="stylesheet" type="text/css"/>

</head>
<body>
<div>this is at the top</div>
<c:import url="header.jsp" />
<c:import url="navigationBar.jsp" />
<c:import url="leftpane.jsp" />
<c:import url="mainContent.jsp" />
<c:import url="rightpane.jsp" />
<c:import url="footer.jsp" />
</body>
</html>

Answer

The reason that you get the 404 File Not Found error, is that your path to CSS given as a value to the href attribute is missing context path.

An HTTP request URL contains the following parts:

http://[host]:[port][request-path]?[query-string]

The request path is further composed of the following elements:

  • Context path: A concatenation of a forward slash (/) with the context root of the servlet's web application. Example: http://host[:port]/context-root[/url-pattern]

  • Servlet path: The path section that corresponds to the component alias that activated this request. This path starts with a forward slash (/).

  • Path info: The part of the request path that is not part of the context path or the servlet path.

Read more here.


Solutions

There are several solutions to your problem, here are some of them:

1) Using <c:url> tag from JSTL

In my Java web applications I usually used <c:url> tag from JSTL when defining the path to CSS/JavaScript/image and other static resources. By doing so you can be sure that those resources are referenced always relative to the application context (context path).

If you say, that your CSS is located inside WebContent folder, then this should work:

<link type="text/css" rel="stylesheet" href="<c:url value="/globalCSS.css" />" />

The reason why it works is explained in the "JavaServer Pages™ Standard Tag Library" version 1.2 specification chapter 7.5 (emphasis mine):

7.5 <c:url>
Builds a URL with the proper rewriting rules applied.
...
The URL must be either an absolute URL starting with a scheme (e.g. "http:// server/context/page.jsp") or a relative URL as defined by JSP 1.2 in JSP.2.2.1 "Relative URL Specification". As a consequence, an implementation must prepend the context path to a URL that starts with a slash (e.g. "/page2.jsp") so that such URLs can be properly interpreted by a client browser.

NOTE
Don't forget to use Taglib directive in your JSP to be able to reference JSTL tags. Also see an example JSP page here.


2) Using JSP Expression Language and implicit objects

An alternative solution is using Expression Language (EL) to add application context:

<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/globalCSS.css" />

Here we have retrieved the context path from the request object. And to access the request object we have used the pageContext implicit object.


3) Using <c:set> tag from JSTL

DISCLAIMER
The idea of this solution was taken from here.

To make accessing the context path more compact than in the solution №2, you can first use the JSTL <c:set> tag, that sets the value of an EL variable or the property of an EL variable in any of the JSP scopes (page, request, session, or application) for later access.

<c:set var="root" value="${pageContext.request.contextPath}"/>
...
<link type="text/css" rel="stylesheet" href="${root}/globalCSS.css" />

IMPORTANT NOTE
By default, in order to set the variable in such manner, the JSP that contains this set tag must be accessed at least once (including in case of setting the value in the application scope using scope attribute, like <c:set var="foo" value="bar" scope="application" />), before using this new variable. For instance, you can have several JSP files where you need this variable. So you must ether a) both set the new variable holding context path in the application scope AND access this JSP first, before using this variable in other JSP files, or b) set this context path holding variable in EVERY JSP file, where you need to access to it.


4) Using ServletContextListener

The more effective way to make accessing the context path more compact is to set a variable that will hold the context path and store it in the application scope using a Listener. This solution is similar to solution №3, but the benefit is that now the variable holding context path is set right at the start of the web application and is available application wide, no need for additional steps.

We need a class that implements ServletContextListener interface. Here is an example of such class:

package com.example.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class AppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext sc = event.getServletContext();
        sc.setAttribute("ctx", sc.getContextPath());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {}

}

Now in a JSP we can access this global variable using EL:

<link type="text/css" rel="stylesheet" href="${ctx}/globalCSS.css" />

NOTE
@WebListener annotation is available since Servlet version 3.0. If you use a servlet container or application server that supports older Servlet specifications, remove the @WebServlet annotation and instead configure the listener in the deployment descriptor (web.xml). Here is an example of web.xml file for the container that supports maximum Servlet version 2.5 (other configurations are omitted for the sake of brevity):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    ...  
    <listener>
        <listener-class>com.example.listener.AppContextListener</listener-class>
    </listener>
    ...
</webapp>


5) Using scriptlets

As suggested by user @gavenkoa you can also use scriptlets like this:

<%= request.getContextPath() %>

For such a small thing it is probably OK, just note that generally the use of scriptlets in JSP is discouraged.


Conclusion

I personally prefer ether the first solution (used it in my previous projects most of the time) or the second, as they are most clear, intuitive and unambiguous (IMHO). But you choose whatever suits you most.


Other thoughts

You can deploy your web app as the default application (i.e. in the default root context), so it can be accessed without specifying context path. For more info read the "Update" section here.

Comments