I am creating a global exception handler in spring mvc application with rest controller.
From controller when I am throwing Exception, the global exception handler catches it, but it is not returning the json data. But when I am using the #ExceptionHandler in the controller, it returns json data.
controller code:
import javax.validation.Valid;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.sphiextest1back.bean.NewUserBean;
import com.sphiextest1back.bean.ResponseBean;
import com.sphiextest1back.exception.FieldValidationException;
#RestController
#RequestMapping(value = "/service")
public class User {
#RequestMapping(value = "/test/test", method = RequestMethod.GET)
public ResponseBean testFunc(#Valid NewUserBean newUserBean, BindingResult result) throws FieldValidationException {
ResponseBean responseBean = new ResponseBean();
if(result.hasErrors()) {
throw new FieldValidationException("Error in field values", responseBean);
}
else {
responseBean.setMessage("Test Message1");
responseBean.setSuccess(true);
responseBean.setStatusCode(200);
}
return responseBean;
}
/**This Works fine when uncommented**/
/*#ExceptionHandler(FieldValidationException.class)
#ResponseBody
public ResponseBean handleFieldValidationException(Exception ex) {
ResponseBean responseBean = new ResponseBean();
responseBean.setStatusCode(401);
responseBean.setMessage(ex.getMessage());
responseBean.setSuccess(false);
return responseBean;
}*/
}
But when using global exception handler, it is not returning json data rather showing HTTP error page with status 500.
Here is my global exception handler code.
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.sphiextest1back.bean.ResponseBean;
#ControllerAdvice
public class GlobalExceptionHandler {
/**
* Method handles the FieldValidationException
*
* #param ex
* #return responseBean
*/
#ExceptionHandler(FieldValidationException.class)
public #ResponseBody ResponseBean handleFieldValidationException(Exception ex) {
ResponseBean responseBean = new ResponseBean();
responseBean.setStatusCode(401);
responseBean.setMessage(ex.getMessage());
responseBean.setSuccess(false);
return responseBean;
}
}
Is there any way to return json data from ResponseBean object!!
It was my mistake. In the xml file I did not include the global exception package for
Now I included the package and it is working fine.
Related
I have the following function from Spring Boot. I cannot do it with declarative client thus my uri domain changed after every call so i need a RestTemplate like in Spring Boot.
How can i achieve the same in Micronaut?
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
Something like this is a good starting point...
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import javax.inject.Inject;
#Controller("/")
public class SomeController {
// The url does not have to be
// hardcoded here. Could be
// something like
// #Client("${some.config.setting}")
#Client("http://localhost:8080")
#Inject
RxHttpClient httpClient;
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}
I hope that helps.
EDIT
Another similar approach:
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
#Controller("/")
public class SomeController {
private final RxHttpClient httpClient;
public SomeController(#Client("http://localhost:8080") RxHttpClient httpClient) {
this.httpClient = httpClient;
}
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}
I created a custom error page to replace the default whitelabel based on this tutorial. It worked fine but I need to pass other attributes to the page so I changed my code to intercept the error endpoint based on the geoand's answer here.
Here is my final code:
#Controller
public class ErroHandlerController implements ErrorController {
#Value("${terena.midas.location}")
private String midasLocation;
#RequestMapping("/error")
public String handleError( Model model ) {
model.addAttribute( "midasLocation", midasLocation );
return "error";
}
#Override
public String getErrorPath() {
return "/error";
}
}
Well the code worked sending my variable midasLocation but I lost the error details like path, status,message, etc... How can I bring them back again?
You need to use the ErrorAttributes which "provides access to error attributes which can be logged or presented to the user".
Take a look:
at how the default Spring Error Controller does it: BasicErrorController.java
LogicBig -
Spring Boot - Using ErrorAttributes in our custom ErrorController
Basic functionality:
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.context.request.WebRequest;
#Controller
public class ErrorHandler implements ErrorController {
private final ErrorAttributes errorAttributes;
public ErrorHandler(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#GetMapping("/error")
public String handleError(Model model, WebRequest webRequest) {
model.addAttribute("midasLocation", "xxx");
final Throwable error = errorAttributes.getError(webRequest);
model.addAttribute("exception", error);
model.addAttribute("message", error == null ? "" : error.getMessage());
return "error";
}
#Override public String getErrorPath() {
return "/error";
}
#GetMapping("/throwErrorForTest")
public String throwError() {
throw new RuntimeException("my exception");
}
}
What is recommended/best way to validate the post request DTO bean ?
If validation failed I need to send customized error message like
{
"code": "invalid_fields",
"fields": {
"email": "Required",
"password": "Required",
}
}
DTO model
public class SignUpRequest {
#JsonProperty("email")
String email;
#JsonProperty("password")
String password;
public Result validate(){
}
}
controller
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
SignUpRequest DTO has the method validate.
What is the spring way of doing the validation ?
Thanks.
You can use the following technique.
add the following dependencies in your gradle/maven file
compile "javax.validation:validation-api:2.0.1.Final"
compile "org.hibernate.validator:hibernate-validator:6.0.9.Final"
Hibernate-validator is implementation of validation-api 2.0
Add Validated annotation to your controller class
import org.springframework.validation.annotation.Validated;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
}
Add Valid annotation to your method parameter
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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 javax.validation.Valid;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
Add Validated annotation to your dto class
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Email;
#Validated
public class SignUpRequest {
#JsonProperty("email")
#Email
String email;
#JsonProperty("password")
#NotNull
String password;
}
Add ExceptionTranslator with RestControllerAdvice annotation
#RestControllerAdvice
public class ExceptionTranslator {
/**
* Exception handler for validation errors caused by method parameters #RequesParam, #PathVariable, #RequestHeader annotated with javax.validation constraints.
*/
#ExceptionHandler
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException exception) {
List<ApiError> apiErrors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
String value = (violation.getInvalidValue() == null ? null : violation.getInvalidValue().toString());
apiErrors.add(new ApiError(violation.getPropertyPath().toString(), value, violation.getMessage()));
}
return ResponseEntity.badRequest().body(apiErrors);
}
}
Create ApiError class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApiError {
#JsonIgnore
private int code;
private String field;
private String value;
private String message;
public ApiError(String message) {
this.message = message;
}
public ApiError(String field, String value, String message) {
this.field = field;
this.value = value;
this.message = message;
}
}
Now if password field is missed you'll see the following response structure:
[
{
"field": "password",
"message": "must be filled"
}
]
If you would like to use some custom logic to validate your fields you may use the following approach
Create specific annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = ContactRequiredParametersValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface ContactRequiredParameters {
String message() default
"Email or phone must be filled";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator
import org.apache.commons.lang.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ContactRequiredParametersValidator implements ConstraintValidator<ContactRequiredParameters, Object[]> {
#Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null) {
return true;
}
if (!(value[0] instanceof SignUpRequest)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
SignUpRequest contact = (SignUpRequest) value[0];
return StringUtils.isNotEmpty(contact.getPassword());
}
}
add #ContactRequiredParameters annotation to your method in controller
#PostMapping(value = "/register")
#ContactRequiredParameters
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest)
That's all. Hope it helps
Spring boot supports validation out of the box using validation-api which is included with spring web mvc starter:
#RestController
#RequiredArgsConstructor
public class TestController {
#PutMapping(value = "/", consumes = APPLICATION_JSON_VALUE)
#ResponseStatus(NO_CONTENT)
public void test(#Valid #RequestBody final SignUpRequest params) {
...
}
}
You can annotate your SignUpRequest using annotations such as javax.validation.constraints.NotNull and other more complex ones.
the error messages can be customised with message properties or hard coded strings if i18n/l10n is of less interest to you.
Sample here: https://spring.io/guides/gs/validating-form-input/
If you want behaviour outside of the provided annotations you can write a custom annotation that can do that, e.g.
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = NotPastValidator.class)
#Documented
public #interface NotPast {
String message() default "date must not be in the past";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then:
public class NotPastValidator implements ConstraintValidator<NotPast, LocalDate> {
#Override
public void initialize(final NotPast constraintAnnotation) {
// nothing to do.
}
#Override
public boolean isValid(final LocalDate value, final ConstraintValidatorContext context) {
// As the Bean Validation specification recommends, we consider null values as being valid.
return value == null || isDateNotPast(value);
}
private boolean isDateNotPast(final LocalDate value) {
return ...
}
}
And finally just annotate your field:
#NotPast
Of course this is just an example with some code I previously used, you'll need to adapt to your needs.
If you don't want to use the validator API at all you can equally just write your own code to programatically check and throw some type of custom exception when invalid. This can then be caught in the controller and you can send what ever response you want, e.g.
#RestController
public class PaymentController {
#PostMapping(value ="/", consumes = APPLICATION_JSON_VALUE)
public void makePayment(#RequestBody final PaymentParams params) {
// validationService.validate(params);
}
#ExceptionHandler(MyValidationException.class)
public ResponseEntity<ExceptionDto> paymentCardException(final MyValidationException e) {
return status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(new ExceptionDto(e.getMessage));
}
}
I'd say given the validation API is well supported by spring, to me it makes sense to apply declarative validations where possible when using this stack. Custom rules can be a little painful, but you can use a multi faceted approach with some annotation based and equally you can perform some more complex validations in your own service.
This is a custom validation.
#PostMapping
private ResponseEntity<?> addMessage(#RequestBody Message message) {
Map<String, String> response = new HashMap<>();
if (message.getInputMessage() == null || message.getInputMessage().equals("")) {
response.put("status", "E");
response.put("message", "input message can not be empty");
return ResponseEntity.ok(response);
}
int id = messageService.addMessage(message);
if (id <= 0) {
response.put("status", "E");
response.put("message", "add message has error");
return ResponseEntity.ok(response);
}
response.put("status", "S");
response.put("message", "success");
return ResponseEntity.ok(response);
}
I'm new user of Spring and I want develop a RestFull Service with Hibernate-PostGreSQL and Spring Boot. I try to learn with the documentation of Spring but I have lot of problem to deploy a simple service.
I don't use XML file properties but a Java Class.
Here are my different files :
PersistanceJPAConfig.java :
package com.spring.configuration;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
#Configuration
public class PersistenceJPAConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource driver = new DriverManagerDataSource();
driver.setDriverClassName("org.postgresql.Driver");
driver.setUrl("jdbc:postgresql://localhost:5432/test");
driver.setUsername("test");
driver.setPassword("test");
return driver;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setDataSource(dataSource());
return factory;
}
#Bean
#Autowired
public JpaTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
I have a classic model and here is the Repository :
package com.spring.persistence.repositories;
import com.spring.persistence.model.ApplicationUser;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
#Qualifier(value = "applicationUserRepository")
public interface ApplicationUserRepository extends JpaRepository<ApplicationUser,Long>{
}
A Simple service :
package com.spring.persistence.service;
import com.spring.persistence.model.ApplicationUser;
import com.spring.persistence.repositories.ApplicationUserRepository;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Transactional
public class ApplicationUserService {
private final ApplicationUserRepository applicationUserRepository;
#Autowired
public ApplicationUserService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
public ApplicationUser createUser(String username, String type, String country)
{
ApplicationUser user = new ApplicationUser(username,type,country);
return applicationUserRepository.saveAndFlush(user);
}
public List<ApplicationUser> getAllUser()
{
return applicationUserRepository.findAll();
}
public ApplicationUser getUser(Long id)
{
ApplicationUser user = null;
if(id != null)
{
user = applicationUserRepository.findOne(id);
}
return user;
}
public boolean deleteUser(Long id)
{
if(id != null)
{
try{
applicationUserRepository.delete(id);
return true;
}
catch(IllegalArgumentException ex)
{
ex.printStackTrace();
return false;
}
}
else
{
System.out.println("Id is null");
return false;
}
}
}
And finally the WebController :
#RestController
#RequestMapping(value = "/applicationuser")
public class ApplicationUserController {
#Autowired
private ApplicationUserService applicationUserService;
#RequestMapping(value="/",method = RequestMethod.GET)
#ResponseBody
public ApplicationUser index()
{
return applicationUserService.createUser("test", "test", "test");
}
}
It's possible is missing lot of things (Annotation,Initializer,Code) but I'm here to learn and any advices can help me.
Thanks for your answers
Spring Data REST
This project will allow you to achieve your goals with significantly less boilerplate code.
Follow the Accessing JPA Data with REST guide which demonstrates how to configure Spring Boot + Spring Data REST with absolutely minimal configuration.
Once you have a basic understanding, then you can add more functionality to meet your business requirements.
Detailed information is provided in the Spring Data REST Documentation
I have created a method which returns a ModelAndView in order to display a list with a jsp "resourcelist". It executes the method showResourceList() but after the return, I get a 404 error on /WEB-INF/views/resources.jsp.
"The resource requested is not found".
But in my views, there is no jsp named resources.jsp. I don't understand why it tries to find this view which doesn't exist. As you can see in my controller's code, I'm trying to forward to resourcelist.jsp and not to resources.jsp.
I've many other controllers returning a ModelAndView which work just fine.
Can anybody help me ?
Here’s the code :
package learningresourcefinder.controller;
import java.util.List;
import learningresourcefinder.model.Resource;
import learningresourcefinder.repository.ResourceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.ModelAndView;
#Controller
public class RessourceListController extends BaseController<Resource>{
#Autowired ResourceRepository resourcerepository;
#RequestMapping("/resources")
public ModelAndView showResourceList () {
List<Resource> list=resourcerepository.findAllRessourceOrderByTitle();
return new ModelAndView("resourcelist", "resourceList", list);
}
}
Many thanks!
Sébastien.
Have you tried putting #RequestMapping("/resources") after the #Controller annotation and then have a String of the correct mapping/view as the return value in the showResourceList() method. So, for example:
#Controller
#RequestMapping("/resources")
public class RessourceListController extends BaseController<Resource>{
...
#RequestMapping(method = RequestMethod.GET)
public String showResourcesList(ModelMap model) {
// retrieve the list instance
model.addAttribute("resourcesList", list);
return "/resources/resourceslist";
}
...
}
Also, how is your viewResolver bean configured in your Spring configuration?
i've compared with another classes and i've seen i've done a wrong import. I had imported "springframework.web.servlet.Modelandview" instead importing "springframework.web.portlet.Modelandview".
Sorry...