3

I have a Spring Boot application that works as expected when ran with embedded tomcat, but I noticed that if I try to run it from an existing tomcat instance that I'm using with a previous project then it fails with a NoClassDefFoundError for a class that I don't use anywhere in my application.

I noticed in the /lib directory I had a single jar that contained a few Spring annotated classes, so as a test I cleaned out the /lib directory which resolved the issue. My assumption is that Spring is seeing some of the configurations/beans/imports on the classpath due to them existing in the /lib directory and either trying to autoconfigure something on its own, or is actually trying to instantiate some of these classes.

So then my question is - assuming I can't always fully control the contents of everything on the classpath, how can I prevent errors like this from occurring?

EDIT

For a little more detail - the class not being found is DefaultCookieSerializer which is part of the spring-session-implementation dependency. It is pulled into one of the classes in the jar located in /lib, but it is not any part of my application.

billoot
  • 77
  • 15
  • 1
    Full stacktrace could be useful... – 30thh Oct 03 '21 at 03:58
  • @30thh - I don't see how a stack trace could be helpful when the cause is known. Spring is trying to autoconfigure based on classes found in the tomcat /lib directory. Those classes aren't dependencies of this project leading to the `NoClassDefFoundError`. To my knowledge I can't prevent spring from pulling in tomcat /lib from within my project, I'd have to modify the tomcat configuration itself, leading to this post. – billoot Oct 03 '21 at 04:24
  • The question isn't "what is causing this problem", it is "are there any good solutions to prevent this problem from occurring". Looking back at my post that should be pretty obvious, pasting the relevant bit for you here so that hopefully you read it this time. `So then my question is - assuming I can't always fully control the contents of everything on the classpath, how can I prevent errors like this from occurring?` – billoot Oct 03 '21 at 05:23
  • 3
    There is no universal solution for Spring picking up a class in the server's classpath (see e.g. [this question](https://stackoverflow.com/q/68935574/11748454)). For you specific problem there might be several solutions, but a stack trace is necessary to see why Spring tried to load the class. – Piotr P. Karwasz Oct 03 '21 at 07:51

3 Answers3

2

Check for features provided by @EnableAutoConfiguration. You can explicitly configure set of auto-configuration classes for your application. This tutorial can be a good starting point.

Vasily Liaskovsky
  • 2,248
  • 1
  • 17
  • 32
  • Doesn't seem like has any impact for missing dependencies of classes autoloaded by tomcats /lib classloaders, and even if it did then it wouldn't allow for future proofing random classpath libraries. – billoot Oct 01 '21 at 15:43
2

You can remove the @SpringBootApplication annotation from the main class and replace it with an @ComponentScan annotation and an @Import annotation that explicitly lists only the configuration classes you want to load. For example, in a Spring boot MVC app that uses metrics, web client, rest template, Jackson, etc, I was able to replace the @SpringBootApplication annotation with below code and get it working exactly as it was before, with all functional tests passing:

@Import({ MetricsAutoConfiguration.class,
        InfluxMetricsExportAutoConfiguration.class,
        ServletWebServerFactoryAutoConfiguration.class,
        DispatcherServletAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
        JacksonAutoConfiguration.class,
        WebClientAutoConfiguration.class,
        RestTemplateAutoConfiguration.class,
        RefreshAutoConfiguration.class,
        ValidationAutoConfiguration.class
})
@ComponentScan
devatherock
  • 2,423
  • 1
  • 8
  • 23
0

The likely culprit of mentioned exception are incompatible jars on the classpath.

As we don't know with what library you have the issue we cant tell you the exact reason, but the situation looks like that:

  1. One of Spring-Boot autoconfiguration classes is being triggered by the presence of class on the classpath
  2. Trigerred configuration tries to create some bean of class that is not present in the jar you have (but it is in the specific version mentioned in the Spring BOM)

Version incompatibilities may also cause MethodNotFound exceptions.

That's one of the reasons why it is good practice not to run Spring Boot applications inside the container (make jar not war), but as a runnable jar with an embedded container.

Even before Spring Boot it was preferred to take account of libraries being present on runtime classpath and mark them as provided inside your project. Having different versions of the library on a classpath may cause weird ClassCastExceptions where on both ends names match, but the rest doesn't.

You could resolve specific cases by disabling autoconfiguration that causes your issue. You can do that either by adding exclude to your @SpringBootApplication or using a property file.

Edit: If you don't use very broad package scan (or use package name from outside of your project in package scan) in your Spring Boot application it is unlikely that Spring Boot simply imports configuration from the classpath. As I have mentioned before it is rather some autoconfiguration that is being triggered by existence of a class in the classpath.

Theoretical solution: You could use maven shade plugin to relocate all packages into your own package space: see docs. The problems is you'd have face:

  1. Defining very broad relocation pattern that would exclude JEE classes that need to be used so that container would know how to run your application.
  2. Relocation most likely won't affect package names used as strings in the Spring Boot annotations (like annotations @PackageScan or @ConditionalOnClass). As far as I know it is not implemented yet. You'd have to implement that by yourself - maybe as some kind of shade plugin resource processor.
  3. When relocating classes you'd have to replace package names in all relevant configuration located in the jars. Possibly also merge some of those.
  4. You'd also have to take into account how libraries that you use, or spring uses use package names or files.

This is definitely not a trivial tasks with many traps ahead. But if done right, then it would possibly allow you to disregard what is on the containers classpath. Spring Boot would also look for classes in relocated packages, and you wouldn't have those in ordinary jars.

Cezary Butler
  • 807
  • 7
  • 21