Nearby lessons
18 of 30JSP - Custom Tags (Classic Model)
- Understand the 3 categories of custom tags (Classic, Simple, Tag Files)
- Learn the Tag Extension API interface hierarchy
- Implement Tag, IterationTag, and BodyTag interfaces
- Use TagSupport and BodyTagSupport convenience classes
- Build nested/co-operative tags
- Access JSP implicit objects from tag handlers
Custom Tags let you create your own JSP tags when standard actions, EL, and JSTL don't meet your needs. This lesson covers the Classic Tag Model (JSP 1.1): the Tag Extension API hierarchy, implementing Tag/IterationTag/BodyTag interfaces, TagSupport and BodyTagSupport classes, nested tags, and accessing JSP implicit objects from tag handlers.
What are Custom Tags?
Standard actions, EL, and JSTL cannot cover every requirement. For example, there are no standard tags for displaying sports information or movie data. Custom tags let you define your own tags to meet any requirement, available since JSP 1.1.
All custom tags are divided into 3 categories:
- Classic Tag Model (JSP 1.1) — uses Tag, IterationTag, BodyTag interfaces
- Simple Tag Model (JSP 2.0) — uses SimpleTag interface
- Tag Files (JSP 2.0) — JSP-like files saved with .tag extension
Components of a Custom Tag Application
Every custom tag application requires 3 components:
| Component | Description |
|---|---|
| 1. Tag Handler Class | A Java class that defines the tag's functionality. Must implement Tag interface (directly or indirectly). Must have a public no-arg constructor (web container creates the instance). |
| 2. TLD File | Tag Library Descriptor — an XML file that maps the custom tag name to its tag handler class. |
| 3. taglib Directive | Makes the custom tag available to JSP pages by specifying the TLD file location. |
Execution Flow:
- JSP engine encounters a custom tag → identifies the prefix
- Finds the matching
taglibdirective - From the directive, locates the TLD file
- From the TLD, identifies the Tag Handler class
- Executes the tag handler to provide functionality
Tag Extension API — Interface Hierarchy
All custom tag components live in the javax.servlet.jsp.tagext package.
Classic Tag Model hierarchy:
JspTag (I)
├── Tag (I)
│ ├── IterationTag (I)
│ │ └── BodyTag (I)
│ └── TagSupport (AC) → implements IterationTag
│ └── BodyTagSupport (AC) → implements BodyTag
└── SimpleTag (I)
└── SimpleTagSupport (AC)
| Type | Interface/Class | Purpose |
|---|---|---|
| Interface | Tag | Base interface for all tag handlers. Defines 6 methods. Use when no body manipulation or iteration is needed. |
| Interface | IterationTag | Child of Tag. Adds doAfterBody() for processing tag body multiple times. |
| Interface | BodyTag | Child of IterationTag. Adds setBodyContent() and doInitBody() for manipulating tag body content. |
| Abstract Class | TagSupport | Implements IterationTag with defaults. Extend this for simple/iteration tags — override only needed methods. |
| Abstract Class | BodyTagSupport | Extends TagSupport, implements BodyTag. Extend this for tags that process body content. |
| Abstract Class | BodyContent | Buffer that holds tag body content. Extends JspWriter. Used only with BodyTag/BodyTagSupport. |
JspTag is a marker interface for polymorphism — it contains no methods.
Tag Interface — Methods and Constants
The Tag interface defines 6 methods:
| Method | Description |
|---|---|
setPageContext(PageContext p) | Called by JSP engine to provide the PageContext object |
setParent(Tag t) | Called to set the parent tag (for nested tags) |
getParent() | Returns the parent tag, or null |
doStartTag() | Called when start tag is encountered. Contains the main tag logic. |
doEndTag() | Called when end tag is encountered |
release() | Cleans up the tag handler when no longer needed |
4 constants:
| Constant | Used By | Meaning |
|---|---|---|
EVAL_BODY_INCLUDE | doStartTag() | Tag body is evaluated and included in the output |
SKIP_BODY | doStartTag() | Tag body is skipped |
EVAL_PAGE | doEndTag() | Rest of the JSP page is executed normally |
SKIP_PAGE | doEndTag() | Rest of the JSP page is NOT executed |
Tag Handler Lifecycle
When JSP engine encounters a custom tag, the lifecycle follows these steps:
- Identify — JSP engine finds the tag handler class via taglib directive and TLD
- Create — Web container creates a TagHandler instance (public no-arg constructor)
- setPageContext() — Makes PageContext available to the handler
- setParent() — Sets parent tag object (for nested tags)
- Set Attributes — JSP engine calls setter methods for each attribute
- doStartTag() — Returns
EVAL_BODY_INCLUDE(include body) orSKIP_BODY - doEndTag() — Returns
EVAL_PAGE(continue JSP) orSKIP_PAGE(stop JSP) - release() — Cleanup when handler is no longer needed
Mapping taglib Directive to TLD
3 ways to map a taglib directive to a TLD file:
Way 1 — Hardcoded TLD location:
<%@ taglib prefix="mine" uri="/WEB-INF/MyTld.tld" %>
Way 2 — Via web.xml:
<web-app>
<jsp-config>
<taglib>
<taglib-location>/WEB-INF/MyTld.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
Then in the JSP, use a custom URI:
<%@ taglib prefix="mine" uri="www.durgajobs.com" %>
Way 3 — Via TLD's uri element:
<taglib version="2.1">
<tlib-version>1.2</tlib-version>
<uri>www.durgajobs.com</uri>
<tag>
<name>mytag</name>
<tag-class>tags.MyCustomTag</tag-class>
</tag>
</taglib>
TLD File Structure
The TLD file maps tag names to handler classes and declares attributes. Key elements:
<taglib version="2.1">
<tlib-version>1.2</tlib-version>
<uri>www.durgajobs.com</uri>
<tag>
<description>Custom tag for weather report</description>
<name>mytag</name>
<tag-class>tags.MyCustomTag</tag-class>
<body-content>jsp</body-content>
<attribute>
<name>color</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
body-content values:
| Value | Description |
|---|---|
empty | No tag body allowed. Use <mine:mytag/> |
tagdependent | Body is treated as plain text (no JSP processing) |
scriptless | No scripting elements, but standard actions and EL are allowed |
jsp | No restrictions on body content (default) |
Attribute sub-elements:
| Element | Description |
|---|---|
<name> | Name of the attribute |
<required> | true = mandatory, false = optional (default: false) |
<rtexprvalue> | true = runtime expressions allowed, false = literals only (default: false) |
Demo — Basic Custom Tag
Project structure:
cust1/
├── test.jsp
├── WEB-INF/
│ ├── MyTld.tld
│ └── classes/tags/
│ └── MyCustomTag.class
test.jsp:
<%@ taglib prefix="mine" uri="/WEB-INF/MyTld.tld" %>
<h1>Hello this is Demo JSP</h1>
<mine:mytag>
<h1>This is body of the custom tag</h1>
</mine:mytag>
<h1>This is after the custom tag invocation</h1>
MyCustomTag.java:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
public class MyCustomTag implements Tag {
private PageContext pageContext;
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag t) { }
public Tag getParent() { return null; }
public int doStartTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
out.println("<h1>Hello this is from tag handler</h1>");
} catch(IOException e) {}
return EVAL_BODY_INCLUDE;
}
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
public void release() { }
}
Output:
Hello this is Demo JSP
Hello this is from tag handler
This is body of the custom tag
This is after the custom tag invocation
Demo — Empty Tag with Mandatory Attribute
This tag takes a number attribute and prints its double.
test.jsp:
<%@ taglib prefix="mine" uri="/WEB-INF/MyTld.tld" %>
<mine:mytag number="5" />
<mine:mytag number="${param.num}" />
MyCustomTag.java:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag implements Tag {
private int number;
private PageContext pageContext;
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag t) { }
public void setNumber(int number) { this.number = number; }
public int doStartTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
out.println("<h1>Double of " + number + " is: " + (2 * number) + "</h1>");
} catch(java.io.IOException e) {}
return SKIP_BODY;
}
public int doEndTag() throws JspException { return EVAL_PAGE; }
public void release() { }
public Tag getParent() { return null; }
}
Demo — Empty Tag with Optional Attribute
This tag takes an optional name attribute for a greeting.
test.jsp:
<%@ taglib prefix="mine1" uri="/WEB-INF/MyTld.tld" %>
<mine1:greeting name="Durga" />
<mine1:greeting />
MyCustomTag.java:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag implements Tag {
private PageContext pageContext;
private String name;
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag t) { }
public void setName(String name) { this.name = name; }
public int doStartTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
if (name == null) {
out.println("<h1>Hello Guest..Good Morning...</h1>");
} else {
out.println("<h1>Good Morning..." + name + "</h1>");
}
} catch(java.io.IOException e) {}
return SKIP_BODY;
}
public int doEndTag() throws JspException { return EVAL_PAGE; }
public void release() { }
public Tag getParent() { return null; }
}
IterationTag — Processing Body Multiple Times
IterationTag is the child interface of Tag. Use it when you want to include the tag body multiple times.
It adds one extra method and one extra constant:
doAfterBody()— called after each body evaluationEVAL_BODY_AGAIN— return this to re-evaluate the body
Lifecycle:
doStartTag()→ returnsEVAL_BODY_INCLUDEorSKIP_BODY- Tag body is evaluated
doAfterBody()→ returnsEVAL_BODY_AGAIN(re-evaluate body) orSKIP_BODY(proceed to doEndTag)doEndTag()→ returnsEVAL_PAGEorSKIP_PAGE
Demo — Loop tag:
<mine:loop count="6">
<h1>Learning custom tags is very easy...</h1>
</mine:loop>
MyCustomTag.java (implementing IterationTag):
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag implements IterationTag {
private int count;
public void setCount(int count) { this.count = count; }
public void setPageContext(PageContext p) { }
public void setParent(Tag t) { }
public int doStartTag() throws JspException {
if (count > 0) return EVAL_BODY_INCLUDE;
else return SKIP_BODY;
}
public int doAfterBody() throws JspException {
if (--count > 0) return EVAL_BODY_AGAIN;
else return SKIP_BODY;
}
public int doEndTag() throws JspException { return EVAL_PAGE; }
public void release() { }
public Tag getParent() { return null; }
}
TagSupport — Convenience Class
When implementing Tag or IterationTag directly, you must provide implementations for all 6+ methods even though you usually only need doStartTag(), doAfterBody(), and doEndTag().
TagSupport implements IterationTag and provides default implementations for all methods. You only override what you need.
Internal implementation of TagSupport:
public class TagSupport implements IterationTag, Serializable {
protected transient PageContext pageContext;
private Tag parent;
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag t) { parent = t; }
public int doStartTag() throws JspException { return SKIP_BODY; }
public int doAfterBody() throws JspException { return SKIP_BODY; }
public int doEndTag() throws JspException { return EVAL_PAGE; }
public Tag getParent() { return parent; }
public void release() { pageContext = null; parent = null; }
}
Key benefits of extending TagSupport:
pageContextis available directly — no need to declare or implement setter- Default return values:
doStartTag()→ SKIP_BODY,doAfterBody()→ SKIP_BODY,doEndTag()→ EVAL_PAGE - Only override the methods you need
Demo — Loop tag using TagSupport:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag extends TagSupport {
private int count;
public void setCount(int count) { this.count = count; }
public int doStartTag() throws JspException {
if (count > 0) return EVAL_BODY_INCLUDE;
else return SKIP_BODY;
}
public int doAfterBody() throws JspException {
if (--count > 0) return EVAL_BODY_AGAIN;
else return SKIP_BODY;
}
}
BodyTag — Manipulating Tag Body
BodyTag is the child interface of IterationTag. Use it when you need to read and manipulate the tag body content.
It adds 2 extra methods:
public void setBodyContent(BodyContent b)
public void doInitBody() throws JspException
And 1 extra constant:
EVAL_BODY_BUFFERED— body is buffered in a BodyContent object (for manipulation)
When doStartTag() returns EVAL_BODY_BUFFERED:
- JSP engine creates a
BodyContentobject - Calls
setBodyContent()to provide it - Calls
doInitBody()for initialization - Body is evaluated and stored in the buffer
doAfterBody()is called — manipulate the buffered body here
Note: setBodyContent() and doInitBody() are NOT called if doStartTag() returns EVAL_BODY_INCLUDE or SKIP_BODY, or if the tag has no body.
BodyContent methods:
| Method | Description |
|---|---|
getString() | Returns the tag body as a String |
getReader() | Returns a Reader to read the tag body |
getEnclosingWriter() | Returns the parent JspWriter (or current JspWriter if no parent) |
clearBody() | Clears the body content buffer |
BodyTagSupport — Convenience Class
BodyTagSupport extends TagSupport and implements BodyTag. It provides default implementations for all 9 methods.
Internal implementation:
public class BodyTagSupport extends TagSupport implements BodyTag {
protected transient BodyContent bodyContent;
public int doStartTag() throws JspException { return EVAL_BODY_BUFFERED; }
public void setBodyContent(BodyContent b) { bodyContent = b; }
public void doInitBody() throws JspException { }
public int doAfterBody() throws JspException { return SKIP_BODY; }
public BodyContent getBodyContent() { return bodyContent; }
}
Demo — Convert body to lowercase:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag extends BodyTagSupport {
public int doAfterBody() throws JspException {
try {
String s = bodyContent.getString();
s = s.toLowerCase();
JspWriter out = bodyContent.getEnclosingWriter();
out.println(s);
} catch(Exception e) {}
return SKIP_BODY;
}
}
TagSupport vs BodyTagSupport
| Method | TagSupport | BodyTagSupport |
|---|---|---|
| doStartTag() | ||
| Possible Returns | EVAL_BODY_INCLUDE, SKIP_BODY | EVAL_BODY_INCLUDE, SKIP_BODY, EVAL_BODY_BUFFERED |
| Default | SKIP_BODY | EVAL_BODY_BUFFERED |
| doAfterBody() | ||
| Possible Returns | EVAL_BODY_AGAIN, SKIP_BODY | EVAL_BODY_AGAIN, SKIP_BODY |
| Default | SKIP_BODY | SKIP_BODY |
| doEndTag() | ||
| Possible Returns | EVAL_PAGE, SKIP_PAGE | EVAL_PAGE, SKIP_PAGE |
| Default | EVAL_PAGE | EVAL_PAGE |
| setBodyContent() | Not applicable | Called when doStartTag() returns EVAL_BODY_BUFFERED |
| doInitBody() | Not applicable | Called after setBodyContent() |
Nested (Co-operative) Tags
Sometimes a group of tags work together to perform functionality. These are called co-operative or nested tags.
Example: In JSTL, <c:choose>, <c:when>, and <c:otherwise> work together to implement a switch statement.
Getting parent tags:
Tag parent = getParent();
Getting an arbitrary ancestor:
TagSupport.findAncestorWithClass(Tag t, Class c)
Demo — Nested tag with level detection:
<mine:mytag>
<mine:mytag>
<mine:mytag>
<mine:mytag />
</mine:mytag>
</mine:mytag>
</mine:mytag>
MyCustomTag.java:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag extends TagSupport {
public int doStartTag() throws JspException {
int level = 0;
Tag t = getParent();
while (t != null) {
level++;
t = t.getParent();
}
try {
JspWriter out = pageContext.getOut();
out.println("<h1>Nested level is: " + level + "</h1>");
} catch(java.io.IOException e) {}
return EVAL_BODY_INCLUDE;
}
}
Demo — Menu/Menuitem Nested Tags
A practical nested tag example: a parent <mine:menu> collects items from child <mine:menuitem> tags.
test.jsp:
<%@ taglib prefix="mine" uri="/WEB-INF/MyTld.tld" %>
<mine:menu>
<mine:menuitem item="chicken65" />
<mine:menuitem item="Mutton" />
<mine:menuitem item="Fish" />
</mine:menu>
MenuTag.java (parent):
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.util.*;
public class MenuTag extends TagSupport {
private ArrayList l = null;
public int doStartTag() throws JspException {
l = new ArrayList();
return EVAL_BODY_INCLUDE;
}
public void addMenuItem(String s) { l.add(s); }
public int doEndTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
out.println("<h1><br>Menu Items are: " + l + "</h1>");
} catch(Exception e) {}
return EVAL_PAGE;
}
}
MenuItemTag.java (child):
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MenuItemTag extends TagSupport {
private String item;
public void setItem(String item) { this.item = item; }
public int doStartTag() throws JspException {
MenuTag parent = (MenuTag) getParent();
parent.addMenuItem(item);
return SKIP_BODY;
}
}
Accessing JSP Implicit Objects in Tag Handlers
Using the PageContext object, you can access all JSP implicit objects from a tag handler:
| Implicit Object | PageContext Method |
|---|---|
request | getRequest() |
response | getResponse() |
config | getServletConfig() |
application | getServletContext() |
session | getSession() |
out | getOut() |
page | getPage() |
exception | getException() |
Note: The exception object is only available in error pages. If the enclosing JSP is not an error page, getException() returns null.
Attribute management methods:
setAttribute(String name, Object value)
setAttribute(String name, Object value, int scope)
getAttribute(String name)
getAttribute(String name, int scope)
removeAttribute(String name)
removeAttribute(String name, int scope)
findAttribute(String name)
getAttributeNamesInScope(int scope)
Demo — Accessing server info from tag handler:
package tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyCustomTag extends TagSupport {
public int doStartTag() throws JspException {
ServletRequest req = pageContext.getRequest();
String s1 = req.getServerName() + ":" + req.getServerPort();
ServletResponse resp = pageContext.getResponse();
String s2 = resp.getContentType();
HttpSession session = pageContext.getSession();
String s3 = session.getId();
try {
JspWriter out = pageContext.getOut();
out.println("<h1>" + s1 + "<br>" + s2 + "<br>" + s3 + "</h1>");
} catch(Exception e) {}
return EVAL_BODY_INCLUDE;
}
}
- Custom tags consist of 3 components: Tag Handler class, TLD file, and taglib directive
- Classic tag model uses doStartTag/doEndTag lifecycle with specific return constants
- TagSupport and BodyTagSupport simplify implementation by providing defaults