TemplateInputException during ClassLoaderTemplateResolver transition
0 reputation · 17 Apr 2024, 11:16 UTC
0 reputation · 17 Apr 2024, 11:16 UTC
A project utilizes a FileTemplateResolver during local development to enable rapid UI iterations. However, the production environment is configured to use a ClassLoaderTemplateResolver to load templates from the packaged JAR. This transition often introduces discrepancies in how relative paths are resolved between the physical file system and the classpath.
While spring.thymeleaf.cache is disabled locally for hot-reloading, it is enabled in production to reduce CPU overhead. There is uncertainty regarding how the template cache interacts with the ClassLoaderTemplateResolver when templates are dynamically updated or when the classpath contains duplicate resource paths across different dependencies.
Does the ClassLoaderTemplateResolver maintain a distinct caching mechanism compared to the FileTemplateResolver? Under what specific conditions will a cached template fail to refresh if the underlying classpath resource is updated without a full JVM restart?
30275 reputation · 17 Apr 2024, 15:35 UTC
The ClassLoaderTemplateResolver does not maintain a distinct caching mechanism; it utilizes the same TemplateCache provided by the template engine (such as Thymeleaf). The TemplateInputException occurs when the resolver cannot locate the resource on the classpath, regardless of whether caching is enabled or disabled.
When spring.thymeleaf.cache is set to true, the engine stores the parsed template in memory. Under the following conditions, a cached template will fail to refresh without a JVM restart:
ClassLoader from a packaged JAR are immutable. Even if the underlying file on the disk is changed, the JVM's classloader typically does not reload the resource from the JAR until the application is redeployed.ClassLoaderTemplateResolver again, bypassing any potential updates to the classpath.To resolve discrepancies when moving from FileTemplateResolver to ClassLoaderTemplateResolver, apply these configuration adjustments:
prefix does not start with a leading slash (e.g., use templates/ instead of /templates/). Many classloader implementations treat a leading slash as an absolute path from the root, which can fail depending on the JAR structure.TemplateInputException if the case differs.src/main/resources so they are bundled into BOOT-INF/classes in the final JAR.To diagnose the exact failure point, use the following scoped checks:
# Inspect the packaged JAR to verify the template exists in the expected path
jar tf your-application.jar | grep your-template-name.html
Check the application logs for the specific path the resolver attempted to access. If the log shows a path that looks correct but still fails, the issue is likely a leading slash in the prefix or a case-sensitivity mismatch.
Diagnostic Detail Needed: Are you using a multi-module project structure where templates are located in a separate dependency JAR rather than the main application JAR?
Use comments to ask for clarification. Post a solution as an answer.
30,275 reputation · 17 Apr 2024, 18:00 UTC
When spring.thymeleaf.cache is enabled, Thymeleaf stores each parsed template in the shared TemplateCache using the full cache key (prefix + templateName + suffix) as the entry. The ClassLoaderTemplateResolver itself does not perform any timestamp checks; it simply returns the resource stream from the classloader. Therefore, to see a updated template that lives inside a JAR without restarting the JVM, you must evict the corresponding cache entry (or clear the whole cache). You can obtain the TemplateCache bean (named templateCache by Spring Boot’s ThymeleafAutoConfiguration) and call evict(templateName) where templateName is the exact key used during resolution (e.g., "home" for prefix=templates/ and suffix=.html). After eviction, the next request forces the resolver to reload the resource from the classloader, picking up any changes made to the underlying JAR (provided the JAR has been rebuilt and re‑loaded by the classloader, e.g., via a devtools restart or a custom classloader). This approach avoids a full JVM restart while still respecting the immutable nature of classpath resources.