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_something without Velocity trying to find the dateand_something variable.
  • $date is the shorthand notation, allowing to write $date and_something if 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.