StackOverflowError with MicroProfile RestClient initialization

I’m attempting to use the MicroProfile RestClient available in Payara 5 Full, but getting a StackOverflow with the microprofile framework when it attempts to build the rest client.

Here part of the error:


Caused by: java.lang.StackOverflowError
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at org.glassfish.jersey.model.AnnotatedMethod.findAnnotatedMethod(AnnotatedMethod.java:276)
	at org.glassfish.jersey.model.AnnotatedMethod.findAnnotatedMethod(AnnotatedMethod.java:267)
	at org.glassfish.jersey.model.AnnotatedMethod.<init>(AnnotatedMethod.java:92)
	at org.glassfish.jersey.model.Parameter.createList(Parameter.java:381)
	at org.glassfish.jersey.model.Parameter.create(Parameter.java:359)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.parameterModels(MethodModel.java:501)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.access$1200(MethodModel.java:85)
	at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:645)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.from(MethodModel.java:110)
	at org.glassfish.jersey.microprofile.restclient.RestClientModel.parseMethodModels(RestClientModel.java:91)
	at org.glassfish.jersey.microprofile.restclient.RestClientModel.from(RestClientModel.java:48)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:125)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:85)
	at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:662)

The following lines are endless repeated:

	at org.glassfish.jersey.microprofile.restclient.MethodModel.from(MethodModel.java:110)
	at org.glassfish.jersey.microprofile.restclient.RestClientModel.parseMethodModels(RestClientModel.java:91)
	at org.glassfish.jersey.microprofile.restclient.RestClientModel.from(RestClientModel.java:48)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:125)
	at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:85)
	at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:662)

According to the docs, 5.2022.5 supports the following:

  • Jakarta EE 8
  • MicroProfile 4.1
  • Jersey 2.37
  • Jackson 2.13.4

My dev server is using OpenJDK 1.8.0_412, but I’ll eventually build a docker image using the official Payara 5.2022.5 image.

Here is my code.

First, the data classes used for client interface:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import java.io.Serializable;
import java.util.Objects;

@JsonAutoDetect(
        creatorVisibility = JsonAutoDetect.Visibility.NONE, 
        fieldVisibility = JsonAutoDetect.Visibility.ANY, 
        getterVisibility = JsonAutoDetect.Visibility.NONE, 
        isGetterVisibility = JsonAutoDetect.Visibility.NONE, 
        setterVisibility = JsonAutoDetect.Visibility.NONE)
public class DataRecord implements Serializable {

    public static final String PREFIX = "SOME_PREFIX:";

    private String key;

    public DataRecord() {
    }

    public DataRecord(DataRecord o) {
        this(o.key);
    }

    public DataRecord(String key) {
        this.key = key;
    }

    public String getDataKey() {
        return buildLockKey(this.getKey());
    }

    public static String buildLockKey(String lockKey) {
        return PREFIX.concat(lockKey);
    }

    public String getKey() {
        return key;
    }

    @Override
    public boolean equals(Object o) {

        if (this.key == null || !(o instanceof DataRecord)) {
            return false;
        }
        DataRecord other = (DataRecord) o;
        return this.key.equals(other.key);

    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + Objects.hashCode(this.key);
        return hash;
    }

}
@JsonAutoDetect(
        creatorVisibility = JsonAutoDetect.Visibility.NONE, 
        fieldVisibility = JsonAutoDetect.Visibility.ANY, 
        getterVisibility = JsonAutoDetect.Visibility.NONE, 
        isGetterVisibility = JsonAutoDetect.Visibility.NONE, 
        setterVisibility = JsonAutoDetect.Visibility.NONE)
public class DataList implements Serializable {

    private final String responseId;
    private Collection<DataRecord> data;

    public DataList() {
        this.responseId = UUID.randomUUID().toString();
    }

    public DataList(Collection<DataRecord> data) {
        this();
        this.data = data;
    }

    public Collection<DataRecord> getdata() {
        return data;
    }

    public String getResponseId() {
        return responseId;
    }

    @Override
    public boolean equals(Object o) {

        if (!(o instanceof DataList)) {
            return false;
        }
        DataList other = (DataList) o;
        return this.responseId.equals(other.responseId);

    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + Objects.hashCode(this.responseId);
        return hash;
    }

}

Next, an exception mapper:

public class DataResponseExceptionMapper implements ResponseExceptionMapper<ServiceClientException> {
    
    @Override
    public boolean handles(int statusCode, MultivaluedMap<String, Object> headers) {
        
        return statusCode == Response.Status.UNAUTHORIZED.getStatusCode()
            || statusCode == Response.Status.NOT_FOUND.getStatusCode()
            || statusCode == Response.Status.CONFLICT.getStatusCode()
            || statusCode == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
        
        
    }

    @Override
    public ServiceClientException toThrowable(Response response) {

        if (Response.Status.UNAUTHORIZED.getStatusCode() == response.getStatus()) {            
            return new UnauthorizedException("User is unauthorized");            
        } else if (Response.Status.CONFLICT.getStatusCode() == response.getStatus()) {
            DataRecord existing = null;
            if (response.hasEntity()) {
                existing = response.readEntity(DataRecord.class);
            }
            return new DataClientException(Response.Status.CONFLICT, existing, "Some message here");
        } else if (response.getStatus() >= 500) {
            return new ServiceErrorException(Response.Status.fromStatusCode(response.getStatus()), "Service failure");
        }

        return null;
    }

}

The REST Client interface:


import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;

@RegisterProvider(DataResponseExceptionMapper.class)
@Path("/data")
public interface DataServiceClient {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public DataList getAllData() throws UnauthorizedException, ServiceErrorException;

    @GET
    @Path("/{key}")
    @Produces(MediaType.APPLICATION_JSON)
    public DataRecord getDataStatus(@PathParam("key") String key) throws UnauthorizedException, ServiceErrorException;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public DataRecord postData(DataRecord Data) throws DataClientException, UnauthorizedException, ServiceErrorException;

    @DELETE
    @Path("/{key}")
    @Produces(MediaType.APPLICATION_JSON)
    public DataRecord getReleaseData(@PathParam("key") String key) throws DataClientException, UnauthorizedException, ServiceErrorException;

    @DELETE
    @Produces(MediaType.APPLICATION_JSON)
    public DataList getReleaseAllData() throws DataClientException, UnauthorizedException, ServiceErrorException;

}

And finally, the producer and injection point.

@ApplicationScoped
public class ApplicationBean {

    @Produces
    public DataServiceClient DataService() {
        try {
            return RestClientBuilder.newBuilder()
                    .baseUri(getDataURI())
                    .build(DataServiceClient.class);
        } catch (URISyntaxException ex) {
            logger.fire(new LogEvent<>(Level.SEVERE, "Could not initialize Data Client Service", ex));
        }
        return null;
    }
}
@Singleton
@ConcurrencyManagement
@AccessTimeout(value = 60, unit = TimeUnit.SECONDS)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class DataManagerBean implements DataManagerLocal {

    @Inject
    private DataServiceClient dataService;  <--- ERROR

    @Override
    public DataRecord get(String key) throws UnauthorizedException, ServiceErrorException {
        return dataService.getData(key);
   }

    @Override
    public DataRecord post(DataRecord data) throws DataClientException, UnauthorizedException, ServiceErrorException {
        return dataService.postData(data);
    }

I made sure to annotate the DataRecord so that only the properties are serialized/deserialized, and there is not cyclic dependencies on the two data classes I created.

What could be the problem? A bug with the version of microprofile bundled with Payara 5.2022.5?

Thank you for your message.

Payara Community 5 reached end of life in December 2022, so unfortunately the Payara Team is unable to assist with any support requests related to this release.

To get help with the issue, consider upgrading to a Payara Enterprise subscription, which includes access to Payara Enterprise 5 until the end of its roadmap, currently planned for 2029.

See: Lifecycle and End of Life Policy – Payara Services Ltd