尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Spring Boot 项目 Cannot find template location 报错排查与修复指南

发布时间:2026/9/9 23:19:28

资讯中心
01
ARTICLE

Spring Boot 项目 Cannot find template location 报错排查与修复指南

Spring Boot 项目 Cannot find template location 报错排查与修复指南
最近帮朋友排查一个 Spring Boot 项目项目一启动就崩控制台里赫然写着Cannot find template location: classpath:/templates/。这个报错在 Spring Boot 的 Web 项目里太经典了随便一搜能出来一堆结果但真正有意思的是他项目目录里明明有templates文件夹而且里面塞满了.html页面它还是固执地告诉我“找不到模板位置”。我用豆包把异常堆栈完整丢进去对了一遍思路又翻了一圈自动配置的源码最后定位到的问题和“目录不存在”没有半毛钱关系。这篇就把整个排查过程、背后的 Spring Boot 模板解析机制、以及不同场景下的修复方式完整写出来。不管你是刚接触 Spring Boot 的小白还是已经被这个报错折腾过几次的开发者照着下面的链路走一遍基本能把自己的问题锁死在具体原因上。1. 报错的抛出过程Spring Boot 在什么时候检查模板目录1.1 从异常堆栈里读出的信息很多人一看到Cannot find template location就直接默认是“路径写错了”于是去把spring.thymeleaf.prefix改来改去其实这是绕了远路。正确做法是先看堆栈顶部找到真正抛出这个异常的那一行因为不同模板引擎抛这个错的时机完全不一样。如果是 Thymeleaf报错通常长这样org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible... Caused by: org.thymeleaf.exceptions.TemplateInputException: Cannot find template location: classpath:/templates/注意最后一个Caused by它说的是Cannot find template location: classpath:/templates/而不是Cannot find template [index]。这两者是有本质区别的。后者是说模板文件本身不存在或者访问不到前者是说模板解析器在按前缀定位目录时压根没拿到任何资源。如果出问题的是 Velocity 模板引擎报错风格会更直白一些Caused by: java.lang.IllegalArgumentException: Cannot find template location: classpath:/templates/ (please add some templates, check your Velocity configuration, or set spring.velocity.resourceLoaderPath)而 FreeMarker 通常不会直接抛这个文案更多是抛TemplateNotFoundException它说的是Template xxx not found。所以当你看到标题这个报错时大概率是 Thymeleaf 或 Velocity 场景不要用 FreeMarker 的思路去套。看堆栈还有一个容易被忽略的细节看异常是在应用启动阶段抛出来的还是在某个 HTTP 请求进来、首次渲染页面时才抛出来的。这决定了问题的性质。启动阶段抛说明自动配置在装配模板引擎时做了位置校验或者项目里配置了容器启动时立即渲染首页请求阶段抛说明引擎本身已经装配好了但解析器在前缀目录里找不到对应的模板资源。这两种情况对应的排查方向完全不同后面我会单独用一个章节细讲。1.2 自动配置里的默认模板位置从哪来Spring Boot 最核心的约定就是“约定优于配置”。对于模板引擎来说这个约定就是模板默认放在classpath:/templates/静态资源默认放在classpath:/static/。这个约定不是凭空来的它写死在各个自动配置类里。拿 Thymeleaf 举例ThymeleafAutoConfiguration里的SpringResourceTemplateResolver会自动装配一个解析器默认前缀就是 classpath 下的/templates/默认后缀是.html。你在application.properties里看到的这几个配置项本质就是去覆盖自动配置里已经设定好的默认值spring.thymeleaf.prefixclasspath:/templates/ spring.thymeleaf.suffix.html spring.thymeleaf.modeHTML spring.thymeleaf.cachefalse只要你引入了spring-boot-starter-thymeleaf并且没有手工去覆盖这些配置Spring Boot 就会默默地把模板解析器指到classpath:/templates/。当这个解析器工作时它会通过 Spring 的ApplicationContext.getResource(classpath:/templates/)去拿目录或目录下的文件拿不到就抛异常。那问题来了为什么目录明明存在却拿不到因为classpath:/templates/说的不是“你的源码目录里的 templates”而是“构建产物里的 classpath 根目录下的 templates”。这两者在开发环境和打包环境下经常不是一回事。这就引出了下面这个关键章节。2. 目录明明存在却继续报错三个高频触发点2.1 模板引擎依赖缺失自动配置根本没生效很多人会忽略一个基础问题如果你的项目里根本没有引入任何模板引擎的 starterSpring Boot 就不会自动配置模板解析器。此时你在application.properties里写什么spring.thymeleaf.prefix都是无效的因为对应的自动配置类根本不存在。你可能会问“那既然没引入它为什么要报模板位置的错”这类报错大多出现在两种场景项目里引入了spring-boot-starter-web但没有引入模板引擎却用自己的方式配置了ViewResolver或直接写了一个指向classpath:/templates/的路径。项目引用了某个公共模块公共模块里带了模板引擎依赖但当前模块没有传递依赖完整导致部分类缺失。正确的检查方式很简单打开pom.xml看有没有下面三个 starter 之一dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependencydependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-freemarker/artifactId /dependencydependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-velocity/artifactId /dependency如果确认依赖存在还可以在 IDE 的Maven面板里把这个 starter 展开看看thymeleaf-spring5或org.thymeleaf:thymeleaf核心包有没有真正被拉到本地仓库。有时候因为网络问题或者 Maven 仓库索引不完整依赖树里显示的虚胖但实际 jar 包没下载完整也会出现这种诡异问题。2.2 手动覆盖了 spring.thymeleaf.prefix 后的路径漂移第二个高频触发点是有人为了“自定义模板目录”写了一段配置结果把默认路径给覆盖掉了。举个例子spring.thymeleaf.prefixclasspath:/my-templates/这个配置本身没问题但如果my-templates目录不存在或者名字大小写不对启动后解析模板时就会报Cannot find template location: classpath:/my-templates/。这种情况其实方向很明确但很多人会被旧的缓存配置误导以为自己改的就是classpath:/templates/。还有一种更隐蔽的覆盖方式不是写在配置文件里而是写在Configuration类中。有些人会手动定义一个SpringResourceTemplateResolver的 Bean像这样Bean public SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver resolver new SpringResourceTemplateResolver(); resolver.setPrefix(templates/); resolver.setSuffix(.html); return resolver; }注意这里setPrefix(templates/)传的是相对路径。SpringResourceTemplateResolver会对前缀做处理最终拼成classpath:/templates/因为它的基类是AbstractConfigurableTemplateResolver默认会通过getResourceLoader()来定位。但如果你在别的地方又自定义了另一个ITemplateResolver导致出现了多个解析器某些解析器的位置不对就会干扰模板匹配。这种时候不要光看application.properties全局搜索一下代码里有没有setPrefix、setSuffix、SpringResourceTemplateResolver这些关键词把自动配置和手工配置之间的冲突找出来。2.3 多模块工程构建后classpath 里没有 templates这是我在实际项目中遇到最多、也最让人头疼的一种情况。假设你的工程是这样组织的parent-project ├── common-module ├── web-module │ └── src/main/resources │ └── templates │ └── index.html └── admin-module你在web-module里明明有templates/index.html运行web-module单独启动时一切正常但一旦打成 jar 整体启动就报模板找不到。这时候十有八九是templates目录没有被正确打进可用 classpath或者打进去了但被另一个模块的同名路径覆盖了。多模块工程里每个模块的src/main/resources内容是独立打包到各自 jar 的。如果你最终依赖的是一个聚合 jar那要看 Maven 的resource插件是否把templates目录包含进去。最常见的问题是有人把模板文件放到了src/main/java下面或者放到了模块的根目录下以为也能被加载实际上 Spring Boot 默认只会扫描src/main/resources下的内容。还有一种情况是构建配置里开启了资源过滤但过滤规则把.html文件当成无关类型忽略了。尤其当你在pom.xml里写了这样的配置resources resource directorysrc/main/resources/directory filteringtrue/filtering excludes exclude**/*.html/exclude /excludes /resource /resources那templates目录里的 HTML 直接被排除了classpath 里当然没有。这种问题报错信息完全一样但排查方向在构建配置里。3. 一套不遗漏的定位流程从开发期到构建期逐层验证如果你不想无头苍蝇一样乱试我建议按下面这个顺序做三层验证每一步都很便宜几秒钟就能出结果。3.1 第一层验证启动时的配置生效状态先把项目用最简单的方式跑起来观察启动日志。Spring Boot 在DEBUG日志级别下会输出大量自动配置报告你可以通过配置application.properties把日志级别打开logging.level.org.springframework.boot.autoconfigureDEBUG logging.level.org.thymeleafDEBUG logging.level.org.springframework.web.servletDEBUG启动后在控制台里搜索关键信息搜索ThymeleafAutoConfiguration确认它有没有被加载。如果显示Negative matches说明模板引擎自动配置没有生效基本就是缺依赖或条件不满足。搜索spring.thymeleaf.prefix相关的绑定结果看最终解析到的前缀是什么。如果这里显示的前缀和你预期的不一致说明配置被某个地方覆盖了。搜索ViewResolver相关的 Bean确认ThymeleafViewResolver有没有被创建出来。不要跳过这一步很多问题在启动日志里已经暴露了真相只是你平时不喜欢看 DEBUG 日志。3.2 第二层验证查看打包产物中的模板资源如果是多模块项目或者是从 CI/CD 流水线打包后运行的环境一定不要只在 IDE 里验证要直接去看最终的 jar 包内容。先定位到构建产物的 jar 文件然后执行jar tf target/demo-0.0.1-SNAPSHOT.jar | grep templates这个命令会列出 jar 内所有路径中包含templates的条目。正常情况下你应该看到BOOT-INF/classes/templates/ BOOT-INF/classes/templates/index.html如果这个命令输出为空说明模板资源根本没进 jar。接下来需要检查 pOM 的 resource 配置、目录结构把资源放回src/main/resources/templates下重新构建。如果输出里能看到模板文件依旧报错怎么办那就进一步看这个 jar 里的目录层级是不是被改动了。比如模板被打到了BOOT-INF/lib/xxx.jar!/templates/index.html那它就不是默认 classpath 根目录下的资源需要你在配置里做额外指定。3.3 第三层验证IDE 运行和 jar 运行时 classpath 差异这一层最容易让人抓狂因为现象往往是在 IDEA 里点运行一切正常打成 jar 再去执行立刻报Cannot find template location。原因在于 IDEA 运行时项目src/main/resources会被作为 classpath 目录之一Spring 的ApplicationContext.getResource(classpath:/templates/)能直接命中而 jar 运行时资源被封装到jar:URL 里ClassLoader 的解析方式会变。大多数情况下 Spring Boot 的 fat jar 做得足够好两种情况都能支持但如果你引入了自定义ResourceLoader或者自己的ClassLoader就可能在 jar 执行环境下得不到同样结果。遇到这种情况我建议先做一次最小化复现用mvn clean package打一个最简 jar放在本地用java -jar启动。如果最小化复现没问题再去对比差异看是额外引入的配置类、自定义ResourceLoader、还是其他模块干扰导致的。可以辅助排查的是在报错出现之前临时写一个启动类里的CommandLineRunner打印所有 classpath 资源Component public class ResourcePrinter implements CommandLineRunner { Override public void run(String... args) throws Exception { Resource[] resources new PathMatchingResourcePatternResolver() .getResources(classpath*:/templates/*); for (Resource r : resources) { System.out.println(r.getDescription()); } } }如果这里打印出来为空说明 classpath 里确实没有模板资源如果打印出来了但请求时依旧报错说明是模板解析器的前缀配置和实际资源路径对不上。4. 修复方案对照按你的实际场景选择处理方式4.1 依赖缺失场景补齐 starter 坐标如果你确认是第一种场景也就是自动配置没有生效那么修复方式很简单在pom.xml里补上对应模板引擎的 starter然后重新 build。以 Thymeleaf 为例dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency注意如果你使用的是 Spring Boot 2.3 及以上版本还需要额外考虑引入thymeleaf-extras-java8time的兼容版本不过它不会影响模板位置解析更多是和时间格式化有关。补完依赖后重新启动观察自动配置日志。如果补齐依赖后出现了另一个常见问题比如模板引擎全部正常加载但页面还是白屏那就要去看ViewResolver的优先级。Spring Boot 会自动配置ContentNegotiatingViewResolver它会把所有ViewResolver按优先级顺序找一遍。正常情况下 Thymeleaf 的解析器会排在前面但如果你手写了其他ViewResolver并且没设Order可能会抢占先机导致模板没被正确解析。4.2 配置覆盖场景恢复 prefix 或显式指向自定义目录如果你的问题出在配置覆盖那就分两种情况处理。第一种你想用默认的classpath:/templates/但配置文件或代码里被改乱了。直接恢复默认spring.thymeleaf.prefixclasspath:/templates/ spring.thymeleaf.suffix.html或者在代码里删除手动定义的SpringResourceTemplateResolverBean让自动配置接管。第二种你确实想把模板放到自定义目录比如classpath:/views/那就要保证目录存在、资源路径正确并且配置完整spring.thymeleaf.prefixclasspath:/views/ spring.thymeleaf.suffix.html spring.thymeleaf.modeHTML spring.thymeleaf.encodingUTF-8同时templates目录要替换成views目录并在其中放好模板文件。这里有个容易忽略的坑prefix的值结尾要带/不带的话Spring 在拼接时会得到classpath:/viewsindex.html这样的错误结果报错信息可能从“找不到目录”变成“找不到文件”。4.3 多模块场景模板放到能进 classpath 的位置多模块工程里先检查模板文件具体放在哪个模块的src/main/resources目录下。如果模板在web-module但最终运行的入口在admin-module你需要确认admin-module是否把web-module依赖进来了。如果依赖关系正确模板资源会作为依赖 jar 的一部分出现在最终 classpath 中但此时默认前缀classpath:/templates/能不能命中取决于依赖 jar 里是不是templates目录位于 jar 根路径。打个比方你的web-module打包后是一个web-module-1.0.jar里面结构是templates/ └── index.html那么最终 classpath 里访问classpath:/templates/index.html就能命中。但如果web-module的构建配置把模板打到了META-INF/resources/templates/下那么classpath:/templates/就访问不到必须换成classpath:/META-INF/resources/templates/。这里我建议在多模块场景下用classpath*:/templates/或者更精确的classpath:/META-INF/resources/templates/去看能不能命中。在application.properties里可以做这样的显式配置spring.thymeleaf.prefixclasspath:/META-INF/resources/templates/不过更推荐的做法还是把模板统一放在最终运行模块的src/main/resources/templates下或者确保依赖模块的模板位置与默认约定保持一致避免维护一套非标准的路径。4.4 需要自定义模板目录时的正向配置如果你既不想用默认目录又不想和自动配置打架最稳妥的方式是用配置项去覆盖而不是手写 Bean。spring.thymeleaf.prefixfile:/opt/app/views/写file:前缀可以让你直接把模板目录指向服务器上的绝对路径这在部署环境里很常见模板文件不打包进 jar运维人员可以直接修改服务器上的页面不用重新构建。spring.thymeleaf.prefixfile:/opt/app/views/ spring.thymeleaf.suffix.html spring.thymeleaf.cachefalse用这种方式要注意目录权限和路径是否存在。因为file:/不是 classpath 资源Spring Boot 不会帮你复制或创建目录。目录不存在启动时不一定立刻报错但第一次请求页面时一定会报Cannot find template location或解析失败。生产环境建议先手动mkdir -p并写入测试模板再用curl访问接口验证。5. 深入 Spring 源码模板位置解析到底怎么工作的5.1 SpringResourceTemplateResolver 的资源解析链路很多排查走到“修改配置重启验证”就停了但对于想彻底搞懂原理的人来说我建议花半小时看一下SpringResourceTemplateResolver的源码把整条链路串起来。这个类继承自AbstractConfigurableTemplateResolver核心方法在AbstractTemplateResolver里。当你调用SpringTemplateEngine.process(index)时引擎会从配置的TemplateResolver列表中找到一个能处理的解析器调用它的resolveTemplate方法。AbstractTemplateResolver的resolve方法会拼接出prefix templateName suffix得到形如classpath:/templates/index.html的字符串然后调用computeTemplateResource。SpringResourceTemplateResolver重写了computeTemplateResourceOverride protected TemplateResource computeTemplateResource( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final String resourceName, final String characterEncoding, final TemplateMode templateMode, final boolean cacheable) { Resource resource this.applicationContext.getResource(resourceName); if (resource null || !resource.exists()) { return null; } ... }重点就在这句applicationContext.getResource(resourceName)。当resourceName是classpath:/templates/index.html时Spring 的DefaultResourceLoader会把classpath:前缀交给ClassPathContextResource去解析。如果resource.exists()返回 falsecomputeTemplateResource最终会返回 null随后AbstractTemplateResolver抛出你看到的Cannot find template location。所以这个错误的本质是资源拼接结果在 classpath 中不存在。那么classpath:/templates/本身的后缀/会被 Spring 如何处理ClassPathResource对目录的判断依赖FileSystem或URL协议。在 IDE 运行时src/main/resources/templates作为目录存在getResource返回的ClassPathResource.exists()会尝试用 class loader 去打开这个目录并判断通常为 true。但在 jar 包运行环境中目录作为一个“虚拟目录”并不一定被所有URL协议正确处理这时仅靠getResource(classpath:/templates/)判断目录是否存在可能返回 false即使classpath:/templates/index.html是存在的。这就是为什么有的项目里你打开 jar 能看到templates/index.html但一执行就报Cannot find template location: classpath:/templates/因为某个环节把模板目录当成“没有”了。5.2 为什么有的项目报错在启动时有的在首次访问时理解了上面的解析链路就能解释时机差异。如果项目启动时没有任何模板请求也没有配置欢迎页仅仅装配了模板引擎那么SpringResourceTemplateResolver不会立刻去解析classpath:/templates/这个前缀目录。只有当你通过 Controller 返回一个视图名或者访问根路径/并映射到某个模板时Spring MVC 才调用ViewResolver去解析视图此时才真正触发资源加载。所以很多项目启动时一切正常控制台也没有模板相关日志但你一访问首页马上就抛出Cannot find template location。这是最典型的情况。还有一种情况是 Spring Boot 配置了欢迎页或错误页处理。当项目里放置了static/index.html或者配置了spring.mvc.view.prefixSpring Boot 会在启动时尝试解析默认页面。如果这些配置指向了一个模板解析器就会在启动阶段提前触发模板位置检查。另外spring.thymeleaf.cachefalse和spring.thymeleaf.check-template-locationtrue的组合也会影响启动检查。Spring Boot 的ThymeleafProperties里有一个checkTemplateLocation属性private boolean checkTemplateLocation true;如果为 true自动配置会在装配SpringResourceTemplateResolver时额外调用一次if (this.properties.isCheckTemplateLocation()) { Resource location this.applicationContext.getResource(this.properties.getPrefix()); if (!location.exists()) { throw new CannotFindTemplateLocationException(this.properties.getPrefix()); } }注意了这个异常就是在启动阶段抛出的非常直接。如果你把这个配置改为false启动阶段不会再去校验目录是否存在但到了首次请求时仍可能报错。所以不要用“关掉校验”来掩盖问题那只是把报错从启动阶段推迟到了请求阶段真正的资源缺失问题还在。6. 实战后的几条建议模板资源管理少踩坑6.1 明确“目录”和“文件”两类报错的差异排查时先分清楚你是卡在“目录不存在”还是“文件不存在”。日志里出现Cannot find template location大概率是目录校验或前缀解析的问题日志里出现Error resolving template [index]或Template input exception则是模板文件本身的问题。前者去查依赖、配置、classpath后者去查文件名、后缀、目录层级。我见过不少人在前者场景里反复修改suffix从.html改成.htm又改回.html纯属瞎忙。先看报错前缀再动配置。6.2 善用 DEBUG 日志和启动报告Spring Boot 的自动配置报告是非常强大的诊断工具大多数人却只在启动异常时才扫一眼。建议排查模板问题时把logging.level.org.springframework.boot.autoconfigureDEBUG打开重点看Positive matches和Negative matches里 Thymeleaf 相关条目。如果看到ThymeleafAutoConfiguration出现在Negative matches直接检查依赖和EnableAutoConfiguration排除配置。如果它在Positive matches里那就继续往下找SpringResourceTemplateResolver确认 prefix 值。6.3 用classpath*:/templates/**做快速档案有时候配置太多看不出问题直接在启动类里写一段临时代码用PathMatchingResourcePatternResolver去检查资源是否存在Bean public CommandLineRunner checkTemplateResource() { return args - { Resource[] resources new PathMatchingResourcePatternResolver() .getResources(classpath*:/templates/**); System.out.println(templates resources: resources.length); for (Resource r : resources) { System.out.println(r.getURI()); } }; }这段代码能跨所有依赖 jar 去匹配资源比单纯的getResource更直观。如果这里能拿到模板文件但实际请求依旧报错那问题一定出在模板解析器配置和资源路径拼接上可以放心去查 prefix 和 suffix。6.4 模板目录不要放在源码目录下有人习惯把模板放在src/main/java下的某个包里然后在配置里写classpath:/com/xxx/templates/。这样虽然能跑但很反直觉也会让构建工具的资源复制逻辑变得复杂。除非你有非常特殊的定制需求否则一律建议放到src/main/resources/templates下和 Spring Boot 约定保持一致降低后续维护成本。如果你确实需要多个模板来源比如一个默认目录加一个可扩展目录可以采用多个ITemplateResolver但要注意给它们设置order和checkExistenceBean public ITemplateResolver customTemplateResolver() { SpringResourceTemplateResolver resolver new SpringResourceTemplateResolver(); resolver.setPrefix(classpath:/custom-templates/); resolver.setSuffix(.html); resolver.setTemplateMode(TemplateMode.HTML); resolver.setOrder(1); resolver.setCheckExistence(true); return resolver; }这里setCheckExistence(true)很关键它允许解析器在当前模板不存在时继续去下一个解析器尝试。如果不设置第一个解析器报错会直接中断根本轮不到后面的默认目录。6.5 缓存配置只在开发期关闭排查模板问题的时候大家习惯把spring.thymeleaf.cachefalse打开方便修改模板后立即生效。但在生产环境千万别偷懒开着缓存关闭否则每次请求都重新从 classpath 读取并解析模板性能和稳定性都会受影响。上线前记得把这个配置改回true或直接删掉。另外提一句我在实际操作中发现有个冷门但高频的坑用 IDE 热部署插件时模板文件被替换但进程没触发重新编译Spring Boot 的 classpath 里还是旧资源。这时报错信息会误导你让你以为是模板位置错了其实只是 IDE 的增量编译缓存捣鬼。最简单的验证方法是mvn clean package后直接用java -jar跑一遍绕开 IDE看问题是否还存在。6.6 结合报错内容做好日志留存最后给一个比较务实的建议排查这类模板问题时不要只截一行报错要把Caused by这条完整链路上的所有关键信息都留在工单或笔记里。因为同一种Cannot find template location报错对应的根因可能有七八种而真正能帮你快速定位的往往是堆栈里那一段不起眼的Caused by或者配置类里一个被忽略的属性值。我自己在排查这个报错时也专门整理了一份排查小抄先看依赖、再看配置、然后查 jar、最后用 CommandLineRunner 验证四步下来基本可以命中九成以上的场景。希望这篇记录能帮你少走几趟弯路要是你手头的项目也跑在这套模板目录的坑里照着上面的链路过一遍大概率能找到答案。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。