I have created a simple web REST application that has to save a “change ticket” to database. But after I try to create a POST request with JSON as body I get an error:
2020-11-28 14:06:10.449 DEBUG 14864 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 400 BAD_REQUEST 2020-11-28 14:06:10.453 DEBUG 14864 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for POST "/error", parameters={} 2020-11-28 14:06:10.455 DEBUG 14864 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) 2020-11-28 14:06:10.464 DEBUG 14864 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json] 2020-11-28 14:06:10.464 DEBUG 14864 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat Nov 28 14:06:10 CET 2020, status=400, error=Bad Request, message=, path=/change/save}] 2020-11-28 14:06:10.476 DEBUG 14864 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 400
I am using Spring Boot 2, MySQL as DB and MapStruct to convert DTO to entities and vice versa. Also I use Project lombok to get rid of some boilerplate code
My controller POST method is depicted here:
@RestController @Slf4j @RequestMapping({"/change"}) public class ChangeTicketController { @PostMapping(value = "/save", consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<String> save(@RequestBody @Validated ChangeTicketDto changeTicketDto){ ChangeTicket mappedChangeTicket = changeTicketMapper.changeTicketDtoToChangeTicket(changeTicketDto); ChangeTicket savedTicket = changeService.save(mappedChangeTicket); return ResponseEntity.created(URI.create(BASE_URL + "/save/" + savedTicket.getChangeId())) .body("Change Ticket has been saved"); } }
My entity which I am saving looks like this:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Entity public class ChangeTicket extends BaseItem{ //some constructor @NotBlank private String changeId; // public ID set by user @NotNull @Enumerated(value = EnumType.STRING) private ChangeType changeType; @NotBlank @Size(min = 15, max = 500) @Column(length = 500) private String description; }
Superclass:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @MappedSuperclass public class BaseItem { @JsonIgnore @Id @GeneratedValue(generator="system-uuid") @GenericGenerator(name="system-uuid", strategy = "uuid") private String id; // Secret ID generated by DB @Enumerated(value = EnumType.STRING) @NotNull @Column(name = "item_status") private ItemStatus itemStatus; @CreationTimestamp @Column(name = "created_at", updatable = false) private Timestamp createdAt; @UpdateTimestamp @Column(name = "updated_at",updatable = true) private Timestamp updatedAt; @Column(name = "closed_at", updatable = false) private Timestamp closedAt; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "incidentSolver_id", referencedColumnName = "id") // owning side private IncidentSolver incidentSolver; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; }
And here is the sample JSON code I am sending via POSTMAN to as POST request to: http://localhost:8080/change/save
{ "changeId": "86edd7ea-4c37-4dd9-a55c-aeea171e0b42", "changeType": "OS_SETTINGS_CHANGE", "description": "description", "itemStatus": "OPEN", "createdAt": "2020-11-28T12:51:58+00:00", "updatedAt": "2020-11-28T12:55:08+00:00", "closedAt":"", "incidentSolver": [ { "incidentId": "015f3e95-de08-4035-9052-9d40ad2b7af6", "userName": "ThisDude" } ], "user": [ { "userId": "3d00339a-5757-4ada-a316-6705ff603d96", "userType": "CUSTOMER", "userName": "WednesdayDude" } ] }
I don’t know if it is a problem with Jackson not knowing how to deserialize/serialize child and parent objects or If I am building my Json wrongly. If you will need to see my repositories of service classes I will edit this post to show them.
EDIT: adding ChangeTicketDTO
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor public class ChangeTicketDto extends BaseItemDto { private String changeId; // public ID set by user private ChangeType changeType; private String description; }
BaseItemDTO
@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class BaseItemDto { private ItemStatus itemStatus; private Timestamp createdAt; private Timestamp updatedAt; private Timestamp closedAt; private IncidentSolver incidentSolver; private User user; }
Thanks for help guys
Advertisement
Answer
I am back with a solution that worked for me. I have tried to test and see what can cause this problem and it turned out, that this problem occurs when you construct your JSON request body wrong.
I was sending POST request to my controller with JSON body as you can see up in my post. But than I got the idea to write a test method that will transform my object to JSON so I will be sure that my JSON is constructed in a proper way. You can see it here:
@ExtendWith(MockitoExtension.class) class ChangeServiceImplTest { ChangeTicketDto changeTicketDto; @Test public void testConversionOfObject() throws JsonProcessingException { changeTicketDto = changeTicketDto.builder().changeId("StringID") .description("SOME TEXT") .changeType(ChangeType.INSTALLATION_OF_OS) .incidentSolver(IncidentSolver.builder().userName("DUDE").id("ID").build()) .user(User.builder().userName("USERNAME").userType(UserType.USER).build()) .closedAt(Timestamp.from(Instant.now())) .createdAt(Timestamp.from(Instant.now())) .updatedAt(Timestamp.from(Instant.now())) .itemStatus(ItemStatus.OPEN) .build(); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(changeTicketDto); System.out.println(json); } }
This method returned a simple JSON string that looked like this:
{ "itemStatus": "OPEN", "createdAt": 1607031646362, "updatedAt": 1607031646362, "closedAt": 1607031646361, "incidentSolver": { "incidentId": null, "userName": "DUDE" }, "user": { "userId": null, "userType": "USER", "userName": "USERNAME" }, "changeId": "StringID", "changeType": "INSTALLATION_OF_OS", "description": "SOME TEXT" }
So I have created another POST request to the same URL and it worked!!! So now I know that if I will create a bad JSON that is not corresponding to my objectt(and its parent super class) I will get this kind of error. Thanks a lot