Hi. Im currently using the java the jira-rest-java-client-app dependency in my java spring boot application to automate creating jira issues/tickets, etc.. Ive been successfully using this for a year but not all of a sudden (new builds) are no longer working. Im able to build and deploy my application but when the code to call jira gets invoked, i get the following error.
2026-01-28 22:17:21,698 ERROR org.apache.juli.logging.DirectJDKLog - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.NoClassDefFoundError: com/atlassian/sal/api/executor/ThreadLocalContextManager] with root cause
java.lang.ClassNotFoundException: com.atlassian.sal.api.executor.ThreadLocalContextManager
I use gradle and kotin. I have the following dependency in my build.gradle.kts.
implementation("com.atlassian.jira:jira-rest-java-client-app:5.2.0")
I have made no recent changes to the dependencies so im not sure how all of a sudden this just started happening when it use to work. The only thing i can think is the this dependency and version was overriden with new source code by atlassian or possible a dependency this library uses was changed.
I have tried directly adding multiple versions of the sal-api dependency. I have tried downgrading and upgrading the version of jira-rest-java-client-app. Nothing seems to fix the issue.
The stack trace points to this line of code
return new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(uri.build(),
username, password);
Hi @Cory Boslet
com.atlassian.sal.api.executor.ThreadLocalContextManager is in Atlassian SAL, and the error says that your runtime classpath contains a missing or an older sal-api.jar
The JRJC core module depends on com.atlassian.sal:sal-api and for the JRJC 5.2 the dependency should be sap-api:3.0.7
I don't know why it started suddenly but in Gradle it may occur when something change in your build which ends up with resolving the dependency graph.
Normally, I'd suggest not using jira-rest-java-client-app in your service. This is the wrapper app. I'd suggest using jira-rest-java-client-core.
implementation("com.atlassian.jira:jira-rest-java-client-core:5.2.0")
implementation("com.atlassian.jira:jira-rest-java-client-api:5.2.0")
also you can force sal-api version
implementation("com.atlassian.sal:sal-api:3.0.7")
configurations.all {
resolutionStrategy.force("com.atlassian.sal:sal-api:3.0.7")
}
I hope that helps or at least shed a light
@Tuncay Senturk _Snapbytes_ Thank you for your response. Yes, im aware that the error indicates that the class in missing from the classpath but just dont understand how this is possible, as jira-rest-java-client-app contains the sal-api dependency. So im indirectly already importing it but just to be sure i tried explicitly adding it.. A dependency conflict could be forcing an incorrect version, like you mentioned, but this would have been evident when i attempted to use the library, over 1 year ago. But like i mentioned, this was previously working and there have been NO changes to my build.gradle.kts in months. I also printed out my effective dependency graph and see it present. Also, I tried to search the sal-api.jar for that class but did not find but maybe the commend i use was wrong.
I tried your suggestions but continuing to see the same error at runtime. Are these code repositories opensource? I havnt been able to find them anywhere.
Here is my complete build.gradle.kts
plugins {
java
id("org.springframework.boot") version "3.2.4"
id("io.spring.dependency-management") version "1.1.3"
id("org.openapi.generator") version "6.5.0"
}
group = "com.ttrace.wizard"
version = "0.0.1-SNAPSHOT"
description = "ttrace-wizard"
java.sourceCompatibility = JavaVersion.VERSION_19
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://repo.spring.io/snapshot") }
//Place your username and jfrog token in your gradle.properties
// jfrogUsername=
// jfrogPassword=
maven {
name = "jfrog"
url = uri("OMMITTED_FOR_SECURITY_REASONS")
authentication {
create<BasicAuthentication>("basic")
}
credentials(PasswordCredentials::class)
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.retry:spring-retry")
implementation("io.swagger.core.v3:swagger-annotations:2.1.9")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.json:json:20230227")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("org.webjars:bootstrap:5.3.1")
implementation("org.springframework.boot:spring-boot-starter-tomcat")
implementation("org.apache.httpcomponents:httpclient:4.5.13"){
exclude(group = "commons-logging", module = "commons-logging")
}
implementation("org.apache.httpcomponents.client5:httpclient5:5.2.1")
implementation("org.checkerframework:checker-qual:3.8.0")
implementation("com.phaos:PSE_Lite:2.2.3")
implementation(platform("org.mongodb:mongodb-driver-bom:5.6.1"))
implementation("org.mongodb:mongodb-driver-sync")
implementation("org.eclipse.angus:angus-mail:2.0.3")
implementation("io.github.hkarthik7:azd:6.0.2")
implementation("org.openapitools:jackson-databind-nullable:0.2.4")
implementation("javax.annotation:javax.annotation-api:1.3.2")
implementation("com.google.code.findbugs:jsr305:3.0.2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.18.1")
implementation("com.atlassian.jira:jira-rest-java-client-core:5.2.0")
implementation("com.atlassian.jira:jira-rest-java-client-api:5.2.0")
implementation("com.atlassian.sal:sal-api:3.0.7")
configurations.all {
resolutionStrategy.force("com.atlassian.sal:sal-api:3.0.7")
}
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:1.32.1-alpha")
implementation("io.opentelemetry:opentelemetry-sdk:1.32.0")
}
openApiGenerate {
generatorName.set("java")
library.set("resttemplate")
inputSpec.set("$rootDir/src/main/resources/swagger.v3.json")
outputDir.set(layout.buildDirectory.dir("generated").get().asFile.absolutePath)
modelPackage.set("com.myapp.atlassian.rest.client.model")
apiPackage.set("com.myapp.atlassian.rest.client.api")
invokerPackage.set("com.myapp.atlassian.rest.client.invoker")
configFile.set("src/main/resources/config.json")
}
configure<SourceSetContainer> {
named("main") {
java.srcDir(layout.buildDirectory.dir("generated/src/main/java").get().asFile)
}
}
tasks.compileJava {
dependsOn("openApiGenerate")
}
tasks.withType<Test> {
useJUnitPlatform()
}You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Cory Boslet
I know it's frustrating, and this is what we face regularly, things broke all of a sudden.
Anyway this narrows the problem (hopefully):
Either it's not running with the sal-api jar you think you are (even if Gradle shows it in the graph)
or
the sal-api jar you have on disk is not the real com.atlassian.sal:sal-aoi contents (sometimes I face weird things, such as the content of the jar became empty, so please check the jar file in your local repository ensuring that it's not empty (Because you have a private JFrog repo, it’s possible your environment started resolving that module from a different place or your proxy cached a bad artifact and it may have become empty. I usually locate the file, change the extension to zip and see the contents, check if the class is there. If you see the class in it, all is fine, change the extension back to jar. Otherwise delete the jar so that the build will re-download the fresh one, make sure it's fine now. If not, that's the problem).
It may also be because of a Spring Boot fat jar and the sal jar is not actually inside the runtime artifact.
ThreadLocalContextManager definitely exists in SAL (you can check the Atlassian SAL javadoc)
So if it can’t find it in sal-api.jar sal-api.jar”, you’re almost certainly inspecting the wrong jar (or a wrong/empty one).
As a proof which sal-api it's actually running, build your jar and run a couple of commands to see what's packaged
jar tf build/libs/*.jar | grep -i sal-api
jar tf build/libs/*.jar | grep ThreadLocalContextManager
As far as I remember, in a Spring Boot fat jar, dependencies live under BOOT-INF/lib/. If SAL isn’t there, this is the reason. If SAL is there, extract and inspect it If you see ThreadLocalContextManager, the jar you packaged is not the one that contains the class.
Why it changed all of a sudden? Even with no build.gradle.kts changes, plugin update, repository/proxy change, cache refresh, transitive dependency (by something else) may change your runtime classpath.
Sorry for the long response, but the problem is big and it's hard to figure out the right path.
Cheers
Tuncay
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.