JSP Lifecycle
Understand how a JSP page is translated into a servlet, compiled, and taken through jspInit, _jspService, and jspDestroy.
Introduction
In the previous lesson, you saw that Tomcat "automatically" turns a .jsp file into something runnable. This lesson pulls back the curtain on exactly what that means. Every JSP page goes through a well-defined lifecycle, and because JSP is built on Servlets (as covered in Lesson 2), that lifecycle closely mirrors the Servlet lifecycle you may already be familiar with.
The Big Picture
From the moment a .jsp file is first requested to the moment the application shuts down, it passes through five stages: translation, compilation, initialization, request servicing (which repeats for every request), and destruction.
- Translation - the .jsp file is converted into Java source code for a Servlet.
- Compilation - that generated Java source is compiled into a .class file.
- Initialization - jspInit() runs once, when the servlet is first loaded into memory.
- Request Servicing - _jspService() runs once per incoming request, for as long as the application is running.
- Destruction - jspDestroy() runs once, when the servlet is being unloaded (for example, on server shutdown).
Translation and compilation only happen the first time a JSP page is requested (or whenever the .jsp source file changes). Every subsequent request reuses the already-compiled servlet, which is why JSP performs well despite looking like it is "interpreted" each time.
Translation Phase
When hello.jsp is requested for the first time, the servlet container reads the file and generates an equivalent Java Servlet source file. Every piece of static HTML becomes an out.write("...") call, and every scriptlet or expression becomes actual embedded Java statements.
<html><body> <h1>Hello, <%= request.getParameter("name") %>!</h1></body></html>public class hello_jsp extends org.apache.jasper.runtime.HttpJspBase {
public void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { JspWriter out = response.getWriter();
out.write("<html>\n<body>\n <h1>Hello, "); out.print(request.getParameter("name")); out.write("!</h1>\n</body>\n</html>"); }}In Tomcat, this generated Java file (and the tool that produces it, called Jasper) is placed in Tomcat's work directory. You can actually open these generated .java files to see exactly how your JSP was translated - a great debugging technique when something behaves unexpectedly.
Compilation Phase
Immediately after translation, the generated .java file is compiled into a regular .class file, exactly as if you had written a Servlet by hand and compiled it with javac. This compiled class is then loaded into the JVM, and an instance of it is created - just like any other Servlet.
| Phase | Input | Output | When It Runs |
|---|---|---|---|
| Translation | .jsp file | .java file (generated servlet source) | First request, or whenever the .jsp file changes |
| Compilation | .java file | .class file | Immediately after translation |
jspInit()
Once the compiled servlet class is loaded, the container calls jspInit() exactly one time, before the servlet handles its first request. This is where you would put one-time setup logic, such as opening a resource that should be shared across all requests. Most JSP pages never override this method explicitly, since a default empty implementation is provided.
<%! private java.util.Date startupTime;
public void jspInit() { startupTime = new java.util.Date(); System.out.println("JSP page initialized at " + startupTime); }%>_jspService()
_jspService() is the heart of the JSP lifecycle. It runs once for every single incoming request, and it is where all of your scriptlets, expressions, and static HTML output actually execute, as shown in the translation example above. You never write or override this method yourself - the container generates it automatically from your JSP source, and it corresponds to a Servlet's doGet()/doPost() methods combined.
Unlike jspInit() and jspDestroy(), you cannot legally override _jspService() in a JSP page - the container generates and controls it. Attempting to declare a method with that exact name will cause a translation error.
jspDestroy()
jspDestroy() is called exactly once, when the container is about to remove the servlet from memory - typically during application shutdown or redeployment. This is the place to release any resources acquired in jspInit(), such as closing a file handle or a shared connection.
<%! public void jspDestroy() { System.out.println("JSP page is being destroyed."); }%>| Method | Called How Many Times | You Can Override? |
|---|---|---|
| jspInit() | Once, before the first request | Yes, via a <%! %> declaration |
| _jspService() | Once per request | No - generated automatically by the container |
| jspDestroy() | Once, at shutdown/unload | Yes, via a <%! %> declaration |
Common Mistakes
- Assuming a JSP page is re-translated and recompiled on every request - it is only redone when the source file changes.
- Trying to declare a method literally named _jspService() inside a JSP page.
- Forgetting that jspInit() and jspDestroy() each run only once, not once per request.
- Putting per-request logic inside jspInit() by mistake, expecting it to run again for each new visitor.
Best Practices
- Use jspInit() only for true one-time setup that should be shared across all requests.
- Use jspDestroy() to clean up anything opened in jspInit(), to avoid resource leaks.
- When debugging unexpected JSP output, check the generated .java file in Tomcat's work directory.
- Remember that heavy one-time initialization (like loading configuration) is often better placed in a Servlet's init() or an application listener rather than jspInit().
Frequently Asked Questions
No. Those steps only happen once, the first time a page is requested (or after the source file changes). Subsequent requests reuse the already-compiled servlet instance and only trigger _jspService().
In Tomcat, it is typically located under tomcat/work/Catalina/localhost/<appname>/org/apache/jsp/. Opening these files is a useful way to understand exactly how your JSP maps to Java.
No. JSP pages do not use doGet()/doPost() directly - that pattern belongs to Servlets. JSP's equivalent, generated automatically, is _jspService().
Typically, redeploying the application, stopping Tomcat, or the container reloading the web application (for example, after certain configuration changes).
Key Takeaways
- The JSP lifecycle consists of translation, compilation, initialization, request servicing, and destruction.
- Translation converts .jsp source into a generated Servlet .java file.
- Compilation turns that generated source into a runnable .class file.
- jspInit() runs once at startup, _jspService() runs on every request, jspDestroy() runs once at shutdown.
- Only jspInit() and jspDestroy() can be overridden by the developer; _jspService() is fully generated by the container.
Summary
Every JSP page follows the same predictable lifecycle: translated once, compiled once, initialized once, and then serviced repeatedly for each incoming request until it is eventually destroyed. Understanding this explains both JSP's good runtime performance and why changes to a .jsp file are picked up automatically. Next, you will look closely at the three original scripting elements - scriptlets, expressions, and declarations - that make up the Java side of a JSP page.