Home > database >  Give 404 response on DELETE in restful Spring Boot Reactive Controller
Give 404 response on DELETE in restful Spring Boot Reactive Controller

Time:02-10

I am fairly new to Java and Spring Boot (coming from TypeScript) and experimenting with a small restful CRUD Controller using the reactive Spring Boot API.

There are many tutorials and examples out there but they all lack proper response statuses, e.g. giving a 404 on DELETE when the resource doesn't exist.

What I like to achieve is a DELETE handler which

  • returns "204 No Content" if the resource existed and was deleted successfully
  • returns "404 Not found" if the resource doesn't exist

A simple "I don't care about HTTP status" DELETE handler looks like this:

  @DeleteMapping("/{id}")
  public Mono<Void> deletePet(@PathVariable String id) {
    return petRepository.deleteById(id);
  }

This always gives status 200, even when there is no Pet for this ID.

I tried to use petRepository.findById(id) and .defaultIfEmpty() in several ways to catch the 404 case, but without luck. E.g. with this implementation I am getting always 204:

  @DeleteMapping("/{id}")
  public Mono<ResponseEntity<Void>> deletePet(@PathVariable String id) {
    return petRepository.findById(id)
      .map(pet1 -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT))
      .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND))
      .flatMap(res -> {
        return petRepository.deleteById(id)
          .map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
      });
  }

I think I understand why this isn't working, because after the .defaultIfEmpty() the Mono isn't empty anymore and the .flatMap will have something to work on (the 404 response) so the deleteById() is executed. This returns an (obviously) non empty Mono as well, so the status turns into NO_CONTENT again.

But all my (many) attempts to change this failed so I hope anyone has the right solution for this problem.

Thanks! :)

CodePudding user response:

When findById returns an empty Mono, the code below will not executed either map or flatMap and will only return the value from defaultIfEmpty

 @DeleteMapping("/{id}")
  public Mono<ResponseEntity<Void>> deletePet(@PathVariable String id) {
    return petRepository.findById(id)
      .map(pet1 -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT))
      .flatMap(res -> {
        return petRepository.deleteById(id)
          .map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
      })
      .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
  }

Also, your understanding as to why this happens in your code snippet is correct.

CodePudding user response:

After some more research I found a solution:

  @DeleteMapping("/{id}")
  public Mono<ResponseEntity<Void>> deletePet(@PathVariable String id) {
    return petRepository.findById(id)
      .defaultIfEmpty(new Pet())
      .flatMap(pet -> {
        if (null == pet.id) {
          return Mono.just(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
        }
        else {
          return petRepository.deleteById(id)
            .map(v -> new ResponseEntity<Void>(HttpStatus.NO_CONTENT));
        }
      });
  }

Now an empty Pet object is created when findById() gives an empty result using defaultIfEmpty().

So the flatMap() gets either the Pet for the given ID or an empty Pet. The latter is recognized by the fact that the id property is null which is turned into a 404 response. In the other case the Pet is deleted and 204 is returned.

So this is a possible solution - but it looks not very elegant to me.

If there is a better way to do this, please give your answer here.

  • Related