I'm building a REST API with SpringBoot and decided to build it in SpringBoot last version.
The problem I am having, is that for some reason my code seems not to be reaching OrElseGet, or I'm not knowing how to deal with the Optional stuff.
What I want to do is return the status code 200 and the entity in case the object is found in the database, and status code 404 if not found.
However, when specifying an invalid code, I am getting the string null in the response body and the status code 200.
Here is my code:
#GetMapping("/{codigo}")
public ResponseEntity<Optional<Categoria>> searchByCode(#PathVariable Long codigo) {
return Optional
.ofNullable( categoriaRepository.findById(codigo) )
.map(cat-> ResponseEntity.ok().body(cat))
.orElseGet(() -> ResponseEntity.notFound().build());
}
Any help would be appreciated.
There are few improvements that can be done here. Firstly why are you returning an Optional from your REST Controller. I don't see any point there. You can merely return the Categoria object instead and let jackson to serialize it into a json payload. So change the code as below.
#RequestMapping(value = "/{codigo}")
public ResponseEntity<Categoria> searchByCode(#PathVariable Long codigo) {
return categoriaRepository.findById(codigo).map(cat -> ResponseEntity.ok().body(cat))
.orElse(ResponseEntity.notFound().build());
}
And here's the mock repository I used to simulate this.
public interface CategoriaRepository extends JpaRepository<Categoria, Integer>{
default Optional<Categoria> findById(long codigo) {
// return Optional.ofNullable(new Categoria(1, "name"));
return Optional.ofNullable(null);
}
}
Also wrapping the response inside of another Optional adds some complexity to your code while making it much harder to read too. This should give you the desired result.
Related
If I have a Get request that returns orders of clients, how can I filter the response to give me the objects that have a specific value for example that are made by those specific clients in Spring Boot?
I have tried with #PathVariable and #RequestParams but every attempt failed.
Thank you in advance.
If you want to show a specific order which has an identifier of some sort, use #PathVariable. In the following example, the identifier is a String, but in many case it will rather be long or an Integer.
#RestController
#RequestMapping("/orders")
public class OrdersController {
#GetMapping("/{id}")
public Order getOrder(#PathVariable("id") String id) {
// get the order with a specified id from the backend
}
}
The web request in this case will look like http:/<host>:<port>/orders/123
If you want to filter the order by some name, like 'madeBy John', use Request parameter:
#RestController
#RequestMapping("/orders")
public class OrdersController {
#GetMapping("/")
public List<Order> getOrdersFilteredByName(#RequestParam("madeBy") madeBy) {
// get the order filtered by the person who made the order
// note, this one returns the list
}
}
In this case the web request will look like this: http:/<host>:<port>/orders?madeBy=John
Note that technically you can implement whatever you want at the backend, so you can pass, say, John in the first example as a path variable, on server its a String after all, however what I've described is a straightforward and kind-of-standard way of doing these things - so can expect to see this convention in many projects at least.
#RestController
#RequestMapping("/order")
public class OrderController {
// http://<host>:<port>/order/1
#GetMapping("/{id}")
public Order getOrder(#PathVariable Long id) {
// Return your order
}
// http://<host>:<port>/order?madeBy=John
#GetMapping("/)
public List<Order> getOrdersMadeBy(#RequestParam("madeBy") String madeBy) {
// Return your order list
}
}
I'm using a RouterFunction to define endpoints in my Spring Boot application. My service returns a Mono<Object> and I want to return the result of this when the endpoint is called. I also need to authenticate so I pass a UserPrinciple object through.
Router
#Bean
RouterFunction<ServerResponse> router() {
return route()
.GET("/api/endpoint-name", this::getExample)
.build();
}
private Mono<ServerResponse> getExample(ServerRequest request) {
return ServerResponse.ok().body(fromPublisher(getUserPrincipal().map(service::getSomething), Object.class)).log();
}
private Mono<UserPrincipal> getUserPrincipal() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.map(UserPrincipal.class::cast);
}
Service
public Mono<Object> getSomething(UserPrincipal userPrincipal) {
WebClient webClient = getWebClient(userPrincipal.getJwt());
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("another/server/endpoint").build())
.retrieve()
.bodyToMono(Object.class);
}
The endpoint is returning this:
{
"scanAvailable": true
}
which suggests that I'm passing the Mono into the body of the response instead of passing in the result. However I've used fromPublisher which I thought would resolve this.
I can't find any examples where the service returns a Mono and the route correctly returns the result of the Mono.
How can I correctly pass a Mono/Flux as the body of the response?
im not going to explain the difference between mapand flatMapsince i have already written a quite comprehensive explanation here:
Do you have a test to show differences between the reactor map() and flatMap()?
The problem in the above code is the return of Object. And input parameters of Object into certain functions. The first function is pretty straight forward
Mono<UserPrincipal> = getUserPrincipal();
While the second one gets a bit more hairy:
Mono<Mono<Object> value = getUserPrincipal().map(service::getSomething);
So why are we getting A nested Mono?, well the get something returns a Mono<Object> and the Map return according the the api is Mono<R> where R is what we return from getSomething.
We then stick it into the fromPublisher which will unrap the first Mono ending up trying to serialize the Mono<Object>resulting in the strange response.
{
"scanAvailable": true
}
The answer here is pay more close attention to the type system. The body function takes a Publisher (Mono or Flux) so you don't need the fromPublisher function.
And also changing map to flatMap since the return type from inside a flatMap is a publisher.
ServerResponse.ok()
.body(getUserPrincipal()
.flatMap(service::getSomething), Object.class));
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! :)
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.
After some more research I found a solution:
#DeleteMapping("/{id}")
#ResponseStatus(HttpStatus.NO_CONTENT)
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. But note, that the .map() there isn't executed because of the empty deleteById() result. It's just necessary to satisfy the generic interface here. The 204 comes from the #ResponseStatus annotation.
So this is a possible solution - but it looks not very elegant to me (creating an empty Pet and having this no-op deleteById().map()).
If there is a better way to do this, please give your answer here.
I have a DTO class like this :
public class User {
#Field("id")
private String id;
private String userName;
private String emailId;
}
I have to provide an update and delete feature through API.
I have written the following code to delete the record:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
RemoveUserDetails method is something like this :
public Mono<User> removeUserDetails(User userObj) {
return findByUsername(userObj.getUsername())
.flatMap(existingUser -> {
// logic to delete the data from database which working as expected
}).switchIfEmpty(
Mono.defer(() -> {
return Mono.error(new Exception("User Name " + userObj.getUsername() + " doesn't exist."));
})
);
}
The problem with this code is even if the user is not existing, it is not showing the Mono error I'm returning. In every case, this always returns "Remove Successful".
How can I change my service layer method so that it can return whatever is received by the repo method? I'm new to Reactor code, so unable to figure out how to write it.
Whenever you call subscribe, consider it an immediate red flag. Subscription is something that should be handled by the framework you're using (Webflux in this case.)
If you subscribe yourself, such as in this example:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
...then you've essentially created a "fire and forget" type subscription, where you have no way of knowing if that publisher completed successfully, if it caused an error, how long it took to complete, whether it completed at all, or whether it emitted an element. So in this case, you're saying "send a request to remove user details, forget you sent it, and then before waiting for any kind of result, always return 'Remove successful'." This is almost never what you want.
You could use something like:
public Mono<String> userData(User body) {
return repo.removeUserDetails(userObj)
.then(Mono.just("Remove Successful"));
}
...which is much better as it includes everything as part of the reactive chain. In this case, you'll either get an error signal, or you'll get "Remove Successful".
However, chances are you don't need that String to be returned at all - you just need to know if it's successful or not. The standard way of doing that (I just need to know that it's completed successfully or not, I don't need it to return a value) is to use Mono<Void> as the return type and then(), something like:
public Mono<Void> userData(User body) {
return repo.removeUserDetails(userObj).then();
}
...which will give you a standard completion if the deletion was successful, and an error signal otherwise.
A common pattern you find when using reactive java code is handling nulls when collecting a list.
The following code is a simple example showing how to handle nulls returned by a Location by wrapping getLocation in a Mono.defer then handling a null using onErrorReturn.
The test code
List<String> items = inventory.testList().block();
items.forEach(System.out::println);
USA
Not Found
SPAIN
private List<Integer> clusters;
private List<Mono<Location>> locations;
private List<String> countryCodes;
public Mono<List<String>> testList() {
clusters = Arrays.asList(0, 1, 2);
locations = Arrays.asList(Mono.just(new Location(0)), null, Mono.just(new Location(2)));
countryCodes = Arrays.asList("USA", "FRANCE", "SPAIN");
return Flux.fromIterable(clusters)
.flatMap(cluster -> getLocation(cluster))
.collectList();
}
public Mono<String> getLocation(int clusterID) {
return Mono.defer(() -> locations.get(clusterID))
.flatMap(location -> Mono.just(location.id))
.flatMap(id -> Mono.just(countryCodes.get(id)))
.onErrorReturn(Exception.class, "Not Found");
}
I am using JAX-RS via Jersey and I have hit a "bump in the road". I have a method that is supposed to return a JSON object following an HTTP POST. It does execute successfully, but does not return the JSON Object (Unless I do a work around). I am hoping someone can tell me why this does not work as I expect it to. See the following code:
#Path("chatroom")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ChatroomResource {
ChatroomService service = new ChatroomService();
//this works properly and returns the object as json
#GET
public List<Chatroom> getChatrooms() {
return service.getChatrooms();
}
/**********
* This works, but does not return any content in response body
*******/
#POST
public Chatroom addRoom(Chatroom room) {
return service.addChatroom(room);
/*
* This one does produce content body
* service.addChatroom(room);
* return room;
*/
}
}
The following Method is in the service object:
public Chatroom addChatroom(Chatroom room) {
return Cache.getChatrooms().put(room.getRoomName(), room);
}
What might be wrong and how to fix it
Based on the superficial details you've provided, I believe the following instruction is returning null:
return Cache.getChatrooms().put(room.getRoomName(), room);
In the put(String, Chatroom) method, I guess you are adding the Chatroom instance to the cache, but you are returning null instead of the Chatroom instance.
The following should work:
public Chatroom addChatroom(Chatroom room) {
Cache.getChatrooms().put(room.getRoomName(), room);
return room;
}
Update 1
As you mentioned in the comments, you are using a Hashtable to implement your cache.
Be aware the put(K, V) method returns the previous value of the specified key in the hashtable, or null if it did not have one. For more details, consider reading the documentation.
Update 2
Have you ever consider using a HashMap instead of a Hashtable?
If synchronization becomes an issue, you might be interested in a ConcurrentHashMap.