spring-bootspring-mvcspring-data-jpajacksonjsonserializer

Spring boot Jackson deserialization error. Invoking un-needed foreign fields


I'm trying to make a post method that accepts a 'food' item and saves it. The food entity has two foreign fields called "restaurant" and "menu". When I send the request, for some reason, Spring boot is trying to access the id field of the restaurant from the menu object. How can I fix this error?

Here are my entities.

@Entity
@Getter
@Setter
@NoArgsConstructor
@JsonSerialize(using = MenuSerializer.class)
public class Menu {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "restaurant_id", nullable = false)
    @JsonIgnore
    private Restaruant restaurant;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    @JoinColumn(name = "menu_id")
    private List<Food> foods = new ArrayList<Food>();

    @Override
    public String toString() {
        return "Menu [id=" + id + ", name=" + name + ", restaurant=" + restaurant + ", foods=" + foods + "]";
    }

    
}
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Food {
    
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY)
    private Long Id;
    private String name;
    private String picture;
    private Double price;
    
    @Column(columnDefinition = "text")
    private String description;
    private String category;
    private Double discount;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "menu_id", nullable = false)
    @JsonIncludeProperties({"id"})
    private Menu menu;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "restaurant_id", nullable = false)
    @JsonIncludeProperties({"id"})
    private Restaruant restaurant;
    
    @OneToMany(mappedBy = "food")
    @JsonIgnore
    private List<CartItem> cartItems = new ArrayList<>();

    private Boolean available;

    @Override
    public String toString() {
        return "Food [Id=" + Id + ", name=" + name + ", picture=" + picture + ", price=" + price + ", description="
                + description + ", category=" + category + ", discount=" + discount + ", menu=" + menu + ", restaurant="
                + restaurant + ", cartItems=" + cartItems + ", available=" + available + "]";
    }
    
    
}
@Entity
@Setter
@Getter
@NoArgsConstructor
public class Restaruant {
    
    @Id
    @GeneratedValue ( strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String profile;
    private Boolean available;
    @Column(columnDefinition = "text")
    private String description;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner_id")
    @JsonIgnore
    private User owner;
    
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    @JsonIgnore
    private Address address;
    
    @ManyToOne
    @JoinColumn(name = "region_id")
    @JsonIgnore
    private Region region;
     
}

And here is my Controller method. There is nothing fancy here.

@PostMapping("/create")
    public ResponseEntity<Food> createFood(@RequestBody Food food, Principal principal) {
        
        //restaurantService.validateRestaurantOwner(food.getRestaurant().getId(), principal.getName());
        System.out.println(food);
        
        Food createdFood = foodService.saveFood(food);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdFood);
    }

I printed out the food object before saving, so that I can see what it looks like.

Food [Id=null, name=jkla;f, picture=null, price=10.0, description=ffff, category=jfla, discount=1.0, menu=Menu [id=3, name=null, restaurant=null, foods=[]], restaurant=com.hostmdy.food.domain.Restaruant@4c91c71a, cartItems=[], available=true]

The object is just what I expected. I don't know if there's anything wrong with it. This is the error message I'm getting.

[2m2024-09-10T18:01:34.590+06:30[0;39m [33m WARN[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36m.w.s.m.s.DefaultHandlerExceptionResolver[0;39m [2m:[0;39m Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Cannot invoke "com.hostmdy.food.domain.Restaruant.getId()" because the return value of "com.hostmdy.food.domain.Menu.getRestaurant()" is null]

Spring is trying to access Menu.getRestaurant(). Which it shouldn't be doing.

I also printed all debug lines in case you guys may want to see.

Executing identity-insert immediately
[2m2024-09-10T18:01:34.546+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36morg.hibernate.SQL                       [0;39m [2m:[0;39m insert into food (available,category,description,discount,menu_id,name,picture,price,restaurant_id,id) values (?,?,?,?,?,?,?,?,?,default)
[2m2024-09-10T18:01:34.548+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36morg.hibernate.orm.results               [0;39m [2m:[0;39m Initializer list is empty
[2m2024-09-10T18:01:34.548+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36morg.hibernate.orm.results.loading       [0;39m [2m:[0;39m Calling top-level assembler (0 / 1) : org.hibernate.sql.results.graph.basic.BasicResultAssembler@7d41471c
[2m2024-09-10T18:01:34.548+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36morg.hibernate.orm.results               [0;39m [2m:[0;39m Extracted JDBC value [0] - [7]
[2m2024-09-10T18:01:34.551+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.id.IdentifierGeneratorHelper        [0;39m [2m:[0;39m Extracted generated values [com.hostmdy.food.domain.Food]: [Ljava.lang.Object;@643eaf3f
[2m2024-09-10T18:01:34.554+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.orm.jpa.JpaTransactionManager       [0;39m [2m:[0;39m Initiating transaction commit
[2m2024-09-10T18:01:34.555+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.orm.jpa.JpaTransactionManager       [0;39m [2m:[0;39m Committing JPA transaction on EntityManager [SessionImpl(610926081<open>)]
[2m2024-09-10T18:01:34.555+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.e.t.internal.TransactionImpl        [0;39m [2m:[0;39m committing
[2m2024-09-10T18:01:34.555+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.e.i.AbstractFlushingEventListener   [0;39m [2m:[0;39m Processing flush-time cascades
[2m2024-09-10T18:01:34.556+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.e.i.AbstractFlushingEventListener   [0;39m [2m:[0;39m Dirty checking collections
[2m2024-09-10T18:01:34.557+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.hibernate.engine.internal.Collections [0;39m [2m:[0;39m Collection found: [com.hostmdy.food.domain.Food.cartItems#7], was: [<unreferenced>] (initialized)
[2m2024-09-10T18:01:34.558+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.e.i.AbstractFlushingEventListener   [0;39m [2m:[0;39m Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
[2m2024-09-10T18:01:34.558+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.h.e.i.AbstractFlushingEventListener   [0;39m [2m:[0;39m Flushed: 1 (re)creations, 0 updates, 0 removals to 1 collections
[2m2024-09-10T18:01:34.565+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.hibernate.internal.util.EntityPrinter [0;39m [2m:[0;39m Listing entities:
[2m2024-09-10T18:01:34.567+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.hibernate.internal.util.EntityPrinter [0;39m [2m:[0;39m com.hostmdy.food.domain.Food{price=10.0, restaurant=com.hostmdy.food.domain.Restaruant#1, available=true, name=jkla;f, description=ffff, discount=1.0, Id=7, cartItems=[], category=jfla, menu=com.hostmdy.food.domain.Menu#3, picture=null}
[2m2024-09-10T18:01:34.568+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.orm.jpa.JpaTransactionManager       [0;39m [2m:[0;39m Not closing pre-bound JPA EntityManager after transaction
[2m2024-09-10T18:01:34.579+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.w.s.m.m.a.HttpEntityMethodProcessor [0;39m [2m:[0;39m Using 'application/json', given [*/*] and supported [application/json, application/*+json]
[2m2024-09-10T18:01:34.580+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.w.s.m.m.a.HttpEntityMethodProcessor [0;39m [2m:[0;39m Writing [Food [Id=7, name=jkla;f, picture=null, price=10.0, description=ffff, category=jfla, discount=1.0, me (truncated)...]
[2m2024-09-10T18:01:34.590+06:30[0;39m [33m WARN[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36m.w.s.m.s.DefaultHandlerExceptionResolver[0;39m [2m:[0;39m Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Cannot invoke "com.hostmdy.food.domain.Restaruant.getId()" because the return value of "com.hostmdy.food.domain.Menu.getRestaurant()" is null]
[2m2024-09-10T18:01:34.590+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.j.s.OpenEntityManagerInViewInterceptor[0;39m [2m:[0;39m Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
[2m2024-09-10T18:01:34.593+06:30[0;39m [32mDEBUG[0;39m [35m9736[0;39m [2m---[0;39m [2m[food_delivery_api] [nio-8686-exec-1][0;39m [2m[0;39m[36mo.s.web.servlet.DispatcherServlet       [0;39m [2m:[0;39m Completed 500 INTERNAL_SERVER_ERROR

I also tried creating custom deserializer and it still didn't work.


Solution

  • The problem comes from @JsonIncludeProperties({"id"}). It will try to serialize the property because of that.

        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "restaurant_id", nullable = false)
        @JsonIncludeProperties({"id"}) //this
        private Restaruant restaurant;
    

    You can fix it by removing those annotations from Food. If another controller was relying on that, I would suggest you to use @JsonView to have different serialization depending on the context.