Bean class
#NotBlankIfAnotherFieldHasValue(fieldName = "addRequest", fieldValue = "false", dependentFieldName = "userId")
public class AddEditUserVo {
private String userId;
#NotBlank
private String userName;
#NotBlank
private String password;
#NotBlank
private String email;
private boolean addRequest;
}
Not working when the #Validation is only at class level
#Controller
#RequestMapping("/user")
#Validated
public class UserController {
#Autowired
private UserService userService;
#PostMapping(value="/update", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public ResponseEntity<Response> addEditUser(#RequestBody AddEditUserVo addEditTagTypeVo) {
return userService.addEditUser(addEditTagTypeVo);
}
But working when the annotation is also added at method parameter level.
#Controller
#RequestMapping("/user")
#Validated
public class UserController {
#Autowired
private UserService userService;
#PostMapping(value="/update", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public ResponseEntity<Response> addEditUser(#RequestBody #Validated AddEditUserVo addEditTagTypeVo) {
return userService.addEditUser(addEditTagTypeVo);
}
Is there anything I need to do for keeping #Validtion at class level only and without mentioning explicitly at method parameter level?
I have the following problem that I need to save a Hateos structure in Elasticsearch using the save method in the Springboot application, but I get the error message "stackoverflowerror null": Could some body please check my code to see where I am making mistakes ? It really took me a long time to demonstrate this example.
Config.java
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.example.demo.elsticsearch")
public class Config {
#Bean
public RestHighLevelClient client() {
ClientConfiguration clientConfiguration
= ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client());
}
}
UserResourceAssembler.java
#Service
public class UserResourseAssembler extends RepresentationModelAssemblerSupport<User, UserResourse> {
public UserResourseAssembler() {
super(JsController.class, UserResourse.class);
}
#Override
public UserResourse toModel(User user) {
UserResourse resource = UserResourse.builder().email("jjj").firstName("hii").lastName("hoo").loginId("123").build();
resource.add(linkTo(methodOn(JsController.class).getByLoginId(user.getLoginId())).withRel("self"));
return resource;
}
}
Repo.java
#Repository
public interface Repo extends ElasticsearchRepository<UserResourse, String> {
List<UserResourse> findByLoginId(String loginId);
}
User.java
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
private String loginId;
private String firstName;
private String lastName;
private String email;
}
UserResourse.java
#Mapping(mappingPath = "/files.json")
#Document(indexName = "files", type = "file")
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class UserResourse extends RepresentationModel<UserResourse> {
#Id
private String loginId;
private String firstName;
private String lastName;
private String email;
}
JsController.java
#RestController
#RequestMapping("/user")
#AllArgsConstructor
public class JsController {
private final Repo repository;
private final UserResourseAssembler userResourseAssembler;
#GetMapping("/check")
public ResponseEntity<UserResourse> uploadData() {
User user = new User(UUID.randomUUID().toString(), "hi", "hello", "jj");
UserResourse userResourse = userResourseAssembler.toModel(user);
repository.save(userResourse);
return new ResponseEntity<>(userResourse, HttpStatus.OK);
}
#GetMapping("/jobId")
public List<UserResourse> getByLoginId(#PathVariable String loginId) {
return repository.findByLoginId(loginId);
}
}
I am getting the following error:
2020-09-18 10:02:35.939 ERROR 16184 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.elasticsearch.ElasticsearchException: Cannot render document to JSON] with root cause
java.lang.StackOverflowError: null
at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
I'm testing a #RestContoller in Spring Boot which has a #PostMapping method and the method #RequestBody is validated using #Valid annotation. To test it, I'm using MockMvc and in order to populate request body content I'm using Jackson ObjectMapper; however, when the model is passed, the test fails:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/user/register
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.springboottutorial.todoapp.controller.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.String> com.springboottutorial.todoapp.controller.UserController.register(com.springboottutorial.todoapp.dao.model.User)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :400
User Model:
#Entity
#Table(name = "users",
uniqueConstraints = #UniqueConstraint(columnNames = {"EMAIL"}))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "FIRST_NAME")
#NotNull
private String firstName;
#Column(name = "LAST_NAME")
#NotNull
private String lastName;
#Column(name = "EMAIL")
#NotNull
#Email
private String emailAddress;
#Column(name = "PASSWORD")
#NotNull
private String password;
#Column(name = "CREATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime createdAt;
#Column(name = "UPDATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime updatedAt;
public User(#NotNull String firstName, #NotNull String lastName,
#NotNull #Email String emailAddress, #NotNull String password,
#NotNull LocalDateTime createdAt, #NotNull LocalDateTime updatedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
this.password = password;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
//setters and getters: omitted
UserController:
#RestController
#RequestMapping("/api/user")
public class UserController {
#Autowired
UserService userService;
#PostMapping("/register")
public ResponseEntity<String> register(#RequestBody #Valid User user){
userService.createUser(user);
return ResponseEntity.ok().build();
}
}
UserControllerTest:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
When I build JSON string manually, the test passes:
String json = "{\n" +
"\t\"firstName\" : \"John\",\n" +
"\t\"lastName\" : \"QPublic\",\n" +
"\t\"password\" : \"123456789\",\n" +
"\t\"createdAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"updatedAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"emailAddress\" : \"john.public#gmail.com\"\n" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
Spring is not necessarily providing you with a "vanilla" ObjectMapper instance. By having Spring inject the ObjectMapper instance into the test instead of creating an ObjectMapper with the default constructor, you will get an instance that matches your actual run-time environment, provided that your spring profile for your unit tests is set up correctly.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper objectMapper;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(objectMapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
As chrylis mentioned in comments, the problem occurred due to Java 8 Date & Time API and Jackson serialization conflicts. By default, ObjectMapper doesn't understand the LocalDateTime data type, so I need to add com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency to my maven which is a datatype module to make Jackson recognize Java 8 Date & Time API data types. A stackoverflow question and a blog post helped me to figure out what actually my problem was.
If LocalDateTime is not a problem, I think we should implementequals in User class.
This is how I implement my rest controllers test. Maybe It could help you.
I've this abstract class to encapsulate common tests functionalities regarding to the JSON mapping.
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
public abstract class RestControllerTest {
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
StandardCharsets.UTF_8);
private HttpMessageConverter messageConverter;
protected MediaType getContentType() {
return contentType;
}
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
messageConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
messageConverter);
}
#SneakyThrows
protected String toJson(Object data) {
val mockHttpOutputMessage = new MockHttpOutputMessage();
messageConverter.write(
data, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
You could use it in your test class like this
#WebMvcTest(UserController.class)
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest extends RestControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(toJson(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
I hope it works for you
I have the following in my Controller:
#Controller
public class GreetingController {
#PostMapping("/register")
public String registerUser(#RequestBody UserEntity request) throws ServletException, IOException {
System.out.println(request.getId());
return "register";
}
}
The UserEntity is:
#Entity
#Table(name = "users")
public class UserEntity {
private int id;
private String name;
private String email;
private String password;
I get the following error:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
Note that I have Jackson installed (from this question: Jackson Databind classpath issue).
Additionally, I am able to use the public String registerUser(HttpServletRequest request) fine, but when I try using #RequestBody it just gives me that error.
How would I get the #RequestBody to be the UserEntity?
You are using the header value "application/x-www-form-urlencoded;charset=UTF-8" in the request while you should use "application/json"
I built a simple Springboot API which is hooked up to a H2 db that contains some test data. However when I hit the API endpoint I get an empty response.
[{}]
When I debug my application the user object that is returned by the controller contains the user I am expecting.
UserController.java
#RestController
#RequestMapping("api/user")
public class UserController {
private UserService userService;
public UserController(#Autowired UserService userService){
this.userService = userService;
}
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public Set<User> getAllUsers(){
final Set<User> users = userService.getAllUsers();
return users;
}
}
UserRepo.java
public interface UserRepository extends CrudRepository<User, Long> {
#Query("SELECT usr from User usr")
Set<User> getAllUsers();
}
UserService.java
public interface UserService {
Set<User> getAllUsers();
}
UserServiceImpl.java
#Service
public class UserServiceImpl implements UserService {
private final UserRepository repository;
public UserServiceImpl(#Autowired UserRepository userRepository){
this.repository = userRepository;
}
#Override
public Set<User> getAllUsers(){
final Set<User> users = repository.getAllUsers();
return users;
}
}
User.java
#Entity
#Getter
public class User {
#Id
private Long id;
private String username;
private String firstname;
private String lastname;
private String email;
private String role;
private String premium;
}
This was a rather strange issue of which I am still unable to say which. However, removing lombok's #getter and #setter annotations then implementing traditional ones fixed this issue.
You'll have to set the class members of User public to allow jackson serialise them, i.e.,
// User.java
#Entity
public class User {
#Id
public Long id;
public String username;
public String firstname;
public String lastname;
public String email;
public String role;
public String premium;
}
Note: if you'd like to not serialise a field, use #JsonIgnore instead of setting it as private, e.g.,
#Entity
public class User {
...
#JsonIgnore
public String role;
...
}
Just remove final and change Set to List as well.
and you really don`t need to do like this:
public UserController(#Autowired UserService userService)
Just remove this method and add Autowired up to userService filed.
Because final object can`t be cast to json string.
remove this as well:
produces = MediaType.APPLICATION_JSON_VALUE</h3>