NPE from Jackson trying to serializing a field that does not exists? - spring-boot

Here is my simple bean
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Foo {
private String firstName;
private String lastName;
public String getFullName(){
return firstName + lastName;
}
}
when this object gets serialized in Spring-boot controller with Jackson,
I get the following error
j.l.NullPointerException: null
com.example.foobar.foo.getFullName(Foo.java:28)
s.r.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
s.r.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
s.r.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
j.l.reflect.Method.invoke(Method.java:498)
c.f.j.d.s.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:653)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)..
28 common frames omitted\nWrapped by: c.f.j.d.JsonMappingException: (was java.lang.NullPointerException)
(through reference chain: com.example.foobar.foo[\"fullName\"])
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:379)
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:339)
c.f.j.d.s.s.StdSerializer.wrapAndThrow(StdSerializer.java:343)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698)
c.f.j.d.s.BeanSerializer.serialize(BeanSerializer.java:155)
c.f.j.d.s.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
c.f.j.d.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1419)
c.f.j.d.ObjectWriter.writeValue(ObjectWriter.java:940)
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:267)...
23 common frames omitted\nWrapped by: o.s.h.c.HttpMessageNotWritableException: Could not write content: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"])
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:274 ..."
Here is the requestBody that I sent
{"firstName": "foo",
"lastName: null
}
Is Jackson trying to serialize fullName property ? but I have no such field declared. is this expected behavior? I am unable to find documentation that supports this behavior.
Thanks

Your guess is right, Jackson is trying to find the field name by its getter method, which it cannot find. And hence NPE.
Different solutions
use #JsonIgnore on the getFullName method.
you can disable this feature by setting this property in spring boot application.properties file
spring.jackson.mapper.use-getters-as-setters=false
If you wish to do it with the java code (If not using spring boot), you can do it with a bean declaration like this
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToDisable(MapperFeature.USE_GETTERS_AS_SETTERS);
return builder;
}

Related

Hibernate Upgrade to v6 (Spring Boot 3) - "Named type [interface java.util.List] did not implement BasicType nor UserType"

After upgrading to Spring Boot 3 / Hibernate 6, I am getting exceptions during Spring application / test startup.
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration#15ec3c0c testClass = ....
Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalArgumentException: Named type [interface java.util.List] did not implement BasicType nor UserType
Problem can be traced / debugged down to a List<String> entity property, which is using an AttributeConverter for storing it as a comma-separated string in the (MySQL) DB (TEXT field).
The converter:
#Converter
class StringListToStringConverter : AttributeConverter<List<String>, String> {
....
}
The entity:
#MappedSuperclass
abstract class MyInstance<T>(
...
#Column(name = "iface_ids", columnDefinition = "TEXT")
#Convert(converter = StringListToStringConverter::class)
var interfaceIds: List<String> = emptyList()
...
)
This has been working nicely before with Spring Boot 2.7.x / Hibernate 5.x.
Do you think there is another solution than writing a custom type (and thus removing/replacing the AttributeConverter) in this case?
Kind Regards,
dom
Expecting it to actually work OOTB, the AttributeConverter solution looks so clean&simple for this purpose compared to a custom type implementation that I don't really want to change that.

quarkus and hibernate mapping (field access)

I moved an entity which worked in JEE wildfly to quarkus:
#Entity
#Getter
#Setter
public class Item {
#Column(name = "archived")
private OffsetDateTime archived;
public boolean isArchived() {
return archived != null;
}
}
After running in dev-mode, I get this error:
Caused by: org.hibernate.MappingException:
In trying to locate getter for property [archived],
Class [com.Item]
defined both a `get` [public java.time.OffsetDateTime com.Item.getArchived()]
and `is` [public boolean com.Item.isArchived()] variant
at org.hibernate.internal.util.ReflectHelper.checkGetAndIsVariants(ReflectHelper.java:538)
at org.hibernate.internal.util.ReflectHelper.verifyNoGetVariantExists(ReflectHelper.java:562)
at org.hibernate.internal.util.ReflectHelper.getGetterOrNull(ReflectHelper.java:502)
at org.hibernate.internal.util.ReflectHelper.findGetterMethod(ReflectHelper.java:424)
at org.hibernate.internal.util.ReflectHelper.getterMethodOrNull(ReflectHelper.java:571)
at org.hibernate.property.access.internal.PropertyAccessMixedImpl.getAccessType(PropertyAccessMixedImpl.java:97)
at org.hibernate.property.access.internal.PropertyAccessMixedImpl.<init>(PropertyAccessMixedImpl.java:47)
at org.hibernate.property.access.internal.PropertyAccessEnhancedImpl.<init>(PropertyAccessEnhancedImpl.java:28)
at org.hibernate.property.access.internal.PropertyAccessStrategyEnhancedImpl.buildPropertyAccess(PropertyAccessStrategyEnhancedImpl.java:27)
at org.hibernate.mapping.Property.getGetter(Property.java:311)
at org.hibernate.tuple.entity.PojoEntityTuplizer.buildPropertyGetter(PojoEntityTuplizer.java:255)
at org.hibernate.tuple.entity.AbstractEntityTuplizer.<init>(AbstractEntityTuplizer.java:142)
at org.hibernate.tuple.entity.PojoEntityTuplizer.<init>(PojoEntityTuplizer.java:59)
... 35 more
I understand the error but I dont know why does this hibernate mapping work in wildfly and not in quarkus? As I declared jpa field access instead of jpa property access, hibernate should see the isArchived() automatically as transient.
Or does quarkus compiles all these field-injections to property-injections?
Quarkus generates a getter for the archived field.
Here Hibernate complains that you have two accessors for the archived field: isArchived() and getArchived().
This is an Hibernate issue/limitation, nothing specific to Quarkus.
I think the best is to rename your isArchived() method as Hibernate don't know which method to use to retrieve the value of the archivedfield. And if you add #Transient to your isArchived() method it could (depending on wich method it get first) handle your field as transient.

ArangoDB - BigDecimal

Simple example
#Data
#Document("product")
#Accessors(chain = true)
public class Product {
#Id
private String id;
private BigDecimal cost;
}
ArangoDB configuration:
#Override
public Builder arango() {
final Builder builder = new Builder();
arangoDbConfig.getHosts().forEach(host -> builder.host(host.getHost(), host.getPort()));
return builder
.user(arangoDbConfig.getUser())
.password(arangoDbConfig.getPassword())
.registerModule(new VPackJdk8Module());
}
Reading document generate exception:
Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.math.BigDecimal using constructor NO_CONSTRUCTOR with arguments
Dependency:
SpingBoot 1.5.9
Arangodb-spring-data 1.0.1
Velocypack-module-jdk8 1.0.2
Ok, just forget BigDecimal is mapped to HashMap. Creating queries (biggerThan/lessThan) will be bizarre. I change the data type to Double.
You need to define JsonSerializer and JsonDeserializer for class which contains BigDecimal, because ArangoDB do not have support for that type of data.
Check this arango serializer documentation: link

Spring Data Rest & Lombok - Exception while adding adding relation

In my project I have 2 entities. Survey and entries to survey. They are in relation one to many (thare can be many entries to one survey).
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "survey_entries")
#TypeDef(name = "SurveyEntry", typeClass = SurveyEntry.class)
public class SurveyEntryEntity extends AbstractEntity {
#ManyToOne
#JoinColumn(name = "survey_id")
private SurveyEntity survey;
#NonNull
#Type(type = "SurveyEntry")
#Column(name = "responses")
// JSON db column type mapped to custom type
private SurveyEntry responses;
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "surveys")
#TypeDef(name = "Survey", typeClass = Survey.class)
public class SurveyEntity extends AbstractEntity {
#NonNull
#Type(type = "Survey")
#Column(name = "template")
// JSON db column type mapped to custom type
private Survey survey;
#OneToMany(mappedBy = "survey")
private List<SurveyEntryEntity> entries;
}
I have also created 2 rest repositories using Spring Data Rest:
#RepositoryRestResource(collectionResourceRel = "survey_entries", path = "survey-entries")
public interface SurveyEntryRepository extends PagingAndSortingRepository<SurveyEntryEntity, Long> {
}
#RepositoryRestResource(collectionResourceRel = "surveys", path = "surveys")
public interface SurveyRepository extends PagingAndSortingRepository<SurveyEntity,Long> {
}
I have successfully added survey by rest POST request and I can access it entries (currently empty) by sending GET to /api/surveys/1/entries.Now I want to add entry to exisiting survey. And while I can add it by sending POST (content below) to /api/survey-entries I have troubles adding it directly as a reference to survey. I'm using POST method with the same content and url /api/surveys/1/entries. What is interesting, I'm getting NullPointerException in logs and entry is not inserted but audit modify timestamp in survey is changed. What am I doing wrong? Did I miss same configuration? Or should I use different content?
Content of POST with entry:
{
"responses": {
"question1": "response1",
"question2": "response2",
"question3": "response3"
}
}
Content of POST with survey:
{
"survey": {
//survey structure
}
}
Exception:
08:41:14.730 [http-nio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod - Failed to resolve argument 1 of type 'org.springframework.data.rest.webmvc.PersistentEntityResource'
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No content to map due to end-of-input; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
#EDIT
I have tried adding entry by POST to /api/survey-entries with 'application/hal+json' Content-Type header and content as below, but now I'm getting other exception:
Content:
{
"survey" : "http://localhost:8080/api/surveys/1",
"responses": {
"question1": "response1",
"question2": "response2",
"question3": "response3"
}
}
Exception:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.domain.SurveyEntity` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/surveys/1')
at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 41] (through reference chain: com.domain.SurveyEntryEntity["survey"])
#Edit 2
Added Lombok annotations present on Entity classess
Unfortunatelly problem lied in Lombok annotations which weren't included in sample code. I added them now so any one can see where the problem lies.
I managed to solve it by downgrading Lombok to version (1.16.14) and changing annotation #AllArgsConstructor to #AllArgsConstructor(suppressConstructorProperties = true). It's immposible to achieve in later Lombok versions as this property is currently removed.
I have found solution on Spring Data Rest JIRA. There is already issue DATAREST-884 mentioning problem and presenting solution/workaround.
Sorry for wasted time while it was impossible to see solution without all the code.

BeanPropertyRowMapper does not understand joda time types anymore since upgrading to spring boot 1.4 / spring cloud camden

I have a Spring Batch Job that defines a JdbcPagingItemReader with a BeanPropertyRowMapper :
JdbcPagingItemReader<RawNotice> reader = new JdbcPagingItemReader<>();
final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
sqlPagingQueryProviderFactoryBean.setFromClause("from a_table");
sqlPagingQueryProviderFactoryBean.setWhereClause("state = :state");
sqlPagingQueryProviderFactoryBean.setSortKey("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("state", "interesting_state");
reader.setQueryProvider(sqlPagingQueryProviderFactoryBean.getObject());
reader.setDataSource(dataSource);
reader.setPageSize(10);
// The line below is the interesting one
reader.setRowMapper(new BeanPropertyRowMapper<>(MyEntity.class));
reader.setParameterValues(parameters);
return reader;
This used to work fine, but since we upgraded to spring boot 1.4 and spring cloud Camden, it throws an exception :
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn': no matching editors or conversion strategy found
The column ADateColumn is declared as a Joda LocalDateTime and stored as a java.sql.Timestamp in the database.
I'm quite aware I could add my own joda converters to the BeanPropertyRawMapper conversionService for example, or create a PropertyEditor that understands Java LocalDateTime, but that looks rather like a configuration problem, like something isn't being registered right.
Anybody with a solution/suggestion to fix this problem ?
Thanks !
This is the part of the entity that poses problem :
#Entity
#EqualsAndHashCode(of = { "..." })
#ToString(of = { .... })
public class MyEntity {
#Getter
#Setter
#Id
#GeneratedValue
private Long id;
#Getter
#Version
#Column(nullable = false)
private int version;
//<--- snip --->
#Getter
#Setter
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime aDateColumn;
}
Hibernate is version 4.3.11.Final
JPA is version 2.1 with Hibernate Entity Manager 4.3.11.Final
So I finally ended up creating my own BeanPropertyRowMapper with custom Joda converters :/

Resources