Spring Boot - throw exception or indicate item is not found - java

I've written two methods, findById searches for an item in the DB and throws an exception if the item is not found :
public Url findById(final Long id){
return urlRepository.findById(id)
.orElseThrow(() -> new ShortUrlNotFoundException("URL not found for the given ID"));
}
The second method, findByShortUrl searches for an item in the DB and uses the JPA method findByShortUrlIs which returns a List of size 1 if the item is found, there should never be more than 1 item in the DB for a given shortUrl :
public Optional<String> findByShortUrl(final String shortUrl){
List<Url> urlList = urlRepository.findByShortUrlIs(shortUrl);
if(urlList.isEmpty()){
return Optional.empty();
}
else {
return Optional.of(urlList.get(0).getLongUrl());
}
}
I like the pattern of using a ShortUrlNotFoundException if an item is not found. Should I use it also in findByShortUrl ? Then, findByShortUrl becomes:
public Optional<String> findByShortUrl(final String shortUrl){
List<Url> urlList = urlRepository.findByShortUrlIs(shortUrl);
if(urlList.isEmpty()){
throw new ShortUrlNotFoundException("URL not found for the given ID")
}
else {
return Optional.of(urlList.get(0).getLongUrl());
}
}

Why not using findFirst as this:
Optional<Url> findFirstByShortUrlIs(String shortUrl);
and then, you call:
public Optional<String> findByShortUrl(final String shortUrl){
return urlRepository.findFirstByShortUrlIs(shortUrl)
.map(Url::getLongUrl)
.map(Optional::of)
.orElseThrow(() -> new ShortUrlNotFoundException("URL not found for the given ID"));
}

Personally speaking, I would never use exception mechanism for both cases. Exceptions are designed for unexpected situations that aren't covered by conventional business logic. For example, findById should throw an exception for the search if and only if you do not expect that item in the database. The same story for the findByShortUrl. As far as I understand it's a normal case not to have a object in db, thus it should be expected case to have empty result. On other hand, you've mentioned that "there should never be more than 1 item in the DB", and that's the perfect case for an exception! If you see that return result set has more than 2 objects, so go and thrown an excpetion.

Related

Conditionally include/exclude elements in builder

I have this bulider method:
public static QuoteDetails quoteDetailsForMA(String response) {
handleErrors(response);
try {
FullResponse quoteDetails = extractResponse(response);
Summary summary = summaryMA(quoteDetails);
List<PenaltyElement> penalties = retrievePenalties(quoteDetails);
return QuoteDetails.builder()
.priceSummary(summary)
.penalties(penalties)
.build();
} catch (Exception e) {
LOGGER.error(
"Exception thrown response: {}",
e.getMessage());
}
}
penalties may or may not be an empty list. If it is not empty I wish to execute the return statement as it currently is(with .penalties(penalties). However, If penalties is an empty list I wish to exclude it from my return. E.g. I wish to return this:
return QuoteDetails.builder()
.priceSummary(summary)
.build();
Is this possible and if so, how is it done?
The easiest technique is to make the .penalties(penalties)
method null and empty list tolerant.
Note, the authors of both of the other answers appear to love NullPointerExceptions.
Here is some example code (with assumptions):
private List<Penalty> penaltiesList;
public QuoteDetailsBuilder penalties(final List<Penalty> newValue)
{
if (CollectionUtils.isNotEmpty(newValue))
{
penaltiesList = newValue;
}
return this;
}
CollectionUtils is an apache utility for some null-safe collection functionality.
You can do it via the isEmpty() method to see if it's empty or not:
.penalties(penalties.isEmpty() ? null : penalties)
Two "obvious" solutions come to my mind:
The builder supports an empty list in the correct way. You could maybe implement your own builder to do that which is just a wrapper around the original and doesn't call the original method penalties if the parameter is an empty list.
Use if as you would regularly do for "conditional" handling:
QuoteDetailsBuilder builder = QuoteDetails.builder()
.priceSummary(summary);
if ((null != penalties) && !penalties.isEmpty()) {
builder = builder.penalties(penalties);
}
return builder.build();
(Of course in solution #2 the name of the builder class may vary depending on the implementation.)

Mono continue if not present

Given two sessions (A & B) as strings, my goal is to check if A is valid in the database. If A exists, return it. If A is not provided (or does not exist in the database), I would like to proceed with checking session B in the database.
The problem with my code below is that I return regardless whether A has been found or not. Instead, I would like to check that if A is not found (is empty), proceed with checking B. Is there a way to check if a Mono does not exist?
String sessionA = "1";
String sessionB = "2";
public Mono<Session> getSession() {
if (!StringUtils.isEmpty(sessionA)) {
return myDatabaseService.findByKeyOne(sessionA); // Should only return if it exists, otherwise, check session B
}
if (!StringUtils.isEmpty(sessionB)) {
return myDatabaseService.findByAnotherSession(sessionB);
}
throw new Exception(); // If both sessions have not been found
}
// My Database Service (dummy code)
public Mono<Session> findByKeyOne(String session) {
try {
return myDatabase.find();
} catch (Exception e) {
return Mono.empty() // If an error occurrd or no result has been found, return empty
}
}
Expanding on #Eugene Botyanovsky's answer with addressing the optional sessionA, you may try something like this:
// Return an exception in case both sessionA and sessionB are empty.
if (StringUtils.isEmpty(sessionA) && StringUtils.isEmpty(sessionB)) {
return Mono.error(new Exception());
}
// simply return an empty Mono if sessionA is empty.
Mono<Session> sessionAMono = StringUtils.isEmpty(sessionA)
? Mono.empty()
: myDatabaseService.findByKeyOne(sessionA);
return sessionAMono
.switchIfEmpty(Mono.defer(() -> myDatabaseService.findByAnotherSession(sessionB)));
In this case, both scenarios when sessionA is empty, as well as when myDatabaseService.findByKeyOne(sessionA) returns an empty result (assuming that it does).
Edit:
Unrelated to your concern but I wanted to point out in your findByKeyOne function, since the myDatabase.find() is returning a Mono, wrapping it with try catch won't work since it's asynchronous. Which means you'll need to handle any potential error in the same stream. You can do something like:
return myDatabase.find()
.onErrorResume(throwable -> Mono.empty();
Try Mono#switchIfEmpty
This is as simple as:
myDatabaseService.findByKeyOne(sessionA)
.switchIfEmpty(myDatabaseService.findByAnotherSession(sessionB))
Now you only have to check your nullabilities

Java 8 Optional <List> returning True Why?

I have a REST Controller
#GetMapping("/getByClientId/{clientId}")
public ResponseEntity<Optional<List<EquityFeeds>>> getByClientId(#PathVariable("clientId") final String clientId) {
Optional<List<EquityFeeds>> cId = Optional.ofNullable(equityFeedsService.findByClientId(clientId));
System.out.println("Client Id: "+cId);
if(cId.isPresent()) {
return ResponseEntity.ok(cId);
} else {
cId.orElseThrow(() -> new ClientIdNotFoundException(clientId));
}
return ResponseEntity.ok(cId);
}
Service Class Code:
public List<EquityFeeds> findByClientId(String clientId) {
List<EquityFeeds> cId = equityFeedsRedisRepositoryImpl.findByClientId(clientId);
System.out.println("In EquityFeedService "+cId);
return cId;
}
Impl. Code (REDIS):
public List<EquityFeeds> findByClientId(String clientId) {
return (List<EquityFeeds>) listOperations.range(clientId, 0, -1);
}
Issue:
1) When the getClientId is called using a REST Controller and the clientId is not present in the REDIS Cache then:
Service class Code returns: In EquityFeedService []
The REST Controller returns: Client Id: Optional[[]]
In the REST Controller the code goes inside the if loop and displays nothing on the screen since the List is empty i.e.
if(cId.isPresent()) {
return ResponseEntity.ok(cId);
}
Why? Why cId.isPresent() returns true and the code goes inside the if loop. Ideally the code should go inside the else loop and throw an Exception since the List is empty. This is happening in case of List only it seems as my other method which has a return type of POJO doesn't have this issue.
Please help me understand this behavior and what should be done to fix this.
cId.isPresent() return true because
List<EquityFeeds> is not null , it's empty list
if(!cId.get().isEmpty()) {
return ResponseEntity.ok(cId);
} else {
throw new ClientIdNotFoundException(clientId);
}
The reason the Optional.isPresent returns true is that there is an actual value - an empty List. The Optional checks whether the value it holds is a null or not, nothing else. The isPresent checks whether the value is present inside the Optional, not inside the List itself.
So you have to treat Optional a bit different. Moreover, don't use Optional like that as substitution to the if-else constructs.
Here is a way to go:
return cId.filter(Predicate.not(List::Empty)) // if the list is not empty
.map(ResponseEntity::ok) // create a response
.orElseThrow(() -> // or else throw an exception
new ClientIdNotFoundException(clientId));
By the way, you don't want to return Optional wrapped inside the ResponseEntity. Unwrap it and return the List itself. If it is empty or null was already handled and the exception would be thrown first.
return cId.filter(Predicate.not(List::Empty))
.map(Optional::get) // exctract from the Optional
.map(ResponseEntity::ok)
.orElseThrow(() -> new ClientIdNotFoundException(clientId));

Java api returning Optional

I'm communicating with an API, and I have utilised the use of the Optional class. But I feel like the error handling could be more elegant, so any suggestions on how to improve this will be well received. Also am I missing a exception handling in the actual api calls?
public Optional<Account> getGreenqloudAccount(String accountUUid) {
System.out.println("tmplog: GreenqloudAccountDao->getGreenqloudAccount");
for (Account account : apiClient.accountList()) {
if (account.getUuid().equals(accountUUid)) {
System.out.println("getGreenqloudAccount, account: " + account.toString());
return Optional.of(account);
}
}
return Optional.empty();
}
public Optional<String> getMarketplaceCustomerIdByUsername(String username) {
if (username == null || username.equals("")) {
return Optional.empty();
}
AwsMarketplace marketplaceData = apiClient.getMarketplaceData(getKeys(username));
if (marketplaceData == null) {
return Optional.empty();
}
return Optional.ofNullable(marketplaceData.getObjects().get(0).getCustomerId());
}
private Pair getKeys(String username) {
GetKeys getKeys = apiClient.getKeys(username);
return new Pair(getKeys.getApiPrivateKey(), getKeys.getApiPublicKey());
}
The main problem with your code: you throw plenty of very different outcomes into the same "bucket".
getMarketplaceCustomerIdByUsername() for example returns an empty Optional when:
the username is null
the username is "" (and think about it "" means empty, but " " isnt empty?!)
no AwsMarketplace instance can be found for the given user
As said, these are very different problems. The first one might indicate: the provided user name is bad, so you should tell your user about that. The last one means: "something is fishy, maybe the user is unknown, or something else happened".
Thus: consider to not reduce different results into an empty Optional. Rather consider to throw (different?) exceptions. You use Optional when "no result" is a valid result of the operation. But "no result, because bad user name" doesn't feel like a valid result.
If you mean handling corner cases, you could improve the code readability along with them as in the first method using findFirst such as:
public Optional<Account> getGreenqloudAccount(String accountUUid) {
System.out.println("tmplog: GreenqloudAccountDao->getGreenqloudAccount");
return apiClient.accountList().stream()
.filter(account -> account.getUuId().equals(accountUUid))
// you can 'peek' and log
.findFirst(); // you return the first account or empty
}
Moving further with the other API, notice Optional.map handles operations returning null values and returns Optional.empty for them implicitly. So you can use:
public Optional<String> getMarketplaceCustomerIdByUsername(String username) {
return Optional.ofNullable(username) // if username is null empty
.filter(name -> !name.isEmpty()) // empty string returns filtered out
.map(name -> apiClient.getMarketplaceData(getKeys(name))) // handles 'null' calue returned
.map(marketplaceData -> marketplaceData.getObjects().get(0).getCustomerId()); // here as well
}
You can use a check using Optional.isPresent()
or
use Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)
Read JDK8 Doc about Optional here
Additionally, you might want to trim your input parameters before checking for empty strings

making fields validation at the same time

I have 3 fields (name, password, email). I want to check if they are valid or not. I wrote the following
public boolean isValidInput() {
if(name.isValid()){
return false;
}
if(password.isInValid()){
return false;
}
if(email.isInValid()){
return false;
}
return true;
}
so this will give me a single invalid. But what to do if I want to show the invalids all at the same time?
There are multiple ways you can handle this. But each of them need a change in the caller to handle these cases.
Create a custom exception which accepts a list of messages. Every time a validation failed add the error to the list, at the end of isValidInput() if the list is not empty then throw an exception with the list of errors.
Return the list of errors from above, instead of throwing exception.
Return a list of boolean variable and each index in the list will represent the status of a validation (name, email, etc)
Have an Enum of all fields that are present. Return a list of enum that failed. Empty list indicates that no error has occurred.
There are still a lot of other ways to handle this. It all depends on what suits you the best.
I would say, try some of them and see how it goes.
You could simply return an integer from the function like this
public int isValidInput() {
if(name.isValid()){
return 1;
}
if(password.isInValid()){
return 2;
}
if(email.isInValid()){
return 3;
}
return 0;
}
and then check the integer to find out which one failed!
It would be better of course to define static final ints with the names of the errors to make the code more readable and robust.

Categories