2

I am learning Spring Boot, I am trying to throw an exception when the service does not found an item on the database,therefore, I tried with optional but when I test it I only get a null response besides an exception

    @GetMapping(value = "/compras", produces = "application/json")
public Optional<Compras> retrieveAllCompras(@RequestParam String id) {
    return Optional.of(compraRepository.findById(id)).orElseThrow(RuntimeException::new);

I expect an exception when the item is not found in Database

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62

4 Answers4

5

Optional.of expects pure value. You can find the info in documentation as well,

/**
 * Constructs an instance with the described value.
 *
 * @param value the non-{@code null} value to describe
 * @throws NullPointerException if value is {@code null}
 */
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

example,

jshell> Optional.of(100)
$2 ==> Optional[100]

jshell> Optional.of(null)
|  Exception java.lang.NullPointerException
|        at Objects.requireNonNull (Objects.java:221)
|        at Optional.<init> (Optional.java:107)
|        at Optional.of (Optional.java:120)
|        at (#1:1)

If your value could be null at runtime, you can use .ofNullable,

jshell> Optional.ofNullable(null)
$3 ==> Optional.empty

ALSO The idea of functional programming is to return a value for all the inputs, instead of throwing Exception which breaks the function composition.

jshell> Function<Integer, Optional<Integer>> f = x -> Optional.of(x + 1)
f ==> $Lambda$23/0x0000000801171c40@6996db8

jshell> Function<Integer, Optional<Integer>> g = x -> Optional.of(x * 2)
g ==> $Lambda$24/0x0000000801172840@7fbe847c

jshell> f.apply(5).flatMap(x -> g.apply(x))
$13 ==> Optional[12]

So in your example you can treat Optional.empty() as item not found, but Spring will consider that as 200 as well which is still better than throwing 500. You might want to send 404 to be accurate.

@GetMapping(
  value = "/compras", 
  produces = "application/json"
)
public Optional<Compras> retrieveAllCompras(@RequestParam String id) {
    return Optional.ofNullable(compraRepository.findById(id)); //will response as 200 even when no item found
}

You can use ResponseEntity<A> to set specific http status

The traditional way of responding 404 is defining specific exception.

import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;

@GetMapping(
  value = "/compras", 
  produces = "application/json"
)
public Compras retrieveAllCompras(@RequestParam String id) {
    return Optional.ofNullable(compraRepository.findById(id))
                   .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "item not found"))
}
prayagupa
  • 30,204
  • 14
  • 155
  • 192
1

Think about the types here, if you want to return Optional, and you will do an .orElse, you have to wrap it again. It wold be a way to do:

 @GetMapping(value = "/compras", produces = "application/json")
public Optional<Compras> retrieveAllCompras(@RequestParam String id) {
    return Optional.ofNullable(compraRepository.findById(id)).
                     map(Optional::of).
                     orElseThrow(() -> new RuntimeException("Not found"));

I'll explain:

  1. Optional.ofNullable() will return an Optional<Compras>, with the value Optional("some compras") or the value Optional.Empty
  2. map(Optional::of) will wrap the optional again, so you will have Optional[Optional<Compras>] or (here the trick) Optional.Empty, because map doesn't wrap the Optional.Empty (that's his reason of being among us)
  3. Finally, orElseThrow(() -> new RuntimeException("Not found")) will unpack your optional, in this case from Optional[Optional<Compras>] to Optional<Compras> or, in case of being Optional.Empty it will throw the exception
developer_hatch
  • 15,898
  • 3
  • 42
  • 75
0

You controller returns Optional<Compras>. This means there will not be #get method called. Your controller will return null if Optional value.

Change to: public Compras retrieveAllCompras... and you'll get your exception

Artem Ptushkin
  • 1,151
  • 9
  • 17
0

Judging by the object name and invoked method (compraRepository.findById(id)), your service is actually a repository. If it's a Spring Data repository then you should leave creation of the Optional to Spring Data: Null Handling of Repository Methods. So you'll have

compraRepository.findById(id).orElseThrow(NotFoundException::new)
Adrian
  • 2,984
  • 15
  • 27