1

I use Optional in my Spring Boot project, but I am not sure if I also need to pass it to Controller as return type. So, could you pls clarify me about the following issues?

Here is the example code to describe my question better:

repository:

Optional<Product> findByCode(String code);

service:

public Optional<Product> findByCode(String code) {
    return productRepository.findByCode(code)
            .orElseGet(Optional::empty);
}

controller:

@GetMapping("/products/{code}")
public ResponseEntity<Optional<Product>> findByCode(@PathVariable String code) {

    Optional<Product> product = productService.findByCode(code);
    if (product.isPresent()) {
        return new ResponseEntity<>(product, HttpStatus.OK);
    }
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

1. I think we use Optional, when the result may be empty. For example, in the previous situation, there may be no product for the given code. Is that all, or may there be some other common examples that make us to use of Optional?

2. In the given example, I try to use something in the service method, but it is not working. But I really have no idea if I should use Optional as return value to the Controller. So, could you give a proper service and Controller method example for this service method?

4 Answers4

2

repository: No change

Optional<Product> findByCode(String code);

service: .orElseGet(Optional::empty); <= Useless

public Optional<Product> findByCode(String code) {
    return productRepository.findByCode(code);
}

controller:

@GetMapping("/products/{code}")
public ResponseEntity<Product> findByCode(@PathVariable String code) {
    return productService.findByCode(code).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.noContent().build());
}
Zorglube
  • 664
  • 1
  • 7
  • 15
  • Seems good, thanks. What about moving `orElseGet` to the service and returning `DTO` from it? Or returning a suitable type instead of `Optional` ? Any alternative pls as **Update** by keeping your valuable answer? –  Jul 12 '22 at 10:19
  • `Seems good, thanks. What about moving orElseGet to the service and returning DTO from it?` <= That's your decision. – Zorglube Jul 12 '22 at 12:08
  • Sorry, I am confused. I mean to return `ResponseEntity` from service. Could you please share a proper `ResponseEntity` example? I will build it in the service method and return it to the Controller. Thanks a lot. –  Jul 12 '22 at 15:06
  • By the way, I would vote up but I cannot vote up as I cannot enough reputation :( Any vote up pls? –  Jul 12 '22 at 15:06
  • Any reply about what ? If you like to make you Service returning `ResponseEntity`, I say : it si a bad idea ! `ResponseEntity` is related to the purely technical communication, and service is related to the functional processing of you datas, so your service shouldn't carry anything related to the technical side. I mean it might stay functional. – Zorglube Jul 12 '22 at 19:34
  • Good explanation, thanks. I will keep in mind that, sorry but I am really too confused and try to learn a most proper way. Your helps are valuable for me and after making some research, now I am at this point. Could you please have a look at [this](https://stackoverflow.com/questions/72957432/best-way-to-return-responseentity-with-data-empty-and-error-in-spring-boot) question? I am waiting for your valuable comments. Regards –  Jul 12 '22 at 19:41
0

You don't return an optional from endpoints, you return the entity or dto. You could use the optional like that:

@GetMapping("/products/{code}")
public ResponseEntity<Product> findByCode(@PathVariable String code) {
  Optional<Product> product = productService.findByCode(code);
  return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.noContent().build());
}

Few notes about your code:

  1. Your service does not make much sense - .orElseGet(Optional::empty) is pointless, if the optional is empty, return empty optional. You can just do:
return productRepository.findByCode(code);

and that's all.

  1. It makes no sense to return code no content(204) when the requested resource is not found. The logical response code would be not found(404). For your code, like this:
return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
Chaosfire
  • 4,818
  • 4
  • 8
  • 23
  • Thanks a lot, I am not sure to pass `Optional` to the Controller. So, what about using `Optional` in Repository and Service only and return other type e.g. `T` to the controller? Could you pls update your answer like that? –  Jul 12 '22 at 10:39
  • @binary It's up to you to decide whether to use `T` or `Optional` in the controller. The code would be equivalent to what you already have, but using **null check** instead of `.isPresent()`. Tbh i don't see what you would gain from using `T`, considering the way you handle missing/not found values, but as i said, it's your decision. – Chaosfire Jul 12 '22 at 14:22
  • according to the links you suggested and search, now I think it would be good idea to return a ``ResponseEntity` class from Service to Controller. If it is a good idea according to you, could you please post a proper ResponseEntity class? –  Jul 12 '22 at 15:08
  • @binary Personally i don't like it, but again, it's your decision. This is also starting to delve into the realm of opinions and opinions are not welcome, only facts. Anyway, the change is simple enough - just change the service method return type and move the logic from controller method to service method. – Chaosfire Jul 12 '22 at 15:34
  • What about this? https://stackoverflow.com/questions/72957432/best-way-to-return-responseentity-with-data-empty-and-error-in-spring-boot –  Jul 12 '22 at 19:42
0

You don't need to pass Optional to Controller as a return type. when you don't even need to declare service method as return type optional. when you use the orElseGet method like

productRepository.findByCode(code)
            .orElseGet(Optional::empty);

you are converting your result to product data if it's available or to empty data type of product. You can declare your service method as:

public Product findByCode(String code) {
    return productRepository.findByCode(code)
            .orElseGet(Optional::empty);
}

and your controller as:

@GetMapping("/products/{code}")
public ResponseEntity<Product> findByCode(@PathVariable String code) {

    Product product = productService.findByCode(code);
    if (product.isPresent()) {
        return new ResponseEntity<>(product, HttpStatus.OK);
    }
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

But if you want to return the optional type then you should remove orElseGet from service method and make it like:

public Optional<Product> findByCode(String code) {
    return productRepository.findByCode(code)
}
gaurav jha
  • 109
  • 2
  • Thanks a lot, but I think it would be good idea not to pass Optional to the Controller. In this scene, how can I treated the result in the controller? SHould I use if block you posted in that case? –  Jul 12 '22 at 10:42
-2

Generally Optional<T> was introduced to avoid working with null values. If we really want to assign a non-existing value to a variable we should use Optional<T> so that we can prevent null pointer exceptions. And it is not mandatory to add it to a controller. You can do something like this if we don't want to give Optional<T> to the controller:

@GetMapping("/products/{code}")
public ResponseEntity<Product> findByCode(@PathVariable String code){
    Optional<Product> products=productService.findProductById(String);
    if(products.isPresent()){
        Product product=products.get();
        return new ResponseEntity<>(product,HttpStatus.OK);
    }
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

And you can make your service method like this:

public Optional<Product> findProductByCode(String code) {
    return productRepository.findProductByCode(code);
}
flyingfishcattle
  • 1,817
  • 3
  • 14
  • 25
Ravi Kiran
  • 44
  • 4
  • Test is useless, have a look on the `map` method of the `Optional` : https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Function- – Zorglube Jul 12 '22 at 09:53
  • This is not exactly true. Please check [Uses for Optional](https://stackoverflow.com/questions/23454952/uses-for-optional). – Chaosfire Jul 12 '22 at 10:10
  • @Chaosfire : to know the exact intent of `Optional`, and what is have been designed for, you might read the JEP and JCP paper. – Zorglube Jul 12 '22 at 12:23