Bad Request from saving structured JSON object

Tags: , , ,



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

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



Source: stackoverflow