@NotNull + @Enumerated causes instrumentation failure
Expected behavior
I have a bean with this, it happened as I upgraded from 13.10 -> 13.17
@Enumerated(value=EnumType.STRING) @NotNull private SubscriptionStatus subscriptionStatus = SubscriptionStatus.FREE_TRIAL;
If I annotate this way, it crashes with
java.lang.RuntimeException: Error reading annotations for dev.featurehub.app.cs.db.models.CsOrganisation
at io.ebeaninternal.server.deploy.parse.ReadAnnotations.readInitial(ReadAnnotations.java:31) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]
at io.ebeaninternal.server.deploy.BeanDescriptorManager.createDeployBeanInfo(BeanDescriptorManager.java:1168) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]
at io.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityDeploymentInitial(BeanDescriptorManager.java:641) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]
at io.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:290) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]
at io.ebeaninternal.server.core.InternalConfiguration.
It seems to try and process the annotation twice.
If I drop the @NotNull and replace it with a @Column(nullable = false) then its ok.
Error mapping Enum type:class dev.featurehub.app.common.model.SubscriptionStatus It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)
What does SubscriptionStatus look like? Does it have any mappings?
it did look like this:
/*
- FeatureHub SaaS Customer Service API
- The API for accessing the customer service API
- OpenAPI spec version: 1.1.1
- NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
- https://openapi-generator.tech
- Do not edit the class manually. */
package dev.featurehub.app.common.model;
import com.fasterxml.jackson.annotation.; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import jakarta.validation.constraints.; import java.util.HashMap; import java.util.Map;
/** Gets or Sets SubscriptionStatus */ public enum SubscriptionStatus { ACTIVE("ACTIVE"),
CANCELLED("CANCELLED"),
UNPAID("UNPAID"),
PAST_DUE("PAST_DUE"),
FREE_TRIAL("FREE_TRIAL"),
TRIAL_EXPIRED("TRIAL_EXPIRED");
private String value;
SubscriptionStatus(String value) { this.value = value; }
@JsonValue public String getValue() { return value; }
@Override public String toString() { return String.valueOf(value); }
public SubscriptionStatus copy() { return this; }
// can't use map.of private static Map<String, SubscriptionStatus> fromValues = new HashMap<>();
static { fromValues.put("ACTIVE", ACTIVE); fromValues.put("ACTIVE", ACTIVE); fromValues.put("CANCELLED", CANCELLED); fromValues.put("CANCELLED", CANCELLED); fromValues.put("UNPAID", UNPAID); fromValues.put("UNPAID", UNPAID); fromValues.put("PAST_DUE", PAST_DUE); fromValues.put("PAST_DUE", PAST_DUE); fromValues.put("FREE_TRIAL", FREE_TRIAL); fromValues.put("FREE_TRIAL", FREE_TRIAL); fromValues.put("TRIAL_EXPIRED", TRIAL_EXPIRED); fromValues.put("TRIAL_EXPIRED", TRIAL_EXPIRED); }
@JsonCreator public static SubscriptionStatus fromValue(String text) { return fromValues.get(text); } }
but then i found that the @Enumerated was being ignored and it was an INT (full rebuild, depoyed in docker container) - so had to put the @io.ebean.annotation.DbEnumValue back in on the class (the API generator will add ebean annotations on command!) But the `@Column while working for tests it appears, didn't work when running it up, so i have had to remove the NotNull and Column annotations.
When i debugged into it, it hit that method in DeployUtil twice for the same field - setEnumScalarType - it seemed like it was unhappy about doing that. If I can debug any further to illuminate this I'm happy to!
This is so weird. When the container starts in k8s, it hits this error when running and crashes. But k8s restarts the container, and it runs ok.
{"@timestamp":"2023-04-19T07:30:12.380+0000","message":"Error in deployment","priority":"ERROR","path":"io.ebean.internal","thread":"main","stack_trace":"java.lang.RuntimeException: Error reading annotations for dev.featurehub.app.cs.db.models.CsOrganisation\n\tio.ebeaninternal.server.deploy.parse.ReadAnnotations.readInitial(ReadAnnotations.java:31) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]\n\tio.ebeaninternal.server.deploy.BeanDescriptorManager.createDeployBeanInfo(BeanDescriptorManager.java:1168) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]\n\tio.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityDeploymentInitial(BeanDescriptorManager.java:641) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]\n\tio.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:290) ~[ebean-core-13.17.0-jakarta.jar:13.17.0-jakarta]\n\tio.ebeaninternal.server.core.InternalConfiguration.
Ok - if i swap from JDK 11.11 -> 11.18 it happens, if i swap back it doesn't happen (tumerin).
I just tried with Amazon's JDK as well and have the same issue.
and now i try and debug it and i can't trigger it 🤕 fails when running, passes when debugging.
The stack trace at the top is truncated and does not include the caused by: ... which would tell us the underlying cause. Are we able to get that?
I'm still trying to figure out how to replicate it, it's changing all the time.
The original error is suggesting that SubscriptionStatus is mapped in 2 or more places, where in one place it is a @Enumerated(value=EnumType.STRING) and in the other its a @Enumerated(value=EnumType.ORDINAL) or @Enumerated (or has no @Enumerated or has @DbEnumValue) ?
Is the enum type SubscriptionStatus used in multiple places / mapped in multiple entity beans? Is it mapped consistently in all the places it is used?
Now that was the trigger I needed to find it thanks @rob-bygrave ! It was an Entity/View that just had it used. It was fine with 13.10/Java 11.11 but with 13.7/11.18 it wasn't. I still cannot reliably reproduce it either, sometimes it happens, sometimes it doesn't. Once it has gone, it has gone for a few hours and then pops up again. I will leave it correctly annotated for now, but I'm wondering if it wouldn't be worthwhile a PR rewording that error message so its more clear what to look for?
dev.featurehub.app.common.model.SubscriptionStatus It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)
Yeah absolutely. I'll create a PR with some better wording and get you to review the new wording.
cannot reliably reproduce
It's very strange. I can imagine that different JVMs could result in different ordering when it reads the entity classes annotations etc.
I wonder if there is something subtle where it actually does not fail when it really/ideally should fail all the time regardless of the order in which things are read. Hmmm, maybe I should try and reproduce on a minimal model. It sounds like there is a bug where it really should fail with this error and somehow does not.
Where it was being used was here (like this):
@Entity
@View(name = "cs_organisation", dependentTables = "cs_organisation")
public class CsOrganisationCounters {
@Aggregation("count(subscription_status)")
public Long count;
@NotNull
public SubscriptionStatus subscriptionStatus;
}
such a heisenbug!