Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullPointerException when POST is invoked for the field with custom logic in getter #3328

Open
tory-kk opened this issue Mar 13, 2025 · 9 comments

Comments

@tory-kk
Copy link

tory-kk commented Mar 13, 2025

Hello,
I am getting NullPointerException when invoking resource creation for JSON:API for the following scenario:

  • there is an entity (see TestEntity in gist below) with the field stringValue
  • there is a custom logic in getStringValue method that relies on another field (relatedValue) which is another entity
  • in order to get computed value from the getter in the response, the method is annotated with @ComputedAttribute. Otherwise, getter logic is ignored and a raw value from the field is returned.

There is a DemoApplicationTests that covers GET/POST/PATCH requests and currently testCreate fails with the exception:

java.lang.reflect.InvocationTargetException: null
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
	at com.yahoo.elide.core.type.MethodType.invoke(MethodType.java:83) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.core.dictionary.EntityDictionary.getValue(EntityDictionary.java:1758) ~[elide-core-7.1.4.jar:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281) ~[spring-core-6.1.14.jar:6.1.14]
	at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:482) ~[spring-cloud-context-4.1.4.jar:4.1.4]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.14.jar:6.1.14]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768) ~[spring-aop-6.1.14.jar:6.1.14]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:720) ~[spring-aop-6.1.14.jar:6.1.14]
	at com.yahoo.elide.core.dictionary.EntityDictionary$$SpringCGLIB$$0.getValue(<generated>) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.core.PersistentResource.getValue(PersistentResource.java:487) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.core.PersistentResource.getValueUnchecked(PersistentResource.java:1752) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.core.PersistentResource.updateAttribute(PersistentResource.java:602) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.state.CollectionTerminalState.createObject(CollectionTerminalState.java:232) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.state.CollectionTerminalState.handlePost(CollectionTerminalState.java:141) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.state.StateContext.handlePost(StateContext.java:116) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.PostVisitor.visitQuery(PostVisitor.java:31) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.PostVisitor.visitQuery(PostVisitor.java:18) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.generated.parsers.CoreParser$QueryContext.accept(CoreParser.java:582) ~[elide-core-7.1.4.jar:na]
	at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visitChildren(AbstractParseTreeVisitor.java:46) ~[antlr4-runtime-4.13.0.jar:4.13.0]
	at com.yahoo.elide.generated.parsers.CoreBaseVisitor.visitStart(CoreBaseVisitor.java:21) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.BaseVisitor.visitStart(BaseVisitor.java:47) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.parser.BaseVisitor.visitStart(BaseVisitor.java:33) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.generated.parsers.CoreParser$StartContext.accept(CoreParser.java:118) ~[elide-core-7.1.4.jar:na]
	at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) ~[antlr4-runtime-4.13.0.jar:4.13.0]
	at com.yahoo.elide.jsonapi.JsonApi.visit(JsonApi.java:251) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.JsonApi.lambda$post$1(JsonApi.java:137) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.JsonApi.handleRequest(JsonApi.java:276) ~[elide-core-7.1.4.jar:na]
	at com.yahoo.elide.jsonapi.JsonApi.post(JsonApi.java:129) ~[elide-core-7.1.4.jar:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:an]
	...
	at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]
Caused by: java.lang.NullPointerException: Cannot invoke "com.example.demo.entity.RelatedEntity.getStringValue()" because "this.relatedValue" is null
	at com.example.demo.entity.TestEntity.getStringValue(TestEntity.java:47) ~[classes/:na]
	... 54 common frames omitted

Please advise if I have a misconfiguration or how the issue could be fixed.

Gist with a demo application: https://gist.github.com/tory-kk/5db5dfd5b073446b1bfa680ddd0cf63f

Elide version: 7.1.4

@justin-tay
Copy link
Collaborator

Shouldn't your getStringValue method also check if relatedValue is null and return null instead of attempting to access it?

@tory-kk
Copy link
Author

tory-kk commented Mar 14, 2025

Actually no, the relatedValue is mandatory. In the test you can see that RelatedEntity exists and the relationship is passed in the request body.

@justin-tay
Copy link
Collaborator

Elide can update relationships in a separate step from the main entity.

@tory-kk
Copy link
Author

tory-kk commented Mar 14, 2025

Yes, I understand that. But adding a null check to the getter logic is a bit confusing given the value is required.

@justin-tay
Copy link
Collaborator

Not sure what you are expecting Elide to do since this is your own code.

@tory-kk
Copy link
Author

tory-kk commented Mar 14, 2025

It seems to be pretty trivial situation to have a custom logic in getter when the value depends on multiple fields.
In my particular case there are a few questionable items:

  1. The necessity to annotate getter method with @ComputedAttribute in order to use the value in the response (I am used to Jackson behaviour where getter value automatically overrides raw field)
  2. Why getter with @ComputedAttribute annotation is invoked during entity creation?

Maybe some mechanism to point out that this computed value depends on other fields or special handling for fields that have annotated getters would be useful in this particular case.

P.S. I am new to Elide and currently only getting familiar with the framework by migrating from another JSON:API framework (CRNK)

@justin-tay
Copy link
Collaborator

  1. Elide similar to Hibernate checks the location of the @Id annotation to determine whether to use field or property accessors. See https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/access.html.

  2. Elide has to copy values from the resource to the persistent resource when creating the object.

@tory-kk
Copy link
Author

tory-kk commented Mar 20, 2025

Sorry for the late response and thank you for the explanation.
So, it looks like the cause of the issue in my use-case is that during the entity creation PersistentResource#updateAttribute() requests the previous state (but as far as it is a new object, obviously there is no stringValue and relatedValue). Please correct me if I misunderstood the logic.
Is there any mechanism to point Elide to use fields for management as usual but getter to render the value in the response?

@justin-tay
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants