Search code examples
javaspring-bootresterror-handlingpath-variables

Spring boot PathVariable not passed getting 404 URL doesn't exists


While creating a GET endpoint in a Spring web application I have a path variable(annotated with @PathVariable) passed to my API.

If I don't pass any value on path, the controller responds with HTTP 404 status.

Endpoint: http://localhost:8080/observability-core/v1/systems/

If I pass a value it responds as expected (e.g http://localhost:8080/observability-core/v1/systems/123)

If the path variable is missing on the request, I want to throw HTTP 400 Bad Request to indicate something like is not passed.

PS: - when I hit request with above url I am not getting any logs implying no request reached the application. That could mean endpoint doesn't exists, which is misleading. How do I customise the error response in this case?

Default response from Spring controller endpoint:

{
    "timestamp": "2024-01-04T06:48:18.584+00:00",
    "status": 404,
    "error": "Not Found",
    "path": "/observability-core/v1/systems/"
}

Solution

  • My proposed solution:

    • Specify the path with and without the parameter: @GetMapping(value = { "/path", "/path/{param}" })
    • Mark the Path variable as required = false (@PathVariable(required = false) String param
    • handle the situation when parameter is missing and respond as required (with HTTP Status 400 in your case)

    Minimalistic Example

    @RestController
    @RequestMapping("/foo")
    public class FooController {
    
        @GetMapping(value = { "/{firstParam}", "/" })
        public ResponseEntity<String> getFoo(@PathVariable(required = false) String firstParam) {
            if (null == firstParam) {
                return ResponseEntity.badRequest().body("The first param is required");
            }
            return ResponseEntity.ok("The first param is: " + firstParam);
        }
    }
    

    A Test case:

    @SpringBootTest
    @AutoConfigureMockMvc
    class FooControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        void whenParameterIsPassesShouldReturnOk() throws Exception {
            mockMvc.perform(MockMvcRequestBuilders.get("/foo/param"))
                    .andExpect(status().isOk())
                    .andExpect(content().string("The first param is: param"));
        }
    
        @Test
        void whenParameterIsNotPassedShouldReturnBadRequest() throws Exception {
            mockMvc.perform(MockMvcRequestBuilders.get("/foo/"))
                    .andExpect(status().isBadRequest())
                    .andExpect(content().string("The first param is required"));
        }
    
    }