I want to use annotations in classes. I use javax.validation.constrants.* for annotations.
public final class EmailCredential implements Serializable {
private static final long serialVersionUID = -1246534146345274432L;
#NotBlank(message = "Sender must not be empty.")
#Email
private final String sender;
#NotBlank(message = "Subject must not be empty.")
private final String subject;
/// getters setters
}
None of them are working as expected. Meaning that when a below API gets called, annotations should throw error if annotated field is invalid. It looks like there is no annotation to check fields. How properly can I use annotations in a normal class?
controller:
#PostMapping(value = "/email/credentials", consumes = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> emailCredentials(#RequestBody EmailCredential emailCredential) {
return emailService.setCredentials(emailCredential);
}
In your case the validation has to be specified to be triggered.
So add the #Valid annotation on the parameter(s) that you want to validate such as :
import javax.validation.Valid;
// ...
#PostMapping(value = "/email/credentials", consumes = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> emailCredentials(#RequestBody #Valid EmailCredential emailCredential) {
return emailService.setCredentials(emailCredential);
}
According to Spring Boot official documentation : Validating Form Input
You should indicate that your EmailCredential need to be validated using the annotation #Valid
Here's an example from documentation :
package hello;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Controller
public class WebController implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/results").setViewName("results");
}
#GetMapping("/")
public String showForm(PersonForm personForm) {
return "form";
}
#PostMapping("/")
public String checkPersonInfo(#Valid PersonForm personForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "form";
}
return "redirect:/results";
}
}
Related
I'm trying to create a custom bean validator, but, no matter what I do, the validator methods are not being called at all. All the other validation annotations are working perfectly.
This is the annotation:
package com.ats.boleta.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MaxDateValidator.class)
#Documented
public #interface MaxDate {
String message() default "Data está acima do máximo permitido.";
String string();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is the validator:
package com.ats.boleta.validation;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* #author Haroldo de Oliveira Pinheiro
* #see https://gist.github.com/MottoX/e689adf41c22a531af4e18ce285690eb
*/
public class MaxDateValidator implements ConstraintValidator<MaxDate, Temporal> {
private LocalDate maxValue;
#Override
public void initialize(MaxDate constraintAnnotation) {
this.maxValue = LocalDate.parse(constraintAnnotation.string());
}
#Override
public boolean isValid(Temporal value, ConstraintValidatorContext context) {
return value == null || !LocalDate.from(value).isAfter(this.maxValue);
}
}
This is the main DTO that is being validated:
package com.ats.boleta.model.dto;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import com.ats.boleta.model.Boleto;
import com.ats.boleta.model.Cedente;
import com.ats.boleta.model.Titulo;
import com.ats.boleta.validation.PrefixConstraint;
import lombok.Data;
import lombok.ToString;
#Data
#ToString
public class BoletoDTO {
#NotBlank(message = "Nome não pode ficar em branco.")
private String nome;
#PrefixConstraint(message = "Cedente")
#NotNull(message = "Cedente não pode ficar em branco.")
private Cedente cedente;
#Valid
#NotEmpty(message = "Não foi informado nenhum título.")
private List<Titulo> titulo;
}
And this is the child DTO where #MaxDate is being used:
(...)
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
#Getter
#Setter
#ToString
public class Titulo {
#NotBlank(message = "Número do documento não pode ficar em branco.")
private String numeroDocumento = "";
private Integer nossoNumero;
#NotNull(message = "Carteira não pode ficar em branco.")
private Integer carteira;
private String valorDocumento = "";
#MaxDate(string = "2099-12-31", message = "Data de vencimento não pode ser posterior a 2099")
private LocalDate dataVencimento;
(...)
}
And this is the controller:
package com.ats.boleta.controller;
(...)
#Slf4j
#RestController
#RequestMapping("/v0/boleto")
public class BoletaController {
(...)
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<InputStreamResource> boleto(#Valid #RequestBody BoletoDTO dto) throws IOException {
(...)
}
#RequestMapping(value = "/remessa", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<InputStreamResource> remessa(#RequestBody BoletoDTO dto) throws IOException {
(...)
}
}
No matter what I do, neither MaxDateValidator.initialize(MaxDate) nor MaxDateValidator.isValid(Temporal, ConstraintValidatorContext) are called, no matter what. No exception is thrown, no warnings appear in the log, none of the aforementioned methods are called. All of the other annotations work as intended.
I have even tried to change the generic on MaxDateValidator to either LocalDate or even Object; nothing works.
What could be causing this behavior? Is there any way to debug it? Are there any Spring logs that could be activated to check what would be going wrong?
Can you please try to use:
#NotEmpty(message = "Não foi informado nenhum título.")
private List<#Valid Titulo> titulo;
Instead of:
#Valid
#NotEmpty(message = "Não foi informado nenhum título.")
private List<Titulo> titulo;
Please add annotation #Valid for method remessa
package com.ats.boleta.controller;
(...)
#Slf4j
#RestController
#RequestMapping("/v0/boleto")
public class BoletaController {
(...)
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<InputStreamResource> boleto(#Valid #RequestBody BoletoDTO dto) throws IOException {
(...)
}
#RequestMapping(value = "/remessa", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<InputStreamResource> remessa(#Valid #RequestBody BoletoDTO dto) throws IOException {
(...)
}
}
In the end, I had forgotten to add the #Valid annotation to the remessa() method on the controller:
package com.ats.boleta.controller;
(...)
#Slf4j
#RestController
#RequestMapping("/v0/boleto")
public class BoletaController {
(...)
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<InputStreamResource> boleto(#Valid #RequestBody BoletoDTO dto) throws IOException {
(...)
}
#RequestMapping(value = "/remessa", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<InputStreamResource> remessa(#Valid #RequestBody BoletoDTO dto) throws IOException {
(...)
}
}
I am new to Spring Boot Framework without any prior knowledge of the Spring Framework. I encountered this error when I am running the spring application:
The error points to the service class
PersonService.java
package com.example.demo1.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.example.demo1.dao.PersonDao;
import com.example.demo1.model.Person;
#Service
public class PersonService {
private final PersonDao personDao;
// The #Autowired means that we are injecting in actual constructor. It means we are autowiring in the PersonDao interface
// We have multiple implementation of the PersonDao interface. So to distinguish between them we use the #Qualifier
#Autowired
public PersonService(#Qualifier("fake") PersonDao personDao) {
this.personDao = personDao;
}
// Here we have the option of providing the id or not
public int addPerson(Person person) {
return personDao.insertPerson(person);
}
}
The error shows that the bean of the type PersonDao is required that could not be found. But I could not identify how to create the bean. I have used the dependency injection for the PersonDao class.
PersonDao.java
package com.example.demo1.dao;
import java.util.UUID;
import com.example.demo1.model.Person;
public interface PersonDao {
int insertPerson(UUID id, Person person);
default int insertPerson(Person person) {
UUID id = UUID.randomUUID();
return insertPerson(id, person);
}
}
FakePersonDataAccessService
package com.example.demo1.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.springframework.stereotype.Repository;
import com.example.demo1.model.Person;
// The #Repository annotation means that this class is served as a Repository
#Repository("fakeDao")
public class FakePersonDataAccessService implements PersonDao {
private static List<Person> DB = new ArrayList<>();
#Override
public int insertPerson(UUID id, Person person) {
DB.add(new Person(id, person.getName()));
return 1;
}
}
Person.java
package com.example.demo1.model;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
private final UUID id;
private final String name;
public Person(#JsonProperty("id") UUID id,
#JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
}
PersonController.java
package com.example.demo1.api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo1.model.Person;
import com.example.demo1.service.PersonService;
// We can implement the http methods(get, put, post, delete) implementation in the controller. We can do that by using the #RestController annotation
#RequestMapping("/api/v1/person")
#RestController
public class PersonController {
private final PersonService personService;
// The #Autowired means that the spring boot injects actual service in the constructor
#Autowired
public PersonController(PersonService personService) {
this.personService = personService;
}
// The #RequestBody annotation shows that we convert the json body that we receive from the postman to an actual Person
#PostMapping
public void addPerson(#RequestBody Person person) {
personService.addPerson(person);
}
}
Problem: a new table is created once when I make a post request through The bash console. The rest of the queries go to the new table.
Than he does not like those databases which are available. As I understand - they just don't know, but I don't know how to direct it in the right. Although all variables are also named.
A problem was found created due to an Entity annotation in the Message class. Please tell me how to make it added to an existing table, tried #Table(name = "ApiTable") to an existing one, and it generates a new api_table.. Also don't quite understand what needs to be added/changed to accept json post requests.
Application
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication
#EnableJpaRepositories("com.example.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
MainController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping(path="/demo") /
public class MainController {
#Autowired
private UserRepository TestApi;
#PostMapping(path="/add")
public #ResponseBody String addNewUser (#RequestParam String name
, #RequestParam String email) {
Message n = new Message();
n.setName(name);
n.setEmail(email);
TestApi.save(n);
return "Saved";
}
#GetMapping(path="/all")
public #ResponseBody Iterable<Message> getAllUsers() {
return TestApi.findAll();
}
}
Message
import javax.persistence.*;
#Entity
public class Message {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
UserRepository
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<Message, Integer> {
}
application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost/Test?useUnicode=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
The problem seems to be Spring Boot's default naming strategy which you'd have to replace.
Spring Boot's default naming strategy now seems to include converting camelCase to snake_case so you need to choose a different one (or implement your own).
Here's some more info on the topic: Hibernate naming strategy changing table names
The annotated spring validation on fields of a POJO works when it is created from json request body. However, when I create the same object manually (using setters) and want to trigger validation, I'm not sure how to do that.
Here is the Registration class, which has Builder inner class that can build the object. In the build method I would like to trigger spring validation. Please scroll to the bottom and check Builder.build() and Builder.valiate() methods to see current implementation. I'm using javax.validation.Validator to trigger validation, but I prefer to leverage spring validation if possible.
package com.projcore.dao;
import com.projcore.util.ToString;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.Size;
import java.util.List;
import java.util.Set;
/**
* The data transfer object that contains the information of a Registration
* and validation rules for attributes.
*/
#JsonIgnoreProperties(ignoreUnknown = true)
public final class Registration {
private static final Logger LOGGER = LoggerFactory.getLogger(Registration.class);
private String id;
#NotEmpty
#Size(max = 255)
private String messageId;
#NotEmpty
#Size(max = 255)
private String version;
#Size(max = 255)
private String system;
public Registration() {
}
private Registration(Builder builder) {
this.id = builder.id;
this.messageId = builder.messageId;
this.version = builder.version;
this.system = builder.system;
}
public static Builder getBuilder() {
return new Builder();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessageId() {
return messageId;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
#Override
public String toString() {
return ToString.create(this);
}
/**
* Builder pattern makes the object easier to construct in one line.
*/
public static class Builder {
private String id;
private String messageId;
private String version;
private String system;
private Builder() {}
public Builder id(String id) {
this.id = id;
return this;
}
public Builder messageId(String messageId) {
this.messageId = messageId;
return this;
}
public Builder version(String version) {
this.version = version;
return this;
}
public Builder system(String system) {
this.system = system;
return this;
}
public Registration build() {
Registration entry = new Registration(this);
// *** Would like to trigger spring validation here ***
Set violations = validate(entry);
if (violations.isEmpty())
return entry;
else
throw new RuntimeException(violations.toString());
}
private Set validate(Registration entry) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Registration>> constraintViolations = validator.validate(entry);
return constraintViolations;
}
}
}
Validation works fine here:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
Registration create(#RequestBody #Valid Registration registration)
Solution:
Removed Registraion.Builder.validate(). Updated Registraion.Builder.build() to:
public Registration build() {
Registration entry = new Registration(this);
return (Registration) ValidatorUtil.validate(entry);
}
ValidationUtil.java
package projcore.util;
import com.ericsson.admcore.error.InvalidDataException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
public class ValidatorUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorUtil.class);
private static final Validator javaxValidator = Validation.buildDefaultValidatorFactory().getValidator();
private static final SpringValidatorAdapter validator = new SpringValidatorAdapter(javaxValidator);
public static Object validate(Object entry) {
Errors errors = new BeanPropertyBindingResult(entry, entry.getClass().getName());
validator.validate(entry, errors);
if (errors == null || errors.getAllErrors().isEmpty())
return entry;
else {
LOGGER.error(errors.toString());
throw new InvalidDataException(errors.getAllErrors().toString(), errors);
}
}
}
InvalidDataException.java
package projcore.error;
import org.springframework.validation.Errors;
/**
* This exception is thrown when the dao has invalid data.
*/
public class InvalidDataException extends RuntimeException {
private Errors errors;
public InvalidDataException(String msg, Errors errors) {
super(msg);
setErrors(errors);
}
public Errors getErrors() {
return errors;
}
public void setErrors(Errors errors) {
this.errors = errors;
}
}
Spring provides full support for the JSR-303 Bean Validation API. This includes convenient support for bootstrapping a JSR-303 implementation as a Spring bean. This allows a javax.validation.Validator to be injected wherever validation is needed in your application.
Use the LocalValidatorFactoryBean to configure a default JSR-303 Validator as a Spring bean:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
The basic configuration above will trigger JSR-303 to initialize using its default bootstrap mechanism. A JSR-303 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically.
5.7.2.1 Injecting a Validator
LocalValidatorFactoryBean implements both javax.validation.Validator and org.springframework.validation.Validator. You may inject a reference to one of these two interfaces into beans that need to invoke validation logic.
Inject a reference to javax.validation.Validator if you prefer to work with the JSR-303 API directly:
// JSR-303 Validator
import javax.validation.Validator;
#Service
public class MyService {
#Autowired
private Validator validator;
}
Inject a reference to org.springframework.validation.Validator if your bean requires the Spring Validation API:
// Spring Validator
import org.springframework.validation.Validator;
#Service
public class MyService {
#Autowired
private Validator validator;
}
Here is a well exaplained example
Using JSR 303 with "classic" Spring Validators (enter the SpringValidatorAdapter)
This link is very helpful. Wrapping javax.validation.Validator in
org.springframework.validation.beanvalidation.SpringValidatorAdapter
helped deal with errors consistently. Can you add this as an answer so
that I can accept it
and
Spring doc here
I am new to Spring MVC. I am trying a simple application to validate form values. I am use Spring BindingResult and JSR303 for field validation. But for some reason the validation errors don't show up in the error tag. In fact, the binding result doesn't return any errors.
My bean is as follows:
package com.app.ebl.bean.login;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class LoginBean {
#NotNull(message = "User Name field can not be blank")
#Size(max = 10, message = "User Name should not be more than 10 characters")
private String userName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
My Controller Class Look like below,
package com.app.ebl.controller.login;
import javax.validation.Valid;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.app.ebl.bean.login.LoginBean;
#Controller
public class LoginController {
private static final Log log = LogFactory.getLog(LoginController.class);
#RequestMapping(value="/login", method=RequestMethod.GET)
public String toLogin(Model model)
{
model.addAttribute("login",new LoginBean());
return "login";
}
#RequestMapping(value="/authenticate", method=RequestMethod.POST)
public String authenticate(#ModelAttribute(value = "login") #Valid LoginBean login,
BindingResult bindingResult, Model model)
{
if(bindingResult.hasErrors()) {
return "login";
}
model.addAttribute("userName",login.getUserName());
return "home";
}
}
Now If I provide no value in the user name field, system is not validating the same, and allowing me to the next view.
Can someone please help me.
Thanks in Advance,
The #Size annotation accepts both min and max parameter. When min is not provided, it uses default:
/**
* #return size the element must be higher or equal to
*/
int min() default 0;
You didn't provided any, and my guess is that your controller do not transform empty strings to null. Firstly, I'd switch to #NotBlank annotation from org.hibernate.validator. In addition to #NotNull, it removes trailing whtespaces:
public class LoginBean {
#NotBlank(message = "User Name field can not be blank")
#Size(max = 10, message = "User Name should not be more than 10 characters")
private String userName;
....
Additionally, define init binder in your controller, that will change empty strings to null with help of StringTrimmerEditor from org.springframework.beans.propertyeditors.StringTrimmerEditor
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
Took me a while to figure this out, I hope this helps someone.
I had the same problem. The bean just wasn't getting validated.
What I had done was created a library via the "buildpath" menu and added the hibernate-validator.jar to my library. I could use the hibernate annotations and I wasn't getting any compile errors but when I ran it, the beans never got validated.
The reason was that the application needed the hibernate-validator.jar in WEB-INF/lib folder, so that it could use it for validation.