Java class to get the first result from multiple sources

Renan Schmitt
Java Tips and Tricks
4 min readJan 13, 2024

When developing complex systems, a recurring challenge arises when specific information is required to perform a calculation, and this specific information may be distributed across diverse sources and we just need to get the information from the first source that meets some criteria.

For example, we need to get the policy definition to validate a customer action, however this is not that simple, because the policy is organized in levels: Global, Country, Region, and City. And we need to get the policy that is “closer” to the customer, it means, if there is a policy to the customer city, then this is the valid policy, however if there isn’t a policy for the customer city, then we need to check if there is a policy in the customer region, and so on.

To design it, we have an interface called Policy and four implementations of this interface. We also have four service classes to read and return the Policy (Optional, because it may not exist).

Now let’s take a look on how would be the code to get the policy:

public class PolicyFinder{
private GlobalPolicyService globalPolicyService;
private CountryPolicyService countryPolicyService;
private RegionPolicyService regionPolicyService;
private CityPolicyService cityPolicyService;

public Policy findPolicy(Customer customer){
var countryCode = customer.getCountryCode();
var regionCode = customer.getRegionCode();
var cityCode = customer.getCityCode();

var result = cityPolicyService.read(countryCode, regionCode, cityCode);
if(result.isEmpty()){
result = regionPolicyService.read(countryCode, regionCode);
if(result.isEmpty()){
result = countryPolicyService.read(countryCode);
if(result.isEmpty()){
result = globalPolicyService.read();
}
}
}

return result.orElseThrow(PolicyNotFoundException::new);
}
}

This code seems to be very simple, but there are some concerns on it:

  • There is a chain of if statements increasing the cyclomatic complexity
  • It is hard to understand the business logic quickly
  • There are a lot of code to control the flow of the execution
  • If you add more levels to the model, you will have more if statements

We can try to use java stream to do it:

public class PolicyFinder{
private GlobalPolicyService globalPolicyService;
private CountryPolicyService countryPolicyService;
private RegionPolicyService regionPolicyService;
private CityPolicyService cityPolicyService;

public Policy findPolicy(Customer customer){
var countryCode = customer.getCountryCode();
var regionCode = customer.getRegionCode();
var cityCode = customer.getCityCode();

return Stream.of(
() -> cityPolicyService.read(countryCode, regionCode, cityCode),
() -> regionPolicyService.read(countryCode, regionCode),
() -> countryPolicyService.read(countryCode),
() -> globalPolicyService.read())
.map(Supplier::get)
.filter(Optional::isPresent)
.findFirst()
.orElseThrow(PolicyNotFoundException::new);
}
}

This code seems to be better because we removed the chain of if statements, and we make use of some stream tools to get first entry which return a value, however:

  • It is still hard to understand the business logic quickly
  • There are technical code of stream handling

Then, I propose to have a specific class which handles all the controller logic and allow your class to handle just the business logic. Then, the class will be like this:

public class PolicyFinder{
private GlobalPolicyService globalPolicyService;
private CountryPolicyService countryPolicyService;
private RegionPolicyService regionPolicyService;
private CityPolicyService cityPolicyService;

public Policy findPolicy(Customer customer){
var countryCode = customer.getCountryCode();
var regionCode = customer.getRegionCode();
var cityCode = customer.getCityCode();

return GetFirstExecutor
.get(() -> cityPolicyService.read(countryCode, regionCode, cityCode))
.or(() -> regionPolicyService.read(countryCode, regionCode))
.or(() -> countryPolicyService.read(countryCode))
.or(() -> globalPolicyService.read()))
.orElseThrow(PolicyNotFoundException::new);
}
}

With this code we removed the controller code and made this class much more clear and business oriented.

Now let’s take a look at the GetFirstExecutor class.

public class GetFirstExecutor {

public static <R> SupplierExecution<R> get(final Supplier<Optional<R>> supplier) {
return new SupplierExecution<R>(Optional.empty()).or(supplier);
}

@RequiredArgsConstructor
public static class SupplierExecution<R> {
private final Optional<R> value;

public SupplierExecution<R> or(final Supplier<Optional<R>> supplier) {
return value.isEmpty() ? new SupplierExecution<>(supplier.get()) : this;
}

public R orElse(final R defaultValue) {
return value.orElse(defaultValue);
}

public R orElseGet(final Supplier<R> defaultSupplier) {
return value.orElseGet(defaultSupplier);
}

public Optional<R> get() {
return value;
}
}
}

This class is not very complex, in fact, it just has one static method which creates an instance of the SupplierExecution class passing an empty Optional to the constructor, and then calling the method or with the received supplier.

The SupplierExecution class is the main part here, and it works similar to the Java Optional class, where:

  • It is immutable
  • It has an attribute as an Optional result
  • The method or only execute the Supplier passed by parameter if the value of the optional attribute is empty
  • It returns another instance of the SupplierExecution with the result of the supplier
  • The get, orElse, and orElseGet method are just facade methods to the optional attribute

Conclusion

It is always a good idea to separate your classes according to the kind of tasks that they execute, let business to business classes and technical stuff to technical classes. In the example above, the PolicyFinder class has the business logic and the GetFirstExecutor class has the technical logic.

--

--

Renan Schmitt
Java Tips and Tricks

20+ years as a Dev & Architect, mastering Java & ABAP. Passionate about clean code, system architecture, and delivering impactful training.