Nearby lessons

18 of 30

JSP - Custom Tags (Classic Model)

📌 What You Will Learn
  • 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:

ComponentDescription
1. Tag Handler ClassA 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 FileTag Library Descriptor — an XML file that maps the custom tag name to its tag handler class.
3. taglib DirectiveMakes the custom tag available to JSP pages by specifying the TLD file location.

Execution Flow:

  1. JSP engine encounters a custom tag → identifies the prefix
  2. Finds the matching taglib directive
  3. From the directive, locates the TLD file
  4. From the TLD, identifies the Tag Handler class
  5. 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)
TypeInterface/ClassPurpose
InterfaceTagBase interface for all tag handlers. Defines 6 methods. Use when no body manipulation or iteration is needed.
InterfaceIterationTagChild of Tag. Adds doAfterBody() for processing tag body multiple times.
InterfaceBodyTagChild of IterationTag. Adds setBodyContent() and doInitBody() for manipulating tag body content.
Abstract ClassTagSupportImplements IterationTag with defaults. Extend this for simple/iteration tags — override only needed methods.
Abstract ClassBodyTagSupportExtends TagSupport, implements BodyTag. Extend this for tags that process body content.
Abstract ClassBodyContentBuffer 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:

MethodDescription
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:

ConstantUsed ByMeaning
EVAL_BODY_INCLUDEdoStartTag()Tag body is evaluated and included in the output
SKIP_BODYdoStartTag()Tag body is skipped
EVAL_PAGEdoEndTag()Rest of the JSP page is executed normally
SKIP_PAGEdoEndTag()Rest of the JSP page is NOT executed

Tag Handler Lifecycle

When JSP engine encounters a custom tag, the lifecycle follows these steps:

  1. Identify — JSP engine finds the tag handler class via taglib directive and TLD
  2. Create — Web container creates a TagHandler instance (public no-arg constructor)
  3. setPageContext() — Makes PageContext available to the handler
  4. setParent() — Sets parent tag object (for nested tags)
  5. Set Attributes — JSP engine calls setter methods for each attribute
  6. doStartTag() — Returns EVAL_BODY_INCLUDE (include body) or SKIP_BODY
  7. doEndTag() — Returns EVAL_PAGE (continue JSP) or SKIP_PAGE (stop JSP)
  8. 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:

ValueDescription
emptyNo tag body allowed. Use <mine:mytag/>
tagdependentBody is treated as plain text (no JSP processing)
scriptlessNo scripting elements, but standard actions and EL are allowed
jspNo restrictions on body content (default)

Attribute sub-elements:

ElementDescription
<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 evaluation
  • EVAL_BODY_AGAIN — return this to re-evaluate the body

Lifecycle:

  1. doStartTag() → returns EVAL_BODY_INCLUDE or SKIP_BODY
  2. Tag body is evaluated
  3. doAfterBody() → returns EVAL_BODY_AGAIN (re-evaluate body) or SKIP_BODY (proceed to doEndTag)
  4. doEndTag() → returns EVAL_PAGE or SKIP_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:

  • pageContext is 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:

  1. JSP engine creates a BodyContent object
  2. Calls setBodyContent() to provide it
  3. Calls doInitBody() for initialization
  4. Body is evaluated and stored in the buffer
  5. 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:

MethodDescription
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

MethodTagSupportBodyTagSupport
doStartTag()
Possible ReturnsEVAL_BODY_INCLUDE, SKIP_BODYEVAL_BODY_INCLUDE, SKIP_BODY, EVAL_BODY_BUFFERED
DefaultSKIP_BODYEVAL_BODY_BUFFERED
doAfterBody()
Possible ReturnsEVAL_BODY_AGAIN, SKIP_BODYEVAL_BODY_AGAIN, SKIP_BODY
DefaultSKIP_BODYSKIP_BODY
doEndTag()
Possible ReturnsEVAL_PAGE, SKIP_PAGEEVAL_PAGE, SKIP_PAGE
DefaultEVAL_PAGEEVAL_PAGE
setBodyContent()Not applicableCalled when doStartTag() returns EVAL_BODY_BUFFERED
doInitBody()Not applicableCalled 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 ObjectPageContext Method
requestgetRequest()
responsegetResponse()
configgetServletConfig()
applicationgetServletContext()
sessiongetSession()
outgetOut()
pagegetPage()
exceptiongetException()

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;
  }
}
📝 Key Takeaways
  • 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

🧠 Test Your Knowledge

3 Questions
Progress: 0 / 3