Every project has that one quiet corner where something almost works.
For me, it was the footer of my projects site’s Maven skin: the copyright
year was hardcoded, and every time I tried to make it dynamic with $date,
nothing rendered.
This is the story of that afternoon, the wrong turns, and the one-liner that finally fixed it.
The Setup
The site is generated by Apache Maven’s Site Plugin using a custom skin — a JAR
that ships Velocity templates under META-INF/maven/. The main template is
site.vm; shared macros live in site-macros.vm.
The footer I wanted:
Copyright © 2024–2026 Joseph Verron. All rights reserved.
Where 2026 should update itself every year without me touching a file.
Attempt 1 — $date.format(...)
My first instinct, and what every AI assistant suggested:
Copyright © 2024–$date.format("yyyy")
Result: the string $date.format("yyyy") rendered literally. Velocity did
not evaluate it.
The AI’s explanation: “$date is a DateTool from Velocity Tools;
call .format('yyyy') to get the year.”
Plausible. Wrong.
Attempt 2 — $date.get("yyyy")
Second AI suggestion, with equal confidence:
$date.get("yyyy")
Result: blank. Nothing. Not even an error in the build log.
Attempt 3 — velocity-tools/site-tools.xml
Third suggestion: configure a site-tools.xml to expose a proper DateTool.
I spent twenty minutes writing the config before realizing the Maven Site Plugin
does not load arbitrary Velocity Tools configuration from the skin JAR.
The Actual Fix — Print the Type, Then Read the Source
Before changing anything, I stopped guessing and printed the runtime type — the most old‑school trick in the book:
Type of $date is: ${date.getClass().getSimpleName()}
That one line told me exactly what I was dealing with at render time. The page printed:
Type of $date is: ComparisonDateTool
Armed with the actual type, I opened site-macros.vm in the skin and searched
for any $date. There it was, in the #copyright() macro:
#macro ( copyright )
#if ( $project )
#set ( $currentYear = $date.getYear() )
#if ( ${project.inceptionYear} && ( ${project.inceptionYear} != ${currentYear}))
${project.inceptionYear}–${currentYear}${period}##
#else
${currentYear}${period}##
#end
#end
#end
$date in Maven Site’s Velocity context is *
*org.apache.velocity.tools.generic.ComparisonDateTool, not DateTool —
consistent with what I just printed. It exposes getYear() (returns
an int), getMonth(), getDay() — but **no format() method.
The working pattern for any footer or template that needs the current year:
#set( $currentYear = $date.getYear() )
Copyright © 2024–${currentYear}
Or, if you want the range to collapse when inception year is equal to current
year (exactly what #copyright() does):
#set( $currentYear = $date.getYear() )
#if( ${project.inceptionYear} && ${project.inceptionYear} != ${currentYear} )
${project.inceptionYear}–${currentYear}
#else
${currentYear}
#end
Why $date.format(...) Silently Fails
Velocity’s reference resolution is lenient by default. When you write $date.
format("yyyy"), Velocity calls format("yyyy") on the object.
ComparisonDateTool has no such method, so Velocity returns null — and by
default renders null as an empty string, not an error.
This is the classic Velocity silent-failure trap: a missing method produces nothing, not a stack trace.
The ${date} vs $date distinction matters too:
${date}is the formal notation, allowing to write${date}and_somethingwithout Velocity trying to find thedateand_somethingvariable.$dateis the shorthand notation, allowing to write$date and_somethingif space exists between the variable and what follows.
Don’t Outsource Your Mental Model
“Plausible” advice (from docs, blogs, or AI) is not a substitute for knowing
what object you’re actually calling. The decisive move here was not another
search query — it was rendering getClass().getSimpleName() and then reading
the macros already in the skin. Old‑school, reliable, fast.
Mindfulness beats cargo culting: pause, observe the real object, confirm with a source, then change one thing.
If you do ask for help, ask concrete questions like: “In Maven Site, what class
is bound to $date in the Velocity context?” You’ll either get the right class
name, or you’ll know exactly what to print to find out yourself.
An Old‑School Debugging Mini‑Checklist (for Velocity, but really for anything)
- Print the runtime type:
${var.getClass().getSimpleName()}(or.getName()). - Echo suspicious values directly into the template to see what they are.
- Reduce the surface: comment out blocks until the minimal failing snippet remains.
- Search the local source/templates for prior art; copy a known‑good pattern.
- Skim the Javadoc of the concrete class you discovered.
Takeaway
| What I tried | Why it failed |
|---|---|
$date.format("yyyy") |
ComparisonDateTool has no format() |
$date.get("yyyy") |
No get(String) method either |
site-tools.xml config |
Maven Site Plugin ignores skin-level tool config |
$date.getYear() |
✅ Works — it’s the actual API |
${date.getClass().getSimpleName()} |
✅ Works — tells you what you’re calling methods on |
If you maintain a Maven skin or use Maven Site’s Velocity templates, bookmark
ComparisonDateTool in the Velocity Tools javadoc — and bookmark your own
curiosity. The fix was one line; the win was remembering to observe the system
in front of me before believing someone else’s model of it.
A note on Velocity itself
Velocity can be quirky — lenient resolution, few guardrails — and those design choices probably explain its limited adoption today. But it is also charming in its simplicity and wonderfully flexible for Java power users. If you meet it on its own terms, it will do exactly what you ask.