Search code examples
spring-bootspring-securityclassloaderkeycloak

Spring Boot / Security classloader issues with Keycloak run from terminal


I use Spring Boot and Spring Security in combination with Keycloak. The build tool is gradle.

When I run ./gradlew bootRun the application works flawless. If I use the resulting fat jar (i.e. java -jar myapp.jar) the application will boot but I encounter an exception when the application tries to invoke some keyloak stuff:

java.lang.IllegalArgumentException: org.keycloak.admin.client.resource.RealmsResource referenced from a method is not visible from class loader
    at java.base/java.lang.reflect.Proxy$ProxyBuilder.ensureVisible(Proxy.java:851) ~[na:na]
    at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:682) ~[na:na]
    at java.base/java.lang.reflect.Proxy$ProxyBuilder.<init>(Proxy.java:628) ~[na:na]
    at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$1(Proxy.java:426) ~[na:na]
    at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:327) ~[na:na]
    at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:203) ~[na:na]
    at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:424) ~[na:na]
    at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:999) ~[na:na]
    at org.jboss.resteasy.client.jaxrs.ProxyBuilder.proxy(ProxyBuilder.java:79) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
    at org.jboss.resteasy.client.jaxrs.ProxyBuilder.build(ProxyBuilder.java:131) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
    at org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:93) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
    at org.keycloak.admin.client.Keycloak.realms(Keycloak.java:114) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]
    at org.keycloak.admin.client.Keycloak.realm(Keycloak.java:118) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]

So I figured out that there must be something wrong how I start the application in the terminal. I found this official site, which explains how to run an Spring application from terminal. So I tried all of the solutions including:

$ unzip -q myapp.jar
$ java org.springframework.boot.loader.JarLauncher

but I get the same error.

After 2 days of searching and experimenting I'm out of any ideas.

So my question is basically: Does anyone have any idea how to solve this problem?

My knowledge regarding the classloader is also limited - so any pratical hints in this direction are very welcomed as well.

EDIT (added build.gradle): There were some Jackson problems that's why the Spring web dependencies are included directly (without the starter and therefor without Jackson). Here is the build.gradle:

buildscript {
   ext {
      kotlinVersion = '1.2.20'
      springBootVersion = '1.5.7.RELEASE'
      keycloakVersion = '3.4.3.Final'
      restEasyClientVersion = '3.1.4.Final'
      postgresServerVersion = '10.0'
      postgresJdbcDriverVersion = '42.1.4'
      spekVersion = '1.1.5'
      jacksonVersion = '2.8.10'
      javaxWsRsVersion = '2.1'

      logbackVersion = '1.2.3'
      slf4jVersion = '1.7.25'
   }
   repositories {
      mavenCentral()
      jcenter()
      //spring dev
      maven { url 'https://repo.spring.io/snapshot' }
      maven { url 'https://repo.spring.io/milestone' }
      //Kotlin dev
      maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
      //gradle plugins
      maven { url "https://plugins.gradle.org/m2/" }
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
      classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
      classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
      classpath('com.bmuschko:gradle-docker-plugin:3.2.0')

      //for tests
      classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0'
   }
}

allprojects {

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

}

subprojects {

   repositories {
      maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
      // for tests
      maven { url "http://dl.bintray.com/jetbrains/spek" }
   }

   // for kotlin
   apply plugin: 'kotlin'
   // for tests
   apply plugin: 'org.junit.platform.gradle.plugin'

   apply plugin: 'kotlin-spring'
   apply plugin: 'org.springframework.boot'
   apply plugin: 'io.spring.dependency-management'

   // for tests
   junitPlatform {
      filters {
         engines {
            include 'spek'
         }
      }
   }

   sourceCompatibility = 1.9

   dependencies {
      // kotlin
      compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
      compile("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}")
      compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
      // jackson
      compile "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
      compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
      compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:${jacksonVersion}"
      compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:${jacksonVersion}"
      compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.2"

      // Java WS RS
      compile "javax.ws.rs:javax.ws.rs-api:${javaxWsRsVersion}"
      // for tests
      testCompile "org.jetbrains.spek:spek-api:${spekVersion}"
      testRuntime "org.jetbrains.spek:spek-junit-platform-engine:${spekVersion}"
      testCompile ("org.jetbrains.spek:spek-api:${spekVersion}") {
         exclude group: 'org.jetbrains.kotlin'
      }
      testRuntime ("org.jetbrains.spek:spek-junit-platform-engine:${spekVersion}") {
         exclude group: 'org.junit.platform'
         exclude group: 'org.jetbrains.kotlin'
      }
      // spring security
      compile('org.springframework.boot:spring-boot-starter-security')
      testCompile('org.springframework.security:spring-security-test')
      // begin: spring web without jackson
      compile('org.springframework.boot:spring-boot-starter')
      compile('org.springframework.boot:spring-boot-starter-tomcat')
      compile('org.springframework:spring-web')
      compile('org.springframework:spring-webmvc')
      testCompile('org.springframework.boot:spring-boot-starter-test')
      // end: spring web without jackson
      // Keycloak
      compile("org.keycloak:keycloak-spring-security-adapter:${keycloakVersion}")
      compile("org.keycloak:keycloak-spring-boot-adapter:${keycloakVersion}")
      compile("org.keycloak:keycloak-tomcat8-adapter:${keycloakVersion}")
      compile("org.keycloak:keycloak-admin-client:${keycloakVersion}")
      compile("org.jboss.resteasy:resteasy-client:${restEasyClientVersion}")
      compile("org.jboss.resteasy:resteasy-jackson2-provider:${restEasyClientVersion}")

   }

   compileKotlin {
      kotlinOptions.jvmTarget = '1.8'
      kotlinOptions.allWarningsAsErrors = true
   }
   compileTestKotlin {
      kotlinOptions.jvmTarget = '1.8'
   }
}

Solution

  • The problem was the underlying class loader of ForkJoinPool.commonPool which is used by CompletableFuture.supplyAsync.

    Because the problem and the solution is complex please refer to my other question for better understanding.

    This question is only kept alive for this cross reference (and may hopefully lead others to the correct solution).