How to use ContentService in DiscoverableListener

I’m developing a migration code using DiscoverableListener. In method onStartAppMigration I use ContentService to query page by id using below code:

private Optional<String> getLatestPageBody(Long pageId) {
    final Expansion historyExpansion = new Expansion("history");
    final Expansion storageBodyExpansion = new Expansion("body", new Expansions(new Expansion("storage")));
    final Optional<Content> pageOpt = contentService
            .find(historyExpansion, storageBodyExpansion)
    return pageOpt
            .filter(page -> page.getHistory().isLatest())
            .map(page -> page.getBody().get(ContentRepresentation.STORAGE).getValue());

The code above always return empty. But when using PageManager.getPage method with the same page id it actually return a page.

I did some debug and found that ContentService.find() always return NoopContentFinder which always return empty result. The reason is somehow there is no user in AuthenticatedUserThreadLocal.get() when the code run as a part of migration:

public <T> T createProxy(T target, Class<T> targetClass) {
    ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get();
    if (!this.confluenceAccessManager.getUserAccessStatus(currentUser).canUseConfluence()) {
        T noopFinder = NOOP_FINDERS.get(targetClass);
        if (noopFinder != null) {
            if (log.isDebugEnabled()) {
                log.debug("User {} does not have permission to use Confluence, returning a no-op Finder", currentUser == null ? "Anonymous" : currentUser.getKey());

            return AopUtils.createAdvisedProxy(noopFinder, targetClass, this.advisors);

        if (!NOOP_FINDERS_EXEMPTED.contains(targetClass)) {
            throw new IllegalArgumentException("Finder class has no no-op implementation:" + targetClass.getName());

    return AopUtils.createAdvisedProxy(target, targetClass, this.advisors);

Above is from class FinderProxyFactoryImpl class from SDK.

Does anyone know to how bypass this issue? Or the best option here is using PageManager in the context of app migration?

I believe below topics have the same root cause


From my experience developing migration routine using DiscoverableListener you dont have a user in the current context (bound to current thread).

I think I have seen somewhere a workaround to find “first user with admin privileges” and use that (setting thread local yourself before using Confluence user-aware APIs).

BTW, same goes with a transaction context - you dont have an “open session” there, need to inject TransactionTemplate if you want to have it