A quick story from last night
\n\n\n\nI was staring at a JSP that looked like a thrift store shelf. Mix of scriptlets, stray includes, and a generous sprinkle of out.println calls. A simple product list with paging and currency formatting had grown messy. The next feature request was waiting. The build was on Tomcat 5.5, the team uses Struts one, and I kept thinking about Jakarta Taglibs and how they could turn this pile of spaghetti into reusable view logic. Two hours later I had a neat tag for paging, JSTL for formatting, and a view that reads like a template. Same servlet. Same controller. Clean view. The manager did not care how I did it. He just noticed the page became fast to change.
\n\n\n\nWhen people say JSP taglibs they often think about JSTL and move on. That is only the start. Jakarta Taglibs gives you a catalog of small helpers plus a way to build your own. You drop a JAR with a TLD, declare the URI once, and then you get tidy tags like c:forEach and fmt:message. That set alone pushes most scriptlets out of your pages. You also gain Expression Language so you can write ${order.total} instead of a bean call wrapped in Java code. The result is a view that is easier to read and change. Controllers stay focused on data and flow. Views handle markup, loops, conditions, and messages with tags that explain themselves. You get reuse by moving repeated chunks into custom tags that land across pages and even across apps.
\n\n\n\nLet me show a simple tag that keeps paging logic out of your JSP. The tag is small but it pays rent on every screen with lists. I am using SimpleTagSupport since we are on JSP two, which keeps the class tiny. Drop this into a small JAR and publish a TLD so your views can call it from anywhere.
\n\n\n\npackage com.example.view.tags;\n\nimport java.io.IOException;\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\nimport javax.servlet.jsp.JspWriter;\n\npublic class PagerTag extends SimpleTagSupport {\n private int page;\n private int totalPages;\n private String url;\n\n public void setPage(int page) { this.page = page; }\n public void setTotalPages(int totalPages) { this.totalPages = totalPages; }\n public void setUrl(String url) { this.url = url; }\n\n @Override\n public void doTag() throws JspException, IOException {\n if (totalPages <= 1) return;\n JspWriter out = getJspContext().getOut();\n\n out.write("<nav class='pager'>");\n if (page > 1) {\n out.write("<a href='" + url + "?page=" + (page - 1) + "' rel='prev'>« Prev</a>");\n }\n out.write(" Page " + page + " of " + totalPages + " ");\n if (page < totalPages) {\n out.write("<a href='" + url + "?page=" + (page + 1) + "' rel='next'>Next »</a>");\n }\n out.write("</nav>");\n }\n}\n\n\n\nThe tag needs a TLD entry. Keep the URI permanent so your JSPs never change when you move the JAR. Name things in a way that matches your team�s language. That makes the view read like a sentence.
\n\n\n\n<?xml version="1.0" encoding="UTF-8"?>\n<taglib xmlns="http://java.sun.com/xml/ns/j2ee"\n xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee\n http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"\n version="2.0">\n\n <tlib-version>1.0</tlib-version>\n <short-name>ui</short-name>\n <uri>http://example.com/tags/ui</uri>\n\n <tag>\n <name>pager</name>\n <tag-class>com.example.view.tags.PagerTag</tag-class>\n <body-content>empty</body-content>\n <attribute>\n <name>page</name>\n <required>true</required>\n <type>java.lang.Integer</type>\n </attribute>\n <attribute>\n <name>totalPages</name>\n <required>true</required>\n <type>java.lang.Integer</type>\n </attribute>\n <attribute>\n <name>url</name>\n <required>true</required>\n <type>java.lang.String</type>\n </attribute>\n </tag>\n</taglib>\n\n\n\nNow the JSP gets clean. You keep Struts logic or Spring MVC logic in the controller, pass numbers as request attributes, and the page says what it does in plain view language. Add JSTL for loops and format tags for money and dates, and the page finally reads like a web page, not a Java workbook.
\n\n\n\n<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>\n<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>\n<%@ taglib prefix="ui" uri="http://example.com/tags/ui" %>\n\n<h3>Products</h3>\n<ul>\n <c:forEach items="${products}" var="p">\n <li>\n <strong>${p.name}</strong>\n <span><fmt:formatNumber value="${p.price}" type="currency"/></span>\n </li>\n </c:forEach>\n</ul>\n\n<ui:pager page="${page}" totalPages="${totalPages}" url="${pageUrl}" />\n\n\n\nFor lighter cases, try tag files. They are JSP fragments stored under WEB INF tags. No Java class. No compile step outside the normal web build. The container turns them into tags for you. It is perfect for a small repeated chunk with a few attributes. You keep markup next to the logic and still get reuse. Here is a tiny date formatter with a fall back message.
\n\n\n\n<%@ tag pageEncoding="UTF-8" %>\n<%@ attribute name="value" required="true" type="java.util.Date" %>\n<%@ attribute name="pattern" required="false" type="java.lang.String" %>\n<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>\n<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>\n\n<c:choose>\n <c:when test="${not empty value}">\n <fmt:formatDate value="${value}" pattern="${empty pattern ? 'yyyy MMM dd' : pattern}" />\n </c:when>\n <c:otherwise>\n <em>N A</em>\n </c:otherwise>\n</c:choose>\n\n\n\nCall it like any other tag and keep going. Jakarta Taglibs also ships handy stuff like String, Request, and Mail tag libraries. The SQL taglib exists too. I avoid it in production since it sneaks data access into the view. Save that for controllers or service classes. Keep the view lean and predictable. Your future self will thank you. This split also plays nice with Tiles in Struts or views in Spring MVC. Each page becomes a composition of tags and templates, and all of them can be tested in small pieces.
\n\n\n\nPeople ask about performance. Taglibs are compiled. Containers like Tomcat do tag pooling so objects get reused. That is already faster than scriptlets that write raw strings and easy to cache if you add a small filter in front. The big win is not just speed. The big win is that the next person can follow the template without reading a ball of Java in the page. It also cuts XSS risks since you can centralize output encoding. JSTL gives you fn:escapeXml. You can also wrap your own out tag that always escapes. One place to get this right keeps the whole site safer.
\n\n\n\nLocalization also shines with taglibs. Set a fmt bundle and sprinkle fmt message with keys. Product names keep their accents. Prices show in the right currency. Dates stop arguing with your browser. This is easy to demo and hard to argue against. It works on Tomcat, Resin, or the big servers like WebLogic old releases that many of us still run. The Standard Taglib 1.1 matches well with JSP two and Expression Language, which is what most projects I see are using today. If your app ships across countries, this set pays for itself the first time a customer in Madrid reads the site without glitches.
\n\n\n\nTesting tags can be simple. For pure view tags you can write tag unit tests with a mock page context, or run Cactus in the container if you need the full stack. I keep one golden JSP that renders a page with all our tags and compare the output on every build. It catches regressions when a teammate changes a default. Also, document your tags right in the TLD. Most IDEs like Eclipse pick that up and show tooltips so juniors learn as they type. Small things like good names, sane defaults, and examples in the JAR make the library a real friend in a big team.
\n\n\n\nOne last piece. EL functions are your secret sauce when a full tag class feels heavy. You can expose static helpers for string tweaks or math that reads clean in a page. Keep them pure and side effect free so they are easy to understand. That keeps your custom tags focused on markup while functions handle tiny data changes. Between JSTL core, fmt, and a small set of your own tags and functions, you will cover most of the gaps that used to invite scriptlets. Your JSPs turn into readable templates and your changes stay small.
\n\n\n\n<!-- functions.tld -->\n<taglib xmlns="http://java.sun.com/xml/ns/j2ee" version="2.0">\n <tlib-version>1.0</tlib-version>\n <short-name>fnx</short-name>\n <uri>http://example.com/functions</uri>\n <function>\n <name>slug</name>\n <function-class>com.example.view.fn.TextFns</function-class>\n <function-signature>java.lang.String slug(java.lang.String)</function-signature>\n </function>\n</taglib>\n\n// usage\n<%@ taglib prefix="fnx" uri="http://example.com/functions" %>\n<a href="/product/${fnx:slug(p.name)}">${p.name}</a>\n\n\n\nThis is not about fashion. Rails is shiny, JSF is getting attention, but many shops still run JSP backed by Struts one or Spring MVC. That stack can be tidy and fast to change if the view layer is broken into Jakarta Taglibs and a small set of your own helpers. You get reusable view logic that survives design changes and new pages. You get fewer bugs when a page grows a second column or a new table variant. You also get happier teammates because the view moves like a page, not like a small app stuck in a template file.
\n\n\n\nTakeaway
\n\n\n\nPush scriptlets out. Reach for JSTL first, then add custom tags or tag files for patterns you repeat. Keep your URI stable, name things like a human, document in the TLD, and write one visual test page to keep the library honest. Use tags for loops, messages, dates, and widgets like pager or breadcrumbs. Leave data work to controllers. With that split, Jakarta Taglibs turn JSP into a clear template language and give you reusable view logic you can trust across projects.
\n