Getting HttpClientErrorException 404 RestTemplate getForObject - java

I am trying to make a REST service run that was written by my uni professor, it has the following 3 modules:
A core module containing services, repositories, model classes
A web module containing controllers exposed as RESTful Web Services
A client module containing a console-based ui that accesses the
RESTful Web Services using RestTemplate.
These are my config files in the web module.
WebConfig
#Configuration
#EnableWebMvc
#ComponentScan({"ro.ubb.catalog.web.controller", "ro.ubb.catalog.web.converter"})
public class WebConfig {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.exposedHeaders("Access-Control-Allow-Origin:*")
.allowedOrigins("localhost:8080","localhost:4200")
.allowedMethods("GET", "PUT", "POST", "DELETE");
}
};
}
}
AppLocalConfig
#Configuration
#ComponentScan({"ro.ubb.catalog.core"})
#Import({JPAConfig.class})
#PropertySources({#PropertySource(value = "classpath:local/db.properties"),
})
public class AppLocalConfig {
/**
* Enables placeholders usage with SpEL expressions.
*
* #return
*/
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
and a initializer.
public class Initializer implements WebApplicationInitializer {
public void onStartup(ServletContext container)
throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.scan("ro.ubb.catalog.web.config");
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/api/*");
}
}
In my client module i have the following config file:
#Configuration
#ComponentScan("ro.ubb.catalog.client.ui")
public class ClientConfig {
#Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
The console:
#Component
public class ClientConsole {
#Autowired
private RestTemplate restTemplate;
public void runConsole() {
String url = "http://localhost:8080/api/students";
StudentsDto students = restTemplate.getForObject(url, StudentsDto.class);
System.out.println(students);
StudentDto savedStudent = restTemplate.postForObject(url,
new StudentDto("saved-st", 10),
StudentDto.class);
System.out.println("saved student:");
System.out.println(savedStudent);
savedStudent.setName("update-st");
restTemplate.put(url + "/{id}", savedStudent, savedStudent.getId());
System.out.println("update:");
System.out.println(restTemplate.getForObject(url, StudentsDto.class));
restTemplate.delete(url + "/{id}", savedStudent.getId());
System.out.println("delete:");
System.out.println(restTemplate.getForObject(url, StudentsDto.class));
}
}
And the client main:
public class ClientApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("ro.ubb.catalog.client.config");
ClientConsole console = context.getBean(ClientConsole.class);
console.runConsole();
System.out.println("bye ");
}
}
And lastly this is the controller in the web module:
#RestController
public class StudentController {
#Autowired
private StudentService studentService;
#Autowired
private StudentConverter studentConverter;
#RequestMapping(value = "/students")
StudentsDto getAllStudents() {
return new StudentsDto(
studentConverter.convertModelsToDtos(
studentService.getAllStudents()));
}
#RequestMapping(value = "/students", method = RequestMethod.POST)
StudentDto addStudent(#RequestBody StudentDto studentDto){
var student = studentConverter.convertDtoToModel(studentDto);
var result = studentService.saveStudent(student);
var resultModel = studentConverter.convertModelToDto(result);
return resultModel;
}
#RequestMapping(value = "/students/{id}", method = RequestMethod.PUT)
StudentDto updateStudent(#PathVariable Long id,
#RequestBody StudentDto dto) {
return
studentConverter.convertModelToDto(
studentService.updateStudent(
studentConverter.convertDtoToModel(dto)
));
}
#RequestMapping(value = "/students/{id}", method = RequestMethod.DELETE)
ResponseEntity<?> deleteStudent(#PathVariable Long id) {
studentService.deleteStudent(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
First I am wondering if maybe I configured the server wrong, I installed tomcat and added to the webapps folder the jar file of the root project module. The server seems to be running, so I am not entirely sure if that is the problem. I created a table with entities, still the same error.
I am not familiar with the code as I am trying first to get it to work and then practice around with it so I understand how everything works.
This is the entire stack trace
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (file:/C:/Users/aytre/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.0.3.RELEASE/8950eb10c466a77677693dd495d4b6a26de315f4/spring-core-5.0.3.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 404 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:773)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:726)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:682)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:330)
at ro.ubb.catalog.client.ui.ClientConsole.runConsole(ClientConsole.java:17)
at ro.ubb.catalog.client.ClientApp.main(ClientApp.java:15)

Related

Spring MVC Matrix Variables retrive only one param value

I'm building a spring-mvc rest API application and I intend to use matrix variables for some of my endpoints. Unfortunately I'm not able to retrive more than one value per matrix variable used.
My spring-mvc version is spring-webmvc:4.3.12.RELEASE
I followed the steps shown in this example of implementation : http://www.baeldung.com/spring-mvc-matrix-variables.
I've enabled Spring MVC Matrix Variables :
package fr.compagny.project.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.util.UrlPathHelper;
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
So I've created 2 test endpoints :
package fr.compagny.project.webservice;
import [...]
#Api
#RefreshScope
#RestController
#RequestMapping(value = "/my_awesome_project")
public class ProjectWS {
//Services
private ProjectService projectService;
//Validator
private ValidatorService validator;
#ApiOperation(value = "Matrix Variable Test 1.")
#GetMapping(value = "/matrix_test_one/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String getMatrixTestOne (#MatrixVariable(pathVar = "vars", required = true) String v1,
#MatrixVariable(pathVar = "vars", required = true) String v2,
#MatrixVariable(pathVar = "vars", required = true) String v3) {
return v1 + v2 + v3;
}
#ApiOperation(value = "Matrix Variable Test 2.")
#GetMapping(value = "/matrix_test_two/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Map<String, String> getMatrixTestTwo (#MatrixVariable Map<String, String> vars) {
return vars;
}
#Autowired
public void setProjectService(ProjectService projectService) {
this.projectService = projectService;
}
#Autowired
public void setValidatorService(ValidatorService validatorService) {
this.validator = validatorService;
}
}
When I call
GET http://[...]/my_awesome_project/matrix_test_one/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_one/v1=toto
I have the same following error message :
There was an unexpected error (type=Bad Request, status=400). Missing
matrix variable 'v2' for method parameter of type String
But when I call
GET http://[...]/my_awesome_project/matrix_test_one/v2=titi
OR
GET http://[...]/my_awesome_project/matrix_test_one/[anything except "v1=*"]
I have the same following error message :
There was an unexpected error (type=Bad Request, status=400).
Missing matrix variable 'v1' for method parameter of type String
So Spring seems ok to get the first element of the matrix variable but stop then.
So I keep trying with the second test function :
GET http://[...]/my_awesome_project/matrix_test_two/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_two/v1=toto
Return :
{
"v1": "toto"
}
-
GET http://[...]/my_awesome_project/matrix_test_two/v2=titi;v1=toto;v3=tata
Return :
{
"v2": "titi"
}
So this behavior seems to confirm my fears.
Did you see something I missed in order to enable matrix variable support (maybe related to semicolon) ?
The mentioned example is using Spring Boot. Launching the exmaple via Spring Boot works as expected. Without Spring Boot it doesn't work out of the box as it is explained in this Q&A. The reason is the UrlPathHelper injected from the #Configuration (point 2 in the exmple) isn't used to process the request. A default instance of UrlPathHelper is used and therefore urlPathHelper.shouldRemoveSemicolonContent() returns true. This removes the matrix variables from the request.
EDIT:
I debugged into it and it turned out that there are two beans of type RequestMappingHandlerMapping.
So I tried this configuration:
#Configuration
#ComponentScan(basePackageClasses = { WebMvcConfiguration.class })
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
#Bean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
#Qualifier("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
public RequestMappingHandlerMapping fullyQualifiedRequestMappingHandlerMapping() {
return requestMappingHandlerMapping();
}
#Bean
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
requestMappingHandlerMapping.getUrlPathHelper().setRemoveSemicolonContent(false);
return requestMappingHandlerMapping;
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = configurer.getUrlPathHelper();
if (urlPathHelper == null) {
urlPathHelper = new UrlPathHelper();
}
urlPathHelper.setRemoveSemicolonContent(false);
}
}
But the fully qualified bean wasn't created by the first method. This bean is processing the request. So the matrix variables were still removed.
As I was unable to provide a factory method for the bean I tried to modify the state of the bean:
#Component
public class Initializer {
#Autowired
private ApplicationContext appContext;
#PostConstruct
protected void init() {
initUrlPathHelper("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
initUrlPathHelper("requestMappingHandlerMapping");
}
protected void initUrlPathHelper(String beanName) {
AbstractHandlerMapping b = (AbstractHandlerMapping) appContext.getBean(beanName);
b.setUrlPathHelper(urlPathHelper());
}
public UrlPathHelper urlPathHelper() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
return urlPathHelper;
}
}
This did it for me. The matrix variables have been mapped.

Spring MVC with RESTful Services and Static HTML

I am using Java8 with Spring 4.3.1.RELEASE. I have a project that needs to serve both static html pages and RESTful Services.
I can get one to work at a time, but not both at the same time.
For example, I need to access:
http://localhost:8080/jbosswildfly-1.0/category/list
http://localhost:8080/jbosswildfly-1.0/category/list/{id}
and
http://localhost:8080/jbosswildfly-1.0/index.html
http://localhost:8080/jbosswildfly-1.0/tocs.html
http://localhost:8080/jbosswildfly-1.0/www/index.html
My issue is with regards to my servlet-mapping.
I have the following:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
Dynamic rest = servletContext.addServlet("rest", new DispatcherServlet(ctx));
//dynamic.addMapping("/**/*.do");
rest.addMapping("/*.html");
rest.addMapping("/category/list");
rest.addMapping("/category/list/*");
rest.setLoadOnStartup(1);
}
}
I have tried a number of combinations of mappings, but cannot seem to get static content and RESTful services to work simultaneously.
In the above example, I can get the following to work:
http://localhost:8080/jbosswildfly-1.0/index.html
http://localhost:8080/jbosswildfly-1.0/snoop.jsp
http://localhost:8080/jbosswildfly-1.0/WebContent/index.html
http://localhost:8080/jbosswildfly-1.0/category/list
But, the following is not found:
http://localhost:8080/jbosswildfly-1.0/category/list/AC
The following allows the RESTful Services to be accessed, but not the static html files:
rest.addMapping("/");
Any help appreciated.
UPDATE
Here is the code for one of my RESTful Services:
#CrossOrigin(origins = {"*"})
#RestController
#RequestMapping(CategoryRESTService.BASE_URI)
public class CategoryRESTService {
public static final String BASE_URI = "/category";
#Autowired
private CategoryService categoryService;
#RequestMapping(value = "/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Category>> findAllCategorys() {
List<Category> categories = categoryService.findAll();
return new ResponseEntity<List<Category>>(categories, HttpStatus.OK);
}
#RequestMapping(value = "/list/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Category> findCategoryById(#PathVariable String id) {
Category category = categoryService.findById(id);
return new ResponseEntity<Category>(category, HttpStatus.OK);
}
}
try to use rest.addMapping("/"); mapping, at the same time you have to configure static resource resolver, for example
through xml configuration
<mvc:resources mapping="*.html" location="location of the resource folder" />
or java-base config
#Configuration
#EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("*.html")
.addResourceLocations("location of the resource folder");
}
}

AnjularJs integration with Spring boot

I need to call index() method in java class. But I tried this way it is not working.
It is going up to console.log('coming here....'); in controller.js, after that http path is not recognizing.
#RestController
public class DatumBoxShedule {
#Autowired
private DatumService datumService;
#RequestMapping(value = "/loadIndex", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public String index() throws IOException {
}
}
controller.js
app.controller('datumBoxShedule', function($scope, $http) {
$scope.newTodo = {};
$scope.loadIndex = function(){
console.log('coming here....');
$http.get('loadIndex')
.success(function(data, status, headers, config) {
$scope.todos = data;
})
.error(function(data, status, headers, config) {
alert('Error loading DatumBoxShedule');
});
};
$scope.loadIndex();
});
Is the Angular project part of the Spring project?
Are other mappings working (in other words: is the REST-Service running)?
If not: do you have an embedded container like Tomcat in your depenedencies?
For example, you could add the dependency for Tomcat to your project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
I figureout ,issue is not in the anujularjs.Issue is in the spring.
my componantscan is not working
package main.java.datumbox;
#Configuration
#SpringBootApplication
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
#ComponentScan({"main.java.datumbox.service.impl","main.java.datumbox.controller","main.java.datumbox.service"})
public class Application{
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class,args);
}
}
package main.java.datumbox.service.impl;
public class DatumServiceImpl{
#Autowired(required = true)
DatumDataRepository datumDataRepository;
}
package main.java.datumbox.controller;
#RestController
public class DatumBoxController {
#Autowired
private DatumService datumService;
#Autowired
private DatumServiceImpl datumServiceImpl;
#RequestMapping( value = "/loadIndex" , produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public String index() throws IOException {
}
}
package main.java.datumbox.service;
#Service
public class DatumService{
#Autowired
HitApiService hitApiService;
}
error is coming..
APPLICATION FAILED TO START
Description:
Field datumServiceImpl in main.java.datumbox.controller.DatumBoxController required a bean of type 'main.java.datumbox.service.impl.DatumServiceImpl' that could not be found.
Action:
Consider defining a bean of type 'main.java.datumbox.service.impl.DatumServiceImpl' in your configuration.

Custom Messages in Bean Validation using Spring’s Validator interface

I'm using Spring Boot 1.3.5 with Rest Controllers and everything is working fine.
I am also using Spring's validation sample techniques from the official documentation (JSR-303 Bean Validation API and Spring's validator interface, i tried both and faced the same problem) and the validations are working, but I am not able to configure custom messages.
I have configured a messages.properties file, and I can access the messages on this file just fine. However this validation seems not to be capable of reading or accessing my messages source (messages.properties) configured automatically via spring boot.
I can access the messages directly from the messages source object injected in controller via #Autowired (there's a comment in the code). However, the binding result of the Spring's validator interface or the JSR-303 Bean Validation seems to not be capable of accessing the messages.properties loaded in MessageSource. The result I have is that my errors have codes but don't have default messages.
Here is my Application class:
#SpringBootApplication
#ImportResource({ "classpath:security/cas-context.xml", "classpath:security/cas-integration.xml",
"classpath:security/security.xml" })
#EnableAutoConfiguration(exclude = VelocityAutoConfiguration.class) // http://stackoverflow.com/questions/32067759/spring-boot-starter-cache-velocity-is-missing
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Nfse nfseService() {
return new NfseImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), nfseService());
endpoint.publish("/nfseSOAP");
return endpoint;
}
}
Here is my Bean:
public class Protocolo {
private Long id;
#NotNull
#Min(1)
#Max(1)
private String protocolo;
private StatusProtocoloEnum status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProtocolo() {
return protocolo;
}
public void setProtocolo(String protocolo) {
this.protocolo = protocolo;
}
public StatusProtocoloEnum getStatus() {
return status;
}
public void setStatus(StatusProtocoloEnum status) {
this.status = status;
}
}
Here is My rest controller:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getAuthorities());
System.out.println(messageSource.getMessage("protocolo.tamanho", null, null));
// IN THIS PART I'M ABLE TO PRINT THE MESSAGE IF VALIDATION IS DISABLED
System.out.println(p.getProtocolo());
}
}
So, this code works fine and the method is not called since i'm calling the method with a invalid Protocolo. However, my angularJS client receives the response with the errors codes populated but with all the default messages empty since the validation is not seeing my loaded messages.properties.
Is there a way to make my Spring validation Interfaces or JSR-303 validation incorporate the loaded message.properties (messagesource) in spring boot ? How can i correct this ? If it's necessary i can paste my code sample of Spring Validation interfaces also.
Thank's a lot,
Tarcísio.
TEST CODE:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
System.out.println(messageSource.getMessage("Min.protocolo.protocolo", null, null));
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
System.out.println(bindingResult.getFieldError().getCode());
}
System.out.println(p.getProtocolo());
}
}
Edit:
Known Bug in Spring Boot 1.5.3 see https://github.com/spring-projects/spring-boot/issues/8979
In Spring Boot since 1.5.3 you need to do this
#Configuration
public class ValidationMessageConfig {
#Bean
public LocalValidatorFactoryBean mvcValidator(MessageSource messageSource) {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
and then it will work.
With version 1.5.2 and before you can extend WebMVcConfigurerAdapter
#Configuration
public class ProfileMvcConfig extends WebMvcConfigurerAdapter {
private MessageSource messageSource;
#Autowired
public ProfileMvcConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This method is overridden due to use the {#link MessageSource message source} in bean validation.
*
* #return A Validator using the {#link MessageSource message source} in bean validation.
*/
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
also see the documentation
In Spring Boot applicaton MessageSource is configured with a MessageSourceAutoConfiguration and you don't need to autowire it. For jsr303, create proper key-value pair in the messages.properties file. For "protocolo" field, you should have following values in property file.
NotNull.protocolo.protocolo=Field cannot be left blank
Min.protocolo.protocolo=Minimum value must be {1}
You can also check messages from property file like below in your code.
public void testar(#Valid #RequestBody Protocolo p,BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}
}
you should have following values in property file:
Min.protocolo.protocolo=Minimum value must be {1}
then in the controller you obtain the message by calling function getMessage from messageSource object
Test code:
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getFieldErrors().forEach(fieldError ->
System.out.println(messageSource.getMessage(fieldError, Locale.getDefault()))
);
}
System.out.println(p.getProtocolo());
}
I solved this in custom message in Spring validation read the last part of my answer.
Check this example as well.
I used a custom validator with custom annotation. I needed to change code in my custom validator.
public class PersonValidator implements ConstraintValidator {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
if (somethingIsInvalid()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Something is invalid.").addConstraintViolation();
return false;
}
return true;
}
}

Spring Boot Rest - How to configure 404 - resource not found

I got a working spring boot rest service. When the path is wrong it doesn't return anything. No response At all. At the same time it doesn't throw error either. Ideally I expected a 404 not found error.
I got a GlobalErrorHandler
#ControllerAdvice
public class GlobalErrorHandler extends ResponseEntityExceptionHandler {
}
There is this method in ResponseEntityExceptionHandler
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
I have marked error.whitelabel.enabled=false in my properties
What else must I do for this service to throw a 404 not found response back to clients
I referred a lot of threads and don't see this trouble faced by anybody.
This is my main application class
#EnableAutoConfiguration // Sprint Boot Auto Configuration
#ComponentScan(basePackages = "com.xxxx")
#EnableJpaRepositories("com.xxxxxxxx") // To segregate MongoDB
// and JPA repositories.
// Otherwise not needed.
#EnableSwagger // auto generation of API docs
#SpringBootApplication
#EnableAspectJAutoProxy
#EnableConfigurationProperties
public class Application extends SpringBootServletInitializer {
private static Class<Application> appClass = Application.class;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(appClass).properties(getProperties());
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public FilterRegistrationBean correlationHeaderFilter() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(new CorrelationHeaderFilter());
filterRegBean.setUrlPatterns(Arrays.asList("/*"));
return filterRegBean;
}
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
static Properties getProperties() {
Properties props = new Properties();
props.put("spring.config.location", "classpath:/");
return props;
}
#Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter webMvcConfigurerAdapter = new WebMvcConfigurerAdapter() {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).favorParameter(true).parameterName("media-type")
.ignoreAcceptHeader(false).useJaf(false).defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML).mediaType("json", MediaType.APPLICATION_JSON);
}
};
return webMvcConfigurerAdapter;
}
#Bean
public RequestMappingHandlerMapping defaultAnnotationHandlerMapping() {
RequestMappingHandlerMapping bean = new RequestMappingHandlerMapping();
bean.setUseSuffixPatternMatch(false);
return bean;
}
}
The solution is pretty easy:
First you need to implement the controller that will handle all error cases. This controller must have #ControllerAdvice -- required to define #ExceptionHandler that apply to all #RequestMappings.
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Provide exception you want to override response in #ExceptionHandler. NoHandlerFoundException is an exception that will be generated when Spring will not be able to delegate request (404 case). You also can specify Throwable to override any exceptions.
Second you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Result when I use non defined URL
{
"errorCode": "custom_404",
"errorMessage": "message for 404 error code"
}
UPDATE: In case you configure your SpringBoot application using application.properties then you need to add the following properties instead of configuring DispatcherServlet in main method (thanks to #mengchengfeng):
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
I know this is an old question but here is another way to configure the DispatcherServlet in code but not in the main class. You can use a separate #Configuration class:
#EnableWebMvc
#Configuration
public class ExceptionHandlingConfig {
#Autowired
private DispatcherServlet dispatcherServlet;
#PostConstruct
private void configureDispatcherServlet() {
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Please not that this does not work without the #EnableWebMvc annotation.
Add this to your Properties file.
spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
In your #ControllerAdvice class add this:
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Object> handleNoHandlerFound404() {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);;
}

Categories