ContentPropertyService is null in Thread

We are using the following Api in our plugin development via dependency injection in order to get a content property of key" docid" of defined pages:
This service is working well except if it is inside a Thread. It will be null in that case.
Below is the method I’m using:

public JsonContentProperty getDocIdJsonContentProperty(Page page) {
 Optional<JsonContentProperty> jsonProperty = contentPropertyService
 return jsonProperty.orElse(null);

Its call is in the following:

 Page page = pageManager.getPage(123);
 if (page != null) {
 JsonContentProperty jsonContentProperty = getDocIdJsonContentProperty(page);
 if (jsonContentProperty != null) {
 System.out.println("jsonContentProperty: " + jsonContentProperty);

This code is working perfectly and I"m able to get the value of jsonContentProperty.
If it is inside a Thread as in below, jsonContentProperty returns NULL:

 new Thread(() -> {
 // above code

Can you please help as I’m only using this service provided by Confluence in order to get content properties and I don’t have any extra custom code.


Hello @rosy.salame

This is totally expected behaviour when using the ContentPropertyService, and really any service in our confluence-java-api

These services perform a permission check against the “remote” user available on the thread; the user must be authenticated, or anonymous if anonymous access is enabled on the site.

This means that when you create a custom/new thread, you have to set the user on the ThreadLocal for the ContentPropertyService (and friends) to successfully perform their permission checks and return valid data if any.

So I have created an example rest endpoint to explain what I mean.

public class ContentPropertyResource {

    private final ContentPropertyService contentPropertyService;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    public ContentPropertyResource(@ComponentImport ContentPropertyService contentPropertyService) {
        this.contentPropertyService = contentPropertyService;

    // we query the contentPermissionService directly, without wrapping into a new thread
    // this being a get authenticated get request, the user is already available on the ThreadLocal
    public Response getDocId(@PathParam("id") long contentId) throws ServiceException {
        return contentPropertyService.find()
                    .map(property -> Response.ok(property).build())
                    .orElseThrow(notFound("not found for content : " + contentId));

    // we query the contentPermissionService by wrapping the call in a new thread
    // however, we do not set the user on the ThreadLocal, this will return not found 
    // (which in your case is null)
    public Response threadGetDocId(@PathParam("id") long contentId) throws ServiceException {
        Future<Response> responseFuture = executor.submit(() -> getDocId(contentId));
        try {
            return responseFuture.get();
        } catch (InterruptedException | ExecutionException exception) {
            return Response.

    // here we query the contentPermissionService, by wrapping the call in a new thread
    // however we capture the current user , and set it in the new thread prior to 
   // calling on the contentPermissionService
   // unlike the previous endpoint, this one will return data
    public Response authThreadGetDocId(@PathParam("id") long contentId) {
        ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get();
        Future<Response> responseFuture = executor.submit(() -> {
            Response response = getDocId(contentId);
            return response;

        try {
            return responseFuture.get();
        } catch (InterruptedException | ExecutionException exception) {
            return Response.

    // just an endpoint to create the property, so I can test the different examples above
    public Response setDocId(@PathParam("id") long contentId) {
        JsonContentProperty property = contentPropertyService.create(
                        .value(new JsonString("docid-" + contentId))
        return Response.status(Response.Status.CREATED).entity(property).build();


This is the endpoints above in action.

Also you may find the example code above in here

I hope this explains it all, and you can modify your code accordingly.

That said, I am actually quite curious to hear about your use case for using a new thread !

PS: I also noticed that you are using the PageManager , we do recommend using the ContentService instead as much as possible

Hasnae R.

1 Like

Hi @viqueen

Thank you for your help and all your details, but, the git project is not opening. Can you please share its code?

Many thanks,

Hi @rosy.salame,

the repository was just reorganized.

1 Like

Thanks @dennis.fischer for having my back !!
Yup I shuffle things around that repo quite often

I get the same issue, not in a different thread but for specific users, in lower permission level.
Every API which contains ContentService.find - failing due to null results, while for the higher permitted users - it returns the results successfully.

Any help??