Hey guys!
I am struggling heavily to get access to theme information via the Confluence Theme API from Java code.
Background: We need to access the selected colors to cope with the requirements defined by supporting the Dark Theme feature. While it is very easy to find information to make adjustments to the client side, we needed to make adjustments to render the macros in the page editor and therefore need access to theme information from our Java code. Our solution is based on what Minh Tran describes in his answer to the question “How a Confluence customize macro image placeholder source generated?” in 2018.
Our approach: Looking at the documentation for Atlassian Theme it seems that we simply need to use the atlassian-theme-api (Public API that can be used by plugins/apps). We try to follow the Getting started with Atlassian Theme guide, specifically “Adding theme support to pages” (without the Soy template). We would like to inject com.atlassian.theme.api.request.RequestScopeThemeService and access the theme information via this Java client API.
We assume that we need to add this library with compile-scope. If we set provided-scope we get:
ERROR [UpmAsynchronousTaskManager:thread-2] [plugin.osgi.factory.OsgiPlugin] enableInternal Detected an error (BundleException) enabling the plugin 'de.smartics.example.confluence.confluence-theme-sample' : Unable to resolve de.smartics.example.confluence.theme-sample [287](R 287.0): missing requirement [de.smartics.example.confluence.theme-sample [287](R 287.0)] osgi.wiring.package; (osgi.wiring.package=com.atlassian.theme.api) Unresolved requirements: [[de.smartics.example.confluence.theme-sample [287](R 287.0)] osgi.wiring.package; (osgi.wiring.package=com.atlassian.theme.api)]. This error usually occurs when your plugin imports a package from another bundle with a specific version constraint and either the bundle providing that package doesn't meet those version constraints, or there is no bundle available that provides the specified package. For more details on how to fix this, see https://developer.atlassian.com/x/mQAN
-- url: /rest/plugins/1.0/ | userName: admin | referer: http://localhost:8080/plugins/servlet/upm | traceId: f7df305d51d951dd
With compile-time scope we can deploy the app:
From the POM (see the full POM on Bitbucket for Confluence 9):
...
<dependency>
<groupId>com.atlassian.theme</groupId>
<artifactId>atlassian-theme-api</artifactId>
<scope>compile</scope>
</dependency>
...
<banningExcludes>
...
<exclude>com.atlassian.theme:atlassian-theme-api</exclude>
</banningExcludes>
<instructions>
...
<Import-Package>*</Import-Package>
<Spring-Context>*</Spring-Context>
</instructions>
...
Albeit there are deploy-time warnings:
WARN [http-nio-8090-exec-9 url: /plugins/servlet/upm; user: admin] [atlassian.soy.impl.WebResourceTemplateSetFactory] findRequiredTemplates Some module descriptors are either missing or disabled; soy compilation may fail. Missing descriptors: [com.atlassian.lookandfeel.lookandfeel-plugin:styles]
-- url: /plugins/servlet/upm | userName: admin | referer: http://localhost:8080/plugins/servlet/upm | traceId: 681d5f9e0cce9e2a
WARN [http-nio-8090-exec-9 url: /plugins/servlet/upm; user: admin] [atlassian.soy.impl.WebResourceTemplateSetFactory] findRequiredTemplates Some module descriptors are either missing or disabled; soy compilation may fail. Missing descriptors: [com.atlassian.lookandfeel.lookandfeel-plugin:styles]
-- url: /plugins/servlet/upm | userName: admin | referer: http://localhost:8080/plugins/servlet/upm | traceId: 681d5f9e0cce9e2a
WARN [http-nio-8090-exec-9 url: /plugins/servlet/upm; user: admin] [atlassian.soy.impl.WebResourceTemplateSetFactory] findRequiredTemplates Some module descriptors are either missing or disabled; soy compilation may fail. Missing descriptors: [com.atlassian.auiplugin:template, com.atlassian.lookandfeel.lookandfeel-plugin:styles]
-- url: /plugins/servlet/upm | userName: admin | referer: http://localhost:8080/plugins/servlet/upm | traceId: 681d5f9e0cce9e2a
And when we access the injected service, it gets stuck.
confluence-1 | WARNING [Catalina-utility-4] org.apache.catalina.valves.StuckThreadDetectionValve.notifyStuckThreadDetected Thread [http-nio-8090-exec-2 url: /plugins/servlet/confluence/placeholder/macro-heading; user: admin] (id=[290]) has been active for [62,065] milliseconds (since [8/26/25, 9:34 AM]) to serve the same request for [http://localhost:8080/plugins/servlet/confluence/placeholder/macro-heading?definition=e...9&locale=en_US&version=2] and may be stuck (configured threshold for this StuckThreadDetectionValve is [60] seconds). There is/are [1] thread(s) in total that are monitored by this Valve and may be stuck.
confluence-1 | java.lang.Throwable
confluence-1 | at java.base/java.lang.Object.wait0(Native Method)
confluence-1 | at java.base/java.lang.Object.wait(Object.java:366)
confluence-1 | at org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryTemplate.execute(RetryTemplate.java:104)
confluence-1 | at org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceDynamicInterceptor.lookupService(ServiceDynamicInterceptor.java:427)
confluence-1 | at org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceDynamicInterceptor.getTarget(ServiceDynamicInterceptor.java:400)
confluence-1 | at org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker.invoke(ServiceInvoker.java:60)
confluence-1 | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
confluence-1 | at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137)
confluence-1 | at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124)
confluence-1 | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
confluence-1 | at org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor.invokeUnprivileged(ServiceTCCLInterceptor.java:70)
confluence-1 | at org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor.invoke(ServiceTCCLInterceptor.java:53)
confluence-1 | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
confluence-1 | at org.eclipse.gemini.blueprint.service.importer.support.LocalBundleContextAdvice.invoke(LocalBundleContextAdvice.java:57)
confluence-1 | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
confluence-1 | at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137)
confluence-1 | at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124)
confluence-1 | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
confluence-1 | at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
confluence-1 | at jdk.proxy292/jdk.proxy292.$Proxy3729.getPreferredLightTheme(Unknown Source)

There seems to be some issue with the dependencies that cannot be resolved properly at runtime. What are we doing wrong? Is there a library we need to add to the Import or as dependency? Is there a log level we can set to get to the bottom of this “thread-stuck” behavior? I tried to get hints from the source code of atlassian-theme-plugin but I all my attempts either do not compile or do not deploy or run into a thread-stuck at runtime.
I have create a somewhat minimal project on Bitbucket to reproduce the issue and to give full access to the code that does not work . There is a branch for Confluence version 10 while the main trunk is supporting version 9. I have added a macro to make sure that the issue is not related to have the injection with the Servlet Filter (spoiler alert: it is not).
What am I missing? Has someone managed to access the Theme API from a Confluence app’s Java code successfully?
Cheers,
Robert