Confluence 11.0 release beta available now

This week we released the first beta for Confluence 11. There are changes in this release that may directly affect third-party apps.

New in 11.0

To find out what’s in this release, check out the Confluence 11.0 beta release notes.

Get started

Download the beta from our site to start testing your app for compatibility with the changes. Be sure to take a look at our Preparing for Confluence 11.0 guide to find out what you need to do to make your app compatible. We’ll update that page regularly, so keep an eye on it to see any changes.

Reminder: for future release updates, make sure you visit the Atlassian changelog, and consider subscribing to the RSS feed.

The Atlassian Confluence team

I am getting this error during startup

Caused by: org.osgi.framework.BundleException: Unable to resolve foo.app-confluence-common [294](R 294.0): missing requirement [foo.app-confluence-common [294](R 294.0)] osgi.wiring.package; (osgi.wiring.package=com.atlassian.user.security.password) Unresolved requirements: [[foo.app-confluence-common [294](R 294.0)] osgi.wiring.package; (osgi.wiring.package=com.atlassian.user.security.password)]

I am importing and using:

import com.atlassian.user.security.password.Credential;

Did this move to a separate dependency I need to add as compile scope? It worked with Confluence 10 and 11 m55.


Second question: Can you provide more docs on the "Asynchronous Macro Execution" feature, thanks. I would love to test it.

Is there a special Jira version we need to compile against so that isCancelled(), isCachable() . a.s.o can be implemented? Currently my app works fine with Confluence 10 and 11. I want to avoid to have a hard breaking change again between the major versions making it hard for users to upgrade.

thanks.

As to what I could find out I need to opt in every macro:

curl -i -X PUT "http://myhost:8090/rest/asyncmacros/latest/execution-controls" \
  -u "admin:admin" \
  -H "Content-Type: application/json" \
  -d '{
    "macroName": "my-macro-name (from atlassian-plugin.xml)",
    "macroKey": "mykey",
    "enabled": true
  }'

Then is this all I need to do to my Macro class?

    public boolean isCacheable() {
        return true;
    }

 
    public boolean isCancellable() {
        return true;
    }

Hey there,

it seems for Confluence that background color in the dark mode was changed and adjusted to the color used in Cloud. Before the background color was #1d2125 and now it seems to be #1F1F21.

The problem is that our macro still renders the old, more greener color #1d2125, which looks terrible in Confluence 11.

Could you please advice us, what CSS variable we are supposed to use now to get background color Confluence uses? Thank you!

Best,
Cedric

Hey there,

What I also noticed is that there are way less logs. For example in previous milestone versions I could see when a new plugin JAR was detected and trying to be installed. Now I see no logs about this at all.

Is it supposed to be like that? How can I go back to the old logging configuration? Thank you!

Best,
Cedric

Hi @PumpeCedric ,

Thanks for flagging this, and good catch. Confluence 11 changed how logging is configured:

  • Earlier versions read /confluence/WEB-INF/classes/log4j.properties.
  • Confluence 11 uses Log4j 2 and reads /confluence/WEB-INF/classes/log4j2.xml instead.

The old properties file is no longer used, so any customisation you made there stops taking effect after the upgrade. That is almost certainly what you are seeing. The messages about a new plugin JAR being detected and installed are logged at DEBUG level by the plugin framework, and the shipped default for com.atlassian.plugin is INFO in both the old and the new configuration. So if you could see them before, the DEBUG level must have come from your own edit to log4j.properties. Those edits are not migrated automatically, so they need to be re-applied in the new XML file.

To get them back, add this inside the element of log4j2.xml:

<Logger level="debug" name="com.atlassian.plugin"/>

The general translation is straightforward:

# log4j.properties (old)
log4j.logger.com.atlassian.plugin=DEBUG
log4j.additivity.com.atlassian.plugin=false
<!-- log4j2.xml (new) -->
<Logger level="debug" name="com.atlassian.plugin" additivity="false">
    <AppenderRef ref="confluencelog"/>
</Logger>

If you leave out additivity and AppenderRef, the logger inherits the root appenders, which is usually what you want.

Two things worth knowing: The confluencelog appender has a DEBUG threshold, so DEBUG messages reach atlassian-confluence.log without further changes. If you ever need TRACE, you also have to relax that appender’s , otherwise the messages are filtered out before they are written.

If you would rather not edit files, or want to try a level temporarily, you can set it from Administration > General Configuration > Logging and Profiling. Changes there apply immediately and do not need a restart, though they are reset when Confluence restarts.

One suggestion for the upgrade in general: it is worth diffing your old log4j.properties against the defaults before you upgrade, so you have a list of exactly what you customised. Then re-apply just those entries to log4j2.xml. Aside from the config format, the shipped logging defaults in Confluence 11 are the same as before, so anything missing should come down to a customisation that needs porting.

Hope that helps, and let us know if the plugin logs still do not show up after that.

Hey @JefferyXie

thank you very much for your reply!

As far as I am aware, we never had a properties file to manage logs.

I should have mentioned that I’m using the “atlas-debug” command. As the name suggests I am expecting the command to set the logging properties to debug automatically.
Do you agree? If yes, do I need to update something else?

Thank you!

Hi @PumpeCedric,
Thanks for the information. We tried to reproduce this by running atlas-debug against the conf 11 beta 3 with JVM arguments:

-Xrunjdwp:transport=dt_socket,address=5005,suspend=n,server=y
-Datlassian.dev.mode=true

I generated a fresh project with atlas-create-confluence-plugin, pointed it at 11.0.0-beta3, ran atlas-debug, and rebuilt the JAR to trigger a reload. The plugin install
messages do still appear in atlassian-confluence.log:

INFO [QuickReload - DirectoryWatcher] [PluginInstaller] promiseToInstall
     Changes noticed in '.../qrtest-1.0.0-SNAPSHOT.jar'...
INFO [QuickReload - Stable File Watcher] [PluginInstaller] loopWaitingForStableFiles
     'qrtest-1.0.0-SNAPSHOT.jar' is indeed an Atlassian plugin and will be installed shortly...
INFO Quick Reload Finished (72 ms) - 'qrtest-1.0.0-SNAPSHOT.jar'

For comparison, this is exactly what we ran:

  • Confluence 11.0.0-beta3
  • atlas-version reports SDK 9.1.1 with AMPS 9.1.1, but we set amps.version to 9.13.0 in the POM
  • QuickReload 6.3.0, which AMPS installed into WEB-INF/atlassian-bundled-plugins
  • JDK 25, which the beta requires.

If your setup differs on the AMPS version, that alone could explain it, because QuickReload comes from AMPS and older versions of it behave differently around logging. Could you post your atlas-version output and the QuickReload version from your startup log?

Two questions so we’re looking at the same thing:

  1. first, does your app still use log4j 1.x directly, as in org.apache.log4j.Logger.getLogger(...)? This matters because org.apache.log4j is now an internal package in Confluence 11 and is no longer available to apps. A minimal bundle that does nothing but declare Import-Package: org.apache.log4j fails to enable on the beta:
WARN [osgi.hook.dmz.DmzResolverHook] filterMatches
     Package org.apache.log4j is internal and is not available for export to plugin <key>
BundleException: Unable to resolve <key>: missing requirement
     osgi.wiring.package=org.apache.log4j
  1. which message exactly are you looking for? Since the QuickReload lines above still work on our side, knowing the specific text you used to see would tell us whether it’s the same logger or something else. The startup section of your atlassian-confluence.log would help too. If you want to force those messages on in the meantime, add this to log4j2.xml:
<Logger level="info" name="com.atlassian.labs.plugins.quickreload"/>

Administration > General Configuration > Logging and Profiling still exists in 11 and applies the same change without a restart.

Hi @JefferyXie

ATLAS Version:    9.1.1
AMPS Version:     9.1.1

<confluence.version>11.0.0-beta3</confluence.version>
<confluence.data.version>11.0.0-beta3</confluence.data.version>
<amps.version>9.13.0</amps.version>

[INFO] Configured Artifact: com.atlassian.labs.plugins:quickreload:6.3.0:jar

There are no usages of log4j in my project. However, when I spin up Confluence Beta 3, I see these logs:

[INFO] 2026-09-14T08:11:39.673120Z ThreadPoolAsyncTaskExecutor::Thread 3 INFO Starting configuration org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration@1687032...
[INFO] 2026-09-14T08:11:39.673922Z ThreadPoolAsyncTaskExecutor::Thread 3 INFO Configuration org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration@1687032 started.
[INFO] 2026-09-14T08:11:39.674648Z ThreadPoolAsyncTaskExecutor::Thread 3 INFO Stopping configuration org.apache.logging.log4j.core.config.DefaultConfiguration@4046d5e...
[INFO] 2026-09-14T08:11:39.674794Z ThreadPoolAsyncTaskExecutor::Thread 3 INFO Configuration org.apache.logging.log4j.core.config.DefaultConfiguration@4046d5e stopped.

What I would like to see and what I see when I go back to an earlier milestone version (e.g. m221) I am looking for these:


[INFO] 10:29:16,138 INFO [QuickReload - DirectoryWatcher] [quickreload.install.PluginInstaller] promiseToInstall Changes noticed in '[PROJECT_PATH]/target/[PLUGIN_ARTIFACT].jar'...
[INFO] 10:29:16,432 INFO [QuickReload - Stable File Watcher] [quickreload.install.PluginInstaller] waitUntilFileIsStableFor Waiting for '[PLUGIN_ARTIFACT].jar' to become stable ([FILE_SIZE] bytes)...
[INFO] 10:29:16,960 INFO [QuickReload - Stable File Watcher] [quickreload.install.PluginInstaller] waitUntilFileIsStableFor '[PLUGIN_ARTIFACT].jar' now appears to be stable ([FILE_SIZE] bytes)
[INFO] 10:29:16,960 INFO [QuickReload - Stable File Watcher] [quickreload.install.PluginInstaller] loopWaitingForStableFiles '[PLUGIN_ARTIFACT].jar' is indeed an Atlassian plugin and will be installed shortly...
[INFO] 10:29:16,960 INFO [QuickReload - Plugin Installer] [quickreload.install.PluginInstallerMechanic] downArrowMsg

Starting Quick Reload - '[PROJECT_PATH]/target/[PLUGIN_ARTIFACT].jar'....

[INFO] 10:29:17,138 INFO [QuickReload - Plugin Installer] [plugin.loaders.ScanningPluginLoader] loadFoundPlugins No plugins found to be installed
[INFO] 10:29:17,405 INFO [FelixDispatchQueue] [container.felix.FelixOsgiContainerManager] bundleChanged Installed bundle [PLUGIN_BUNDLE_ID] ([BUNDLE_ID])
[INFO] 10:29:17,442 INFO [FelixDispatchQueue] [container.felix.FelixOsgiContainerManager] bundleChanged Resolved bundle [PLUGIN_BUNDLE_ID] ([BUNDLE_ID])
[INFO] 10:29:17,442 INFO [QuickReload - Plugin Installer] [plugin.manager.PluginEnabler] actualEnable Resolving 1 plugins
[INFO] 10:29:17,442 INFO [QuickReload - Plugin Installer] [plugin.manager.PluginEnabler] actualEnable Enabling 1 plugins: [[PLUGIN_BUNDLE_ID]]
[INFO] 10:29:17,449 INFO [FelixDispatchQueue] [container.felix.FelixOsgiContainerManager] bundleChanged Started bundle [PLUGIN_BUNDLE_ID] ([BUNDLE_ID])
[INFO] 10:29:17,662 INFO [QuickReload - Plugin Installer] [plugin.manager.PluginEnabler] actualEnable Waiting for 1 plugins to finish ENABLING: [[PLUGIN_BUNDLE_ID]]
[INFO] 10:29:17,663 INFO [QuickReload - Plugin Installer] [plugin.manager.PluginEnabler] isFinished Only one plugin left not enabled. Resetting the timeout to 30 seconds.
[INFO] 10:29:17,663 INFO [QuickReload - Plugin Installer] [plugin.util.WaitUntil] invoke Plugins that have yet to be enabled: (1): [[PLUGIN_BUNDLE_ID]], 300 seconds remaining
[INFO] 10:29:17,688 WARN [ThreadPoolAsyncTaskExecutor::Thread 15] [[PLUGIN_BUNDLE_ID]] logInDevMode Spring context started for bundle: [PLUGIN_BUNDLE_ID] id([BUNDLE_ID]) v([VERSION]) file:[SERVER_PATH]/plugins-cache/[PLUGIN_CACHE_FILE].jar
[INFO] 10:29:18,668 INFO [QuickReload - Plugin Installer] [plugin.manager.PluginEnabler] isFinished Plugin '[PLUGIN_BUNDLE_ID]' is now ENABLED

I would like to add that I’m using macOS.
Hope that helps.

Hi @PumpeCedric,
I have done the investigation, and it’s on our side. Thanks for pushing on this.

In the beta:

  • the development env logging configuration sends the main log output to <confluence-home>/logs/atlassian-confluence.log instead of the console.
  • Nothing is being dropped, the messages you’re looking for are all in that file, they just no longer reach your atlas-debug terminal.

That happened when we converted the development log4j.properties to log4j2.xml and the root logger ended up pointing at the file appender rather than the console one. We’re fixing it so that the development environment logs to the console again in the beta/release version.

Two ways to work around it in the meantime so it does not block your plugin development:

  1. The quickest is to just read the file: tail -f target/confluence/home/logs/atlassian-confluence.log. Everything is there, including the QuickReload lines about a JAR being detected and installed.

  2. If you want the console output back, run atlas-debug with your own logging configuration.

    1. Copy the shipped file out of the product: cp target/confluence/webapp/WEB-INF/classes/log4j2.xml my-log4j2.xml
    2. then add one line to its root logger:
<Root level="warn">
  <AppenderRef ref="confluencelog"/>
  <AppenderRef ref="errorlog"/>
  <AppenderRef ref="consolelog"/>  <--------- add this line to output to console
</Root>
  1. and point the JVM at it: atlas-debug --jvmargs "-Dlog4j2.configurationFile=file:///absolute/path/to/my-log4j2.xml"

I tested it against 11.0.0-beta3 and the console output comes back in full, your QuickReload messages included. Keeping the file outside target also means it survives atlas-clean. One thing that won’t help is Administration > General Configuration > Logging and Profiling, since that changes log levels rather than where the output goes.

Thanks again for your patience through the back and forth, and for spotting this.

Jeffery

Correct. While there’s an AUI 11 visual refresh in Confluence 11.0, this particular dark theme background colour change already happened in Confluence 10.2.

The best way to stay aligned with Confluence is to use AUI variables that map to Atlassian design tokens for DC apps. For background it’s --aui-body-background.

Hi @clouless - We investigated this and found the issue. Visibility of com.atlassian.user.security.password was tightened which causes this issue - We will have this fixed for the next beta which will be out soon! Thanks for raising this!

As for Asynchronous Macro Execution, full usage docs are being finalised as we speak, I will let you know once they are up on the EAP documentation space! For now here is the details:

How cancellation works

  1. The REST call publishes a Confluence event.

  2. The CancellationListener receives the event and calls the canceller.

  3. The canceller sends Thread.interrupt() to the execution thread and sets a cancellation flag in the thread-local ExecutionCancellationContext.

  4. If the macro implements isCancellable() = true and handles the signal, it stops promptly.

  5. If the macro does not support cancellation, the interrupt/flag is issued but the macro continues running until it finishes or times out naturally.

Declaring isCancellable() = true is a contract — you are promising that your macro will stop promptly when a cancellation signal is received. If you return true but ignore the signals, administrators will believe the cancel worked (the API returns success), but your macro keeps running, wasting thread resources.

Your macro must handle both signals the framework sends:

Signal 1 — Thread interruption (for blocking I/O)

:warning: Never swallow InterruptedException without calling Thread.currentThread().interrupt(). Swallowing it makes the interrupt invisible to the rest of the call stack and breaks cancellation for any code above yours.

Signal 2 — Cancellation flag (for CPU-bound loops or non-blocking code)

ExecutionCancellationContextThreadLocal.isCancelled() is safe to call from anywhere on the execution thread — it returns false (not an exception) if called outside an executor thread.

Recommended pattern (combining both)

@Override
public String execute(Map<String, String> params, String body, ConversionContext context)
throws MacroExecutionException {
for (Item item : getItems(params)) {
// Check flag for CPU-bound code
if (ExecutionCancellationContextThreadLocal.isCancelled()) {
return "<p>Execution cancelled.</p>";
}
try {
processItem(item); // may do I/O
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore
return "<p>Execution cancelled.</p>";
}
}
return renderResult();
}

Implementing result caching

Cache key semantics: Results are keyed by (macroParameters + macro body hash + userId). This means:

  • Results are never shared across users — each user has their own cache entry

  • Two calls with different parameters always produce different cache entries

  • Body content changes invalidate the cache entry

Before enabling caching, confirm all of these:

Check Why
Output depends only on parameters, body, and the current user If the output also depends on external state (database, current time, permissions), cached results may be incorrect
Output is safe to serve up to 60 seconds stale (default TTL) Adjust atlassian.macros.result.cache.expire.after.write.millis if your data changes more frequently
The macro does not produce side effects that must run every page view Caching skips execution entirely — side effects are not triggered on cache hits
The macro does not include user-specific sensitive data from external sources While the cache is per-user, confirm that user-identifying data from outside the parameters/body is not embedded in the output

The cache is node-local. In a clustered setup, users hitting different nodes will get independently cached results with potentially different staleness.

Thanks!
Edward

Thanks for the info! We implemented your suggestion and it works perfectly fine. Thank you!

Thanks for the explanation. My app does no heavy lifting in the execute() method. Only during PDF export. In the normal Browser rendering view the execute() simply provides some HTML with data attributes so my ReactApp can hook in and render the macro. A REST API Call is then made at the initial render of the macro.

Is there a way to “cancel” this REST API Call when the page execution is cancelled?
Meaning: Is there some JS emitter or some other logic to be able to cancel these calls?

Or is it enough to implement the execute() logic you provided and simply not render the div with data attributes of the frontend to hook in, but simply “

macro execution cancelled
”.

I think a lot of other apps work this way too. Would be great to have a best practice for that too :slight_smile:

A third thing I noticed is this error:

2026-09-15 22:01:33,250 WARN [http-nio-8090-exec-7 url: /rest/tinymce/1/macro/preview; 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]

I get it when:

  • Insert a macro (macro browser opens, preview works)
  • Insert into page, edit the macroBody
  • Click on macro again, macro browser opens again => 400 Error

Hi @clouless - I replicated the warning with the Page properties report macro, and can confirm it is harmless and should not affect functionality. I was unable to reproduce the 400 however, do you have more details on the cause of the 400 (logs, which macro)?

thanks for checking. I retested it with beta4 and cannot reproduce it anymore as well. The logs showed no entries when this happened and it was a tomcat like http 400 message. Very strange.

Hi @clouless — the docs are now published: Asynchronous macro execution. Worth a read in full; answers to your specific questions below.

The method you’re missing. isCacheable() and isCancellable() don’t make a macro async. The gate is:

@Override
public boolean isConcurrentExecutionSafe() {  // since 11.0.0, defaults to false
    return true;
}

Async dispatch requires all three: the dark feature enabled, your macro declaring itself safe, and an admin enabling it for that macro. The feature is experimental in 11.0 and off by default.

Correct curl request

curl -i -X PUT "http://myhost:8090/rest/asyncmacros/latest/execution-controls/" \
  -u "admin:admin" -H "Content-Type: application/json" \
  -d '{"macroName":"my-macro-name","isAsync":true,"deduplication":true,"rateLimit":10,"timeLimit":60000}'

A 200 OK doesn’t mean it’s active. Please check GET response for the read-only supportedByMacro field.

Compiling for 10 and 11. No special version needed and no breaking change for your users. All three are default methods.

Where the gain comes from : worth measuring first. The framework parallelises execute() and nothing else. If yours returns a placeholder in a few milliseconds, there’s little to parallelise, and the dispatch overhead may outweigh the benefit. The REST call your React app makes afterwards sits outside the framework entirely.

So for the placeholder plus REST pattern, and you’re right that it’s common. We would suggest measuring execute() first and opt in where the time is actually being spent. If your macro is majorly server-side work, so that’s the more promising candidate.

Cancelling the frontend REST call. There’s no hook for it. Cancellation is server-side only: it interrupts the execution thread and sets a flag your execute() can poll. Your React app’s call is an independent request the framework doesn’t know about. Rendering “cancelled” instead of the mount point would work in principle, but if execute() returns in milliseconds there’s little window for a cancel to arrive.