JavaServer Pages (JSP) tag libraries were once a central mechanism for encapsulating reusable view-layer logic in Java web applications. Custom tags allowed developers to build expressive, component-like structures long before modern frontend frameworks became popular. These tags were organized and exposed through Tag Library Descriptor (TLD) files, which describe the tag names, classes, attributes, and behaviors used by the JSP engine.
However, many legacy applications still rely on older JSP tag libraries that were written during the Java 6, Java 7, or Java 8 era. When migrating such applications to Java 17, developers often encounter compatibility issues. One recurring problem is the absence of valid or updated TLD files, especially when older frameworks relied on automatic TLD generation or outdated build tools.
To address this issue, developers can create a Java 17-compatible TLD generator that scans legacy tag classes and automatically produces valid TLD files. Such a tool can be integrated into build processes and ensures that legacy JSP tag libraries remain usable without rewriting the entire tag infrastructure.
This article explains how to design and implement a TLD generator compatible with Java 17, including architecture, scanning strategies, metadata extraction, XML generation, and practical coding examples.
Understanding JSP Tag Libraries and TLD Files
A Tag Library Descriptor (TLD) is an XML document that describes a collection of JSP custom tags. It informs the JSP container how tags behave and which Java classes implement them.
A typical TLD file contains information such as:
-
- Tag library name
- Tag handler class
- Attribute definitions
- Body content rules
- Description metadata
Example structure:
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.3</jsp-version>
<short-name>legacy</short-name>
<uri>http://example.com/tags</uri>
<tag>
<name>hello</name>
<tag-class>com.example.tags.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
In older projects, developers often wrote these files manually or relied on frameworks that generated them during compilation.
When migrating to Java 17, some legacy build plugins no longer work, making it necessary to create a custom generator.
Challenges When Migrating Legacy JSP Tag Libraries to Java 17
Legacy JSP environments face several technical issues during modernization.
1. Deprecated Build Plugins
Many Maven or Ant plugins that once generated TLD files have not been updated for modern Java versions.
2. Reflection Restrictions
Java 17 enforces stronger module boundaries, which can limit reflection access if not handled correctly.
3. Incomplete Metadata
Older tag libraries often lack annotations or metadata needed for automatic descriptor generation.
4. Outdated Class Structures
Legacy tags may extend older classes such as:
javax.servlet.jsp.tagext.TagSupport
javax.servlet.jsp.tagext.BodyTagSupport
Despite these challenges, Java 17 still supports the Servlet and JSP APIs, meaning that legacy tags can still work with proper descriptors.
Designing a Java 17-Compatible TLD Generator
A good TLD generator typically performs several key tasks:
-
- Scan classpath packages for tag classes
- Detect valid JSP tag handlers
- Extract attributes from setter methods
- Generate a structured TLD XML file
- Write the descriptor to the correct directory
The generator can run as:
-
- A standalone CLI tool
- A Maven or Gradle task
- A build-time annotation processor
For flexibility, we will implement a standalone Java generator.
Defining the Tag Metadata Model
First, we define a Java class that represents tag metadata.
public class TagInfoModel {
private String name;
private String tagClass;
private String bodyContent;
public TagInfoModel(String name, String tagClass, String bodyContent) {
this.name = name;
this.tagClass = tagClass;
this.bodyContent = bodyContent;
}
public String getName() {
return name;
}
public String getTagClass() {
return tagClass;
}
public String getBodyContent() {
return bodyContent;
}
}
This model will hold extracted data before converting it to XML.
Scanning for Legacy Tag Classes
Next, we implement a class scanner that searches packages for tag handlers.
import javax.servlet.jsp.tagext.TagSupport;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class TagClassScanner {
public List<Class<?>> scan(String basePackage) throws Exception {
List<Class<?>> tags = new ArrayList<>();
String path = basePackage.replace('.', '/');
File directory = new File("target/classes/" + path);
for (File file : directory.listFiles()) {
if (file.getName().endsWith(".class")) {
String className = basePackage + "." +
file.getName().replace(".class", "");
Class<?> clazz = Class.forName(className);
if (TagSupport.class.isAssignableFrom(clazz)) {
tags.add(clazz);
}
}
}
return tags;
}
}
This scanner detects classes that extend TagSupport, which is common in legacy JSP tags.
Extracting Tag Metadata Using Reflection
Once classes are discovered, we extract metadata.
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TagMetadataExtractor {
public TagInfoModel extract(Class<?> tagClass) {
String tagName = tagClass.getSimpleName()
.replace("Tag", "")
.toLowerCase();
return new TagInfoModel(
tagName,
tagClass.getName(),
"empty"
);
}
public List<String> extractAttributes(Class<?> tagClass) {
List<String> attributes = new ArrayList<>();
for (Method method : tagClass.getMethods()) {
if (method.getName().startsWith("set")) {
String attribute =
method.getName().substring(3,4).toLowerCase()
+ method.getName().substring(4);
attributes.add(attribute);
}
}
return attributes;
}
}
Setter methods automatically map to tag attributes.
Example:
public void setUser(String user)
becomes
<attribute>
<name>user</name>
</attribute>
Generating the TLD XML Structure
Now we generate the TLD XML document.
import java.io.FileWriter;
import java.util.List;
public class TldWriter {
public void write(List<TagInfoModel> tags, String output) throws Exception {
FileWriter writer = new FileWriter(output);
writer.write("<taglib>\n");
writer.write("<tlib-version>1.0</tlib-version>\n");
writer.write("<jsp-version>2.3</jsp-version>\n");
writer.write("<short-name>generated</short-name>\n");
for (TagInfoModel tag : tags) {
writer.write("<tag>\n");
writer.write("<name>" + tag.getName() + "</name>\n");
writer.write("<tag-class>" + tag.getTagClass() + "</tag-class>\n");
writer.write("<body-content>" + tag.getBodyContent() + "</body-content>\n");
writer.write("</tag>\n");
}
writer.write("</taglib>");
writer.close();
}
}
This produces a valid TLD descriptor.
Building the Generator Orchestrator
Next we connect everything together.
import java.util.ArrayList;
import java.util.List;
public class TldGenerator {
public static void main(String[] args) throws Exception {
TagClassScanner scanner = new TagClassScanner();
TagMetadataExtractor extractor = new TagMetadataExtractor();
TldWriter writer = new TldWriter();
List<Class<?>> tagClasses =
scanner.scan("com.example.tags");
List<TagInfoModel> models = new ArrayList<>();
for (Class<?> tagClass : tagClasses) {
TagInfoModel model =
extractor.extract(tagClass);
models.add(model);
}
writer.write(models, "generated.tld");
System.out.println("TLD generated successfully.");
}
}
Running this tool will generate a TLD file automatically.
Example Legacy Tag Class
To see how this works, consider a typical legacy tag.
package com.example.tags;
import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
public class HelloTag extends TagSupport {
private String user;
public void setUser(String user) {
this.user = user;
}
@Override
public int doStartTag() {
try {
PageContext pc = pageContext;
JspWriter out = pc.getOut();
out.write("Hello " + user);
} catch (Exception e) {
e.printStackTrace();
}
return SKIP_BODY;
}
}
The generator will automatically produce:
<tag>
<name>hello</name>
<tag-class>com.example.tags.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>
This allows the tag to be used in JSP pages.
Integrating the Generator into a Build Pipeline
To maximize usefulness, the generator should run automatically during builds.
For example, using Maven:
-
- Compile Java sources
- Run TLD generator
- Package TLD into
WEB-INF
A simple execution approach is to run the generator from a Maven exec plugin or Gradle task.
Example output location:
src/main/webapp/WEB-INF/generated.tld
JSP usage:
<%@ taglib uri="/WEB-INF/generated.tld" prefix="app" %>
<app:hello user="John"/>
Enhancing the Generator for Modern Java
For production systems, the generator can be extended with advanced features.
Annotation Support
Custom annotations can provide metadata.
@JspTag(name="hello")
public class HelloTag extends TagSupport
Attribute Type Detection
Reflection can capture attribute types.
method.getParameterTypes()
Body Content Modes
Tags may support:
empty
JSP
scriptless
tagdependent
XML Formatting
Using libraries like DOM or StAX can improve formatting and validation.
Module Compatibility
If running under the Java module system, ensure reflection access is granted using JVM flags.
Conclusion
Building a Java 17-compatible TLD generator for legacy JSP tag libraries is a practical and efficient solution for organizations maintaining older Java web applications. While JSP technology is no longer the primary approach for modern web development, many enterprise systems still rely heavily on legacy tag libraries. Rewriting these systems entirely would be costly and risky, so enabling them to operate smoothly in newer Java environments is often the preferred path.
A custom TLD generator solves several migration challenges simultaneously. It removes the dependency on outdated plugins, automates the descriptor creation process, and ensures that tag metadata remains synchronized with the actual Java classes. By scanning packages, detecting tag handler classes, extracting attributes via reflection, and generating valid XML descriptors, developers can rebuild the missing infrastructure that older build tools once provided.
The architecture of such a generator is relatively straightforward but powerful. It typically consists of four major components: a class scanner, a metadata extractor, a descriptor model, and an XML writer. Together, these components create a pipeline that transforms compiled Java classes into structured TLD files that the JSP container can understand. The approach works especially well for legacy systems where conventions like TagSupport inheritance and setter-based attributes are already established.
Another major advantage of this approach is automation. Instead of manually writing or updating TLD files whenever a tag changes, the generator ensures that descriptors are created directly from the source code. This significantly reduces maintenance overhead and prevents inconsistencies between the tag implementation and its descriptor definition.
Furthermore, the generator can evolve alongside modern Java practices. By incorporating annotation processing, improved reflection handling, and better XML generation techniques, the tool can become an integral part of a modern build pipeline. It can also be extended to support additional metadata such as attribute types, descriptions, body content rules, and validation logic.
From a modernization perspective, this approach provides a bridge between legacy JSP architecture and modern Java runtimes like Java 17. Instead of forcing an immediate rewrite of large JSP-based systems, organizations can incrementally stabilize their infrastructure while planning long-term migrations toward newer technologies such as component-based web frameworks or REST-driven frontends.
In summary, creating a Java 17-compatible TLD generator is an effective strategy for preserving and maintaining legacy JSP tag libraries in modern environments. By leveraging reflection, structured metadata models, and automated XML generation, developers can ensure that older tag libraries remain functional, maintainable, and build-friendly. This solution not only restores compatibility with modern Java versions but also introduces automation and consistency that improve the long-term sustainability of legacy web applications.