4

Context

I have a situation where I do not control the main of the application nor the libraries on the class path. I am extending (via a plug-in API) an existing Swing application. The goal of my project is to make the information inside of that legacy application available through a HTTP API to then interface it via a web application.

Technical situation

The application in question has already Jackson in the class path in version 2.7. I am trying to use Spring Boot version 2, which is built against Jackson 2.8. When I start the server, Spring is unable to initialise because it is trying to configure Jackson stuff, using classes and methods that did not exist in 2.7.

What I would like to do is prevent Spring from automatically configuring anything Jackson-related and let me provide my own content negotiator.

I already tried

@SpringBootApplication(
    exclude = arrayOf(JacksonAutoConfiguration::class)
)

but it does not work.

Digging into Spring code, I see places where it is hard-coded inside of AllEncompassingFormHttpMessageConverter that is Jackson is on the class path, it should create new objects (that cause the exception in question), despite the fact I am excluding the Jackson configuration in my Spring Boot application.

private static final boolean jackson2Present =
        ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
                ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader());

and then later:

    if (jackson2Present) {
        addPartConverter(new MappingJackson2HttpMessageConverter());
    }

Project as it is now

Here is a link to GitHub, to the right branch.

https://github.com/Adeynack/finances/tree/spring--jackson-init-bypass/backend

The project in this state causes the error. Run ./gradlew server-standalone:run.

Complete error log

The errors I get are the following:

2017-11-30 17:15:54,475 | ERROR | main                                 | o.springframework.boot.SpringApplication | Application startup failed
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:137)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:327)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
    at com.github.adeynack.finances.backend.serverStandalone.FinancesServerStandalone.main(FinancesServerStandalone.kt:10)
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:114)
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:81)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:527)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:185)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:161)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:134)
    ... 8 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'httpPutFormContentFilter' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter]: Factory method 'httpPutFormContentFilter' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/exc/InvalidDefinitionException
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:583)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1249)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1098)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
    at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$83/517355658.getObject(Unknown Source)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:228)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:182)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:177)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAdaptableBeans(ServletContextInitializerBeans.java:159)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:80)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:232)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:219)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext$$Lambda$126/1009916891.onStartup(Unknown Source)
    at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:54)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5196)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter]: Factory method 'httpPutFormContentFilter' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/exc/InvalidDefinitionException
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:186)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:575)
    ... 26 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/exc/InvalidDefinitionException
    at org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter.<init>(AllEncompassingFormHttpMessageConverter.java:67)
    at org.springframework.web.filter.HttpPutFormContentFilter.<init>(HttpPutFormContentFilter.java:63)
    at org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter.<init>(OrderedHttpPutFormContentFilter.java:29)
    at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.httpPutFormContentFilter(WebMvcAutoConfiguration.java:161)
    at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$$EnhancerBySpringCGLIB$$f9ae12ce.CGLIB$httpPutFormContentFilter$1(<generated>)
    at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$$EnhancerBySpringCGLIB$$f9ae12ce$$FastClassBySpringCGLIB$$60a40e61.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
    at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$$EnhancerBySpringCGLIB$$f9ae12ce.httpPutFormContentFilter(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:155)
    ... 27 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 41 common frames omitted

Gradle file

I was able to recreate the conditions of this situation with this Gradle build file.

buildscript {

    ext {
        kotlin_version = '1.1.51'
        spring_boot_version = '2.0.0.M5'
        junit_version = '5.0.1'
        jackson_version = '2.7.9' // has to match Moneydance included Jackson version
    }

    repositories {
        mavenCentral()
        jcenter()
        maven { url 'https://repo.spring.io/libs-milestone' } // remove when using a stable version of Spring Boot 2.x
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.1'
        classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
    }
}

subprojects {

    apply plugin: 'java'
    apply plugin: 'kotlin'
    apply plugin: 'idea'
    apply plugin: 'org.junit.platform.gradle.plugin'
//    apply plugin: 'org.springframework.boot' // causes problems with Spring Boot 2. Unable to compile sub-projects.
    apply plugin: 'io.spring.dependency-management'

    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8

    repositories {
        mavenCentral()
        jcenter()
        maven { url 'https://repo.spring.io/libs-milestone' }
    }

    dependencies {

        //
        // PRODUCTION
        //

        // Kotlin and language extensions
        compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
        compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

        // Spring
        compile("org.springframework.boot:spring-boot-starter-web:$spring_boot_version") {
            // Totally excluding Jackson (version conflict between version included in Moneydance and the one used in SpringBoot
            exclude group: 'com.fasterxml.jackson.core'
            exclude group: 'com.fasterxml.jackson.datatype'
            exclude group: 'com.fasterxml.jackson.module'
        }

        compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"

        //
        // TEST
        //

        // JUnit
        testCompile("org.junit.jupiter:junit-jupiter-api:$junit_version")
        testRuntime("org.junit.jupiter:junit-jupiter-engine:$junit_version")

    }

    compileKotlin {
        kotlinOptions {
            jvmTarget = JavaVersion.VERSION_1_8
        }
    }

    compileTestKotlin {
        kotlinOptions {
            jvmTarget = JavaVersion.VERSION_1_8
        }
    }

    junitPlatform {
        platformVersion '1.0.0'
    }

}
Adeynack
  • 1,200
  • 11
  • 18
  • Instead of excluding `Jackson` auto-configs, try to add a newer version of `Jackson` library on the classpath – er-han Nov 30 '17 at 16:34
  • What build tool are you using and can you include the relevant config file (e.g. `pom.xml`)? – Robert Farley Nov 30 '17 at 16:39
  • I think specifying the required dependency in pom.xml will override the Spring Boot's default jackson dependency. – Amit K Bist Nov 30 '17 at 19:57
  • @er-han : There is a newer version of _Jackson_ in the fat-JAR of my plugin, which ends up in the classpath. But apparently, since those classes are already on the classpath, the "2nd ones to be loaded" are not used. – Adeynack Nov 30 '17 at 20:25
  • @AmitKBist : I added the _Gradle_ build file at the end of my original post. – Adeynack Nov 30 '17 at 20:33
  • @RobertFarley : I added the _Gradle_ build file at the end of my original post. – Adeynack Nov 30 '17 at 20:33
  • I am not gradle expert, but can you do by trying including the jackson dependency with the version which you are interested in, instead of excluding those dependencies. – Amit K Bist Nov 30 '17 at 20:35
  • I also added a link to the project, pointing at the right branch. – Adeynack Nov 30 '17 at 20:48

1 Answers1

2

The not-found class which causes the exception is InvalidDefinitionException

nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/exc/InvalidDefinitionException

I found out that InvalidDefinitionException class exists in Jackson since 2.9 as you can see here: InvalidDefinitionException

So I changed the jackson_version value to 2.9.2 (latest) in build.gradle and tried to run the project as you described and the project started to run with no exception.

Now to exclude Jackson you can still use this:

@SpringBootApplication(
    exclude = arrayOf(JacksonAutoConfiguration::class)
)
er-han
  • 1,859
  • 12
  • 20
  • As stated in the initial question: I have _no control_ over the version of Jackson that is used at runtime. It is `2.7` because it is already on the classpath. – Adeynack Dec 01 '17 at 12:18
  • 1
    My goal here is to tell _Spring_ not to automatically configure _Jackson_ content negotiators and let me provide one manually. – Adeynack Dec 01 '17 at 12:18
  • That will work well for the stand-alone server, which is just for the test. The REAL end product runs inside of _Moneydance_ which is the application in which I do not control nor the main nor the class path (where _Jackson_ 2.7 is already present). – Adeynack Dec 01 '17 at 12:21