this problem has me crazy and I don't know where to look anymore.
I have a rest api that receives a #RequestBody DTO
public ResponseEntity<JuntaCalificadoraDTO> edit(#Valid #RequestBody JuntaCalificadoraDTO juntaCalifDTO) {
......
This is the DTO that I receive and that I validate with java bean validations. Generate getter and setters with lombok
#Data
#NoArgsConstructor
public class JuntaCalificadoraDTO extends RepresentationModel<JuntaCalificadoraDTO> {
private Long id_process;
#NotNull #Min(1) #Positive
private Integer presidentJC;
#NotNull #Min(1) #Positive
private Integer secretaryJC;
#NotNull #Min(1) #Positive
private Integer representativeFuncJC;
}
Java bean validations does its job. It is valid that it is not zero, its minimum and also that it is positive. The problem is that it does not validate when I pass a letter to a variable, for example:
{
"id_process": 4455,
"presidentJC": "dd",
"secretaryJC": 33,
"representativeFuncJC": 3
}
It detects the error, and postman returns "400 Bad Request" but nothing else. Also the Intellij console doesn't show anything, no stack.
I need to catch that error which I imagine is a "NumberFormatException" but I haven't been able to. And I don't know why he hides it. I created a method in a #ControllerAdvice class but no success either.
#ExceptionHandler (value = {NumberFormatException.class})
public final ResponseEntity<Object> invalidNumberHandling(NumberFormatException ex) {
ApiError apiError = ApiError.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST)
.message("Number Format Exception")
.errors(List.of("El o los parámetros de entrada no son válidos"))
.details(ex.getMessage())
.build();
return new ResponseEntity<>(apiError, apiError.getStatus());
}
I will appreciate any guidance. And sorry for my bad english
You are close. It's actually an InvalidFormatException that is wrapped into a HttpMessageNotReadableException.
By catching the InvalidFormatException you have access to the field that failed and to the wrong value, so it should be enough for you create a meaningful response to the user.
See this sample application (Java 17) in case you have any doubts - if this doesn't work please let me know your Spring Boot version.
#SpringBootApplication
public class SO72312634 {
public static void main(String[] args) {
SpringApplication.run(SO72312634.class, args);
}
#Controller
static class MyController {
#PostMapping
public ResponseEntity<?> edit(#Valid #RequestBody JuntaCalificadoraDTO juntaCalifDTO) {
return ResponseEntity.ok().build();
}
}
#Bean
ApplicationRunner runner() {
return args -> new RestTemplate().exchange(RequestEntity
.post("http://localhost:8080")
.contentType(MediaType.APPLICATION_JSON)
.body("""
{
"id_process": 4455,
"presidentJC": "ds",
"secretaryJC": "abc",
"representativeFuncJC": 3
}
"""), String.class);
}
#ControllerAdvice
static class ExceptionAdvice {
#ExceptionHandler(value = InvalidFormatException.class)
public final ResponseEntity<Object> httpMessageNotReadable(InvalidFormatException ex) {
String failedPaths = ex.getPath().stream().map(JsonMappingException.Reference::getFieldName).collect(Collectors.joining());
return new ResponseEntity<>("Field %s has invalid value %s ".formatted(failedPaths, ex.getValue()), HttpStatus.BAD_REQUEST);
}
}
#Data
#NoArgsConstructor
public static class JuntaCalificadoraDTO {
private Long id_process;
#NotNull
#Min(1) #Positive
private Integer presidentJC;
#NotNull #Min(1) #Positive
private Integer secretaryJC;
#NotNull #Min(1) #Positive
private Integer representativeFuncJC;
}
}
Output:
Caused by: org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : "Field presidentJC has invalid value ds "
Related
I have Measurement class and DTO for it.
Here's the class:
#Data
public class MeasurementDTO {
#Column(name = "measurement_temp")
private double temp;
#Column(name = "measurement_raining")
private boolean isRaining;
#NotEmpty
#NotNull
private String sensorName;
}
And controller for it:
#PostMapping("/add")
private ResponseEntity<HttpStatus> add(#RequestBody #Valid MeasurementDTO measurementDTO,
BindingResult bindingResult){
System.out.println(measurementDTO.isRaining()); // returns false only
if(bindingResult.hasErrors()){
StringBuilder errorMsg = new StringBuilder();
List<FieldError> errors = bindingResult.getFieldErrors();
for (FieldError error : errors)
errorMsg.append(error.getField())
.append(" - ")
.append(error.getDefaultMessage())
.append("; ");
throw new MeasurementNotCreatedException(errorMsg.toString());
}
if(sensorService.getAll().stream().anyMatch(s -> s.getName().equals(measurementDTO.getSensorName()))){
Measurement measurement = convertToMeasurement(measurementDTO);
measurement.setSensor(sensorService.getByName(measurementDTO.getSensorName()));
measurementService.save(measurement);
return new ResponseEntity<>(HttpStatus.OK);
} else throw new SensorNotFoundException();
}
I do POST requestes with Postman. Sample
{
"isRaining" : true,
"temp" : 24.0,
"sensorName" : "new sensor"
}
I would guess the problem is accessing the private field without a getter function.
You could try to implement (or use an IDE for auto implementing) a public getter function in your measurementDTO class for the isRaining field to return its value like this:
public boolean getIsRaining(){
return isRaining;
}
And then reference that field via measurementDTO.getIsRaining() instead of measurementDTO.isRaining().
Try changing the name of isRaining to raining, ie
private boolean raining;
Lombok #Data might be getting confused because the convention for generated boolean getters is to prepend “is”.
I have a REST POST endpoint which is used to create an entity. I've trying to test it with MockMVC but every time that i sent the request i received a 415 status code (media not supported):
java.lang.AssertionError: Status expected:<201> but was:<415> Expected :201 Actual :415
The endpoint accepts json body in the request and I sent this data using the MockMVC contentType as APPLICATION_JSON_VALUE and the content method with the serialized object by Jackson.
The controller ALSO is managed my Spring Security Filters but i think this is not the problem as i'm using the #AutoConfigureMockMvc(addFilters = false) and the HTTP status code is related to not supported media type and not about any security exception.
I've found a plenty of topics talking about it but none was able to solve my problem. One of the cases was including the #EnableWebMvc into the Controller OR as a configuration bean test, but none work it.
My attempt with #EnableWebMvc as test bean
#TestConfiguration
#EnableWebMvc
public class ProdutoControllerConfigurationTest {
#Bean
public ProdutoController produtoController() {
return new ProdutoController(/* dependencies by autowired */);
}
}
EDIT: I also tried with different MediaType like MediaType.APPLICATION_JSON and MediaType.APPLICATION_JSON_VALUE
My DTO class
public class CriarProdutoDTO {
#NotNull
#Size(min = 2)
#JsonProperty("nome_produto")
private final String nomeProduto;
#DecimalMin("0.1")
private final BigDecimal preco;
private final String descricao;
#NotNull
#Min(0)
#JsonProperty("quantidade_estoque")
private final Integer quantidadeEstoque;
#NotNull
#Min(1)
#JsonProperty("categoria_id")
private final Integer categoriaId;
public CriarProdutoDTO(String nomeProduto, BigDecimal preco, String descricao, Integer quantidadeEstoque, Integer categoriaId) {
this.nomeProduto = nomeProduto;
this.preco = preco;
this.descricao = descricao;
this.quantidadeEstoque = quantidadeEstoque;
this.categoriaId = categoriaId;
}
}
My current tests:
#ActiveProfiles("testes")
#ExtendWith(SpringExtension.class)
#SpringBootTest
#WebAppConfiguration
#AutoConfigureMockMvc(addFilters = false)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
public class ProdutoControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void deveRetornarCreated_criacaoProdutoSucesso() throws Exception {
CriarProdutoDTO criarProdutoDTO = new CriarProdutoDTO("Nome", new BigDecimal("2.0"), "DESCRIÇÃO", 2, 1);
mockMvc.perform(MockMvcRequestBuilders.post("/api/produtos")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(criarProdutoDTO)))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isCreated());
}
}
My Controller:
#RestController
#RequestMapping(value = "/api/produtos")
public class ProdutoController {
#Autowired
private ProdutoService produtoService;
#Autowired
private CriarProdutoDtoToProdutoConverter produtoConverter;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void cadastrar(#RequestBody #Valid CriarProdutoDTO produtoDTO) {
Produto novoProduto = produtoConverter.converter(produtoDTO);
produtoService.cadastrar(novoProduto);
}
}
try add Accept header to your request
Accept=application/json
I found out the problem.
The problem was occurring in Jackson's Serialization from my Data Transfer Object (DTO)
My DTO has an args-constructor and because of that i have to use the #JsonCreator to point the args constructor. What i didn't expect was that you must annotate all the constructor parameters with #JsonProperty as Jackson didn't know the exact order to instantiate the object from the construtor, that was my problem.
Another way is creating a bean for that, so you don't have to use the #JsonCreator
The solution:
#JsonCreator
public CriarProdutoDTO(
#JsonProperty("nome_produto") String nomeProduto, #JsonProperty("preco") BigDecimal preco,
#JsonProperty("descricao") String descricao, #JsonProperty("quantidade_estoque") Integer quantidadeEstoque,
#JsonProperty("categoria_id") Integer categoriaId) {
this.nomeProduto = nomeProduto;
this.preco = preco;
this.descricao = descricao;
this.quantidadeEstoque = quantidadeEstoque;
this.categoriaId = categoriaId;
}
I have two API:s , CarRental-API on port 8080 and CarRental-CRUD on port 8081.
CarRental-CRUD uses JpaRepository to access a h2 memory DB.
I want to use CarRental-API to make requests to CarRental-CRUD, using webclient.
In CarRental-CRUD , I can make post requests and add cars to the db using this service:
public String addCar(Car car) {
carRepository.save(car);
return loggerService.writeLoggerMsg("CREATED CAR AND ADDED TO DB");
}
And then in the controller :
#RestController
#RequestMapping("/crud/v1")
public class AdminCarController {
#Autowired
private AdminCarService adminCarService;
#PostMapping(path = "/addcar", consumes = "application/json")
public String addCar(#RequestBody Car car) {
return adminCarService.addCar(car);
}
}
I tried to post a request with webclient in CarRental-API with :
#Service
public class AdminCarService {
#Autowired
LoggerService loggerService;
#Autowired
private WebClient.Builder webClientBuilder;
public String addCar(Car car) {
webClientBuilder
.build()
.post()
.uri("localhost:8081/crud/v1/addcar")
.retrieve()
.bodyToFlux(Car.class);
return loggerService.writeLoggerMsg("ADDED CAR TO DB");
}
}
However, using the carRental-API , I get this error in postman when I try to post a request :
"status": 500,
"error": "Internal Server Error",
"trace": "org.springframework.web.reactive.function.client.WebClientResponseException: 200 OK from POST localhost:8081/crud/v1/addcar; nested exception is org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/plain;charset=UTF-8' not supported for bodyType=com.backend.carrentalapi.entity.Car\n\tat
This is the Car Entity :
#Getter
#Setter
#RequiredArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "TBL_CAR")
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long carId;
#Column(name = "NAME")
private String carName;
#Column(name = "MODEL")
private String carModel;
#Column(name = "DAILY_PRICE")
private double dailyPrice;
}
I can't seem to find where in the code I am producing text/plain. I made sure in postman that I'm posting a raw JSON body request, and the headers say content type : application/json.
In your WebClient you are not adding the request body, but instead expecting a Car back from the API you are calling (and this API returns a simple String instead). The following should work.
#Service
public class AdminCarService {
#Autowired
LoggerService loggerService;
#Autowired
private WebClient.Builder webClientBuilder;
public String addCar(Car car) {
webClientBuilder
.build()
.post()
.uri("localhost:8081/crud/v1/addcar")
.body(BodyInserters.fromValue(car))
.retrieve()
.toBodilessEntity();
return loggerService.writeLoggerMsg("ADDED CAR TO DB");
}
}
Using .toBodilessEntity() since you don't really do anything with the response.
I have a rest service that receives an object like this:
public class Item implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String ID;
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date insertDate;
//getter setter
}
My controller has a saveItem method of this type:
#RequestMapping(value = "/item", method = RequestMethod.POST, produces = "application/json")
public Object saveItem(#RequestBody(required = false) Item item) {
if (item == null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
//code..
return new ResponseEntity<>(HttpStatus.OK);
}
when an incorrect date format arrives, an error is generated that is not handled by me because it does not enter the if. Why does this happen? How should it be managed?
The problem occurred even without the #JsonFormat annotation
I suppose you are using spring boot. You can use ControllerAdvice in order to define response in case of exceptions. You can define multiple handlers as multiple methods annotated with ExceptionHandler.
#Slf4j
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(InvalidArgumentException.class)
public ResponseEntity<ResponseDto<Object>> handleException(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. BAD_REQUEST);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<ResponseDto<Object>> handleException2(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. INTERNAL_SERVER_ERROR);
}
}
I am trying to apply validations on my SPRING REST-API but i am getting this exception:
Apr 10, 2020 12:05:26 PM org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver doResolveHandlerMethodExceptionWARNING: Failed to invoke #ExceptionHandler method: public com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation com.luv2code.springdemo.exceptionhandling.GlobalExceptionHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException)org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:226)
Entity Class:
#Entity#Table(name="customer")
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
#NotNull(message = "Firstname is necessary")
#Size(min=1,message="This field is required")
private String firstName;
#Column(name="last_name")
#NotNull(message = "Lastname is necessary")
#Size(min=1,message="This field is required")
private String lastName;
#Column(name="email")
private String email;
// getters and setters
}
FieldValidation Handler classes:
public class RestFieldError {
private String field;
private String message;
public RestFieldError() {
}
// getters and setters
}
and
public class RestFieldErrorValidation {
private List<RestFieldError> fieldErrors = new ArrayList<>();
public RestFieldErrorValidation() {
}
public void addFieldError(String path, String message) {
RestFieldError error = new RestFieldError(path, message);
fieldErrors.add(error);
}
}
RestController Code:
#RestController
#RequestMapping("/api")
public class CustomerRestController {
// autowire the CustomerService
#Autowired
private CustomerService customerService;
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
System.out.println("Entered init binder");
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
// add the mapping for POST/customers (add a new customer)
#PostMapping("/customers")
#ResponseBody
public Customer addCustomer(#Valid #RequestBody Customer theCustomer) {
System.out.println("theCustomer :"+theCustomer.getFirstName());
theCustomer.setId(0);
customerService.saveCustomer(theCustomer);
return theCustomer;
}
}
Exception handler Class:
#ControllerAdvice
public class GlobalExceptionHandler {
// Adding Validation Support on REST APIs--------------------------------------------------------->
private MessageSource messageSource;
#Autowired
public GlobalExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public RestFieldErrorValidation processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private RestFieldErrorValidation processFieldErrors(List<FieldError> fieldErrors) {
RestFieldErrorValidation dto = new RestFieldErrorValidation();
for (FieldError fieldError: fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
//If the message was not found, return the most accurate field error code instead.
//You can remove this check if you prefer to get the default error message.
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
}
return localizedErrorMessage;
}
}
Here is the google drive link of the project if you can check the code:
https://drive.google.com/open?id=1QSFVMi3adHGkc7BqXsqAY0P_tO2UfT2I
Here is the Article that i followed:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
I'm assuming you are using plain Spring here, not Spring Boot.
The question is: To what exactly do you want to convert your RestFieldErrorValidation object? XML? JSON?
For either, you need an appropriate third-party library on your classpath, so Spring can do the conversion automatically.
In the case of JSON, you might want to add this dependency to your project.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>