0

I kept getting this error while calling an API (under development) in my spring boot app:

java.base/java.lang.Thread.run(Thread.java:1623)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
No serializer found for class 
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor 
and no properties discovered to create BeanSerializer 
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) 
(through reference chain: com.example.service.model.Listing[\"project\"]-
>com.example.service.model.Project[\"user\"]->com.example.service.model.User$HibernateProxy$Nf8nVUyV[\"company\"]-
>com.example.service.model.Company$HibernateProxy$ulaPUlxC[\"hibernateLazyInitializer\"])

From my research, it looks like it's caused by Jackson not being able to serialize the object into json.

Here's my code exerpts:

Model:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "listing")
public class Listing {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long id;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "project_id", referencedColumnName = "id")
  private Project project;

  @NotNull
  @Column(name = "limit")
  private Integer limit;

  ...
}

Controller:

@RestController
@AllArgsConstructor
@RequestMapping("/listing")
public class ListingController {
  private final ListingService listingService;

  @PostMapping
  public ResponseEntity<?> create(@Valid @RequestBody CreateListingRequest request) {
    Listing listing = listingService.create(request);
    return new ResponseEntity<>(listing,  HttpStatus.CREATED);
  }

I set the breakpoint on the return line in the controller while debugging and observed a valid object constructed before it was serialized, so everything up to the serialization step is working.

What I've tried:

  • Turning off spring.jackson.serialization.FAIL_ON_EMPTY_BEANS through both application.properties file and via a custom bean:
  @Bean
  @Primary
  public ObjectMapper objectMapper() {
    return new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
        .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
  }
  • Setting visibility to "visible to all" (via the bean code above). But this shouldn't be necessary since Lombok does this already through annotations in the model;
  • Setting FetchType from LAZY to EAGER;
  • Adding @JoinColumn annotation for project field;
  • Adding the @OneToMany counterpart in the Project class (with property listings);
  • Adding @JsonIgnore for project property. This actually solves the problem, but this causes problem with deserialization - the project field becomes null during deserilization;

I'm looking for a way to make this work for both serialization and deserialization.

Ascendant
  • 2,430
  • 3
  • 26
  • 34

1 Answers1

2

I think you are a victim of a bad design. You are exposing your database entity objects via REST controller, which is considered as antipattern. Apart from the bad architecture, it brings you the technical problems you are facing.

Ideally you should have three layers (repository/entities, domain/business logic, enpoint/data tranfser objects). Each layer should have their own objects, which should not mix, even if they look more or less the same.

The reason is, among others, that you are free to change (and free to keep stable) your API and your database model separately from each other. It also gives you free hands to design each layer's objects optimally for their purpose.

Mapping between the objects from different layers does not cost you much work, you may use a framework such as MapStruct.

If you omit the service layer, at least you should not expose your database entities via endpoints.

Create a separate ListingDto and ProjectDto and use a mapper to fill them with the data from your Listing and Project objects. The entity should be designed for the database, and DTO should be designed for JSON serialization/deserialization. Most probably if the DTOs are (Lombok-powered) POJOs tailored for you API, you will not even need any JSON annotations at all!

Database and REST API are two different worlds - do not mix them. <opinionated>Hibernate aka ORM is antipattern on its own</opinionated>, but mixing it with JSON serialization is a design disaster per se.

Thus you will give your program a proper design, and as an extra reward your Jackson will not have to bother with the hibernate crap and your problem will be solved for free.

ADDED: In case you have problems with Lombok + MapStruct, which sometimes do not cooperate well, there is a Maven config for it. See the aswers to MapStruct and Lombok not working together

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
  • agreed but would that still be the case for simple Domains ? I mean why add complexity when all Entities and DTOs are the same (and will always be) – Ahmed Nabil Jun 27 '23 at 10:41
  • @AhmedNabil: This is not *adding* complexity, but *removing* complexity! Look at the overannotated class, and still the code does not work at all :) – – Honza Zidek Jun 27 '23 at 12:39
  • Most annotations are lombok related and will be added for the new classes as well. Dont get me wrong I agree with you but I think there are cases where exposing entities is acceptable (maybe this is one of them) – Ahmed Nabil Jun 27 '23 at 13:33
  • @AhmedNabil Apparently in this case exposing the entities *does* make problems: "No serializer found for class `org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor`" - this sounds like the serialization problem is caused by the fact that the class is not just a normal DTO. – Honza Zidek Jun 27 '23 at 18:20
  • True, but Its due to bad jackson configurarions. And mostly solved by 1 line of code. Why add Mapstruct(which doesnot work well with lombok and needs lots of configs) to save 1 line of configs – Ahmed Nabil Jun 27 '23 at 21:49
  • 1
    This solved my problem. Thank you so much for the very detailed & extremely useful answer! – Ascendant Jun 28 '23 at 02:09
  • 2
    @AhmedNabil Why to **bend** the configuration of jackson in order to fix the clearly wrong design? And still, if the application is big, introducing layers is unavoidable. If the application is small, introducing layers is good as an exercise and teaches the OP good habits :) And lombok works perfectly well with MapStruct, it is just a matter of a proper configuration. However, thanks for mentioning this issue, I have added a link to the solution. – Honza Zidek Jun 28 '23 at 07:05
  • 2
    @AhmedNabil I still remember one of my first lessons of programming at university more than 30 years ago :) The teacher emphasized that we should remember that we do not split our program into functions *primarily* for reusability, but for the sake of *structured thinking*, "divide et impera", in order to be able to better *think* about the code. This is also why I recommend designing properly even small applications. The persistence layer and the presentation layer are what they are. We simply do not put salt into the sugar bowl :) – Honza Zidek Jun 28 '23 at 08:46