Other Resources

Supplementary materials and guidance

Finally, we’ll explore an assortment of resources, designed to provide further context and support for working with the Result library.

Topics include hassle-free dependency management, performance benchmarks, demo projects that demonstrate integration with popular frameworks, and details on the library’s licensing terms.

Bill of Materials

How to declare dependencies without having to worry about version numbers

Tracking multiple add-on versions for your project can quickly become cumbersome. In that situation, you can use the convenient Result Library Bill of Materials to centralize and align their versions. This ensures compatibility and simplifies dependency maintenance.

An icon indicating this blurb contains information

Maven’s Bill of Materials POMs are special POM files that group dependency versions known to be valid and tested to work together, reducing the chances of having version mismatches.

The basic idea is that instead of specifying a version number for each Result library in your project, you can use this BOM to get a complete set of consistent versions.

How to Use this Add-On

Add this Maven dependency to your build:

Group ID Artifact ID Version
com.leakyabstractions result-bom 1.0.0.0

You can find the latest version of the BOM in Maven Central.

Maven

To import the BOM using Maven, use the following:

Figure 74. Importing the BOM using Maven
<!-- Import the BOM -->
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.leakyabstractions</groupId>
      <artifactId>result-bom</artifactId>
      <version>1.0.0.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- Define dependencies without version numbers -->
<dependencies>
  <dependency>
    <groupId>com.leakyabstractions</groupId>
    <artifactId>result</artifactId>
  </dependency>
  <dependency>
    <groupId>com.leakyabstractions</groupId>
    <artifactId>result-assertj</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

Gradle

To import the BOM using Gradle, use the following:

Figure 75. Importing the BOM using Gradle
dependencies {
  // Import the BOM
  implementation platform("com.leakyabstractions:result-bom:1.0.0.0")

  // Define dependencies without version numbers
  implementation("com.leakyabstractions:result")
  testImplementation("com.leakyabstractions:result-assertj")
}

Conclusion

We discussed the benefits of using the Bill of Materials for managing dependencies in your project. With the BOM, you can eliminate the hassle of manually specifying version numbers, ensuring consistency and compatibility across all Result libraries.

Benchmarks

Measuring performance to find out how fast Results are

Throughout these guides, we have mentioned that throwing Java exceptions is slow. But… how slow? According to our benchmarks, throwing an exception is several orders of magnitude slower than returning a failed result. This proves that using exceptional logic just to control normal program flow is a bad idea.

We should throw exceptions sparingly, even more so when developing performance-critical applications.

Benchmarking Result Library

This library comes with a set of benchmarks that compare performance when using results versus when using exceptions.

Simple Scenarios

The first scenarios compare the most basic usage: a method that returns a String or fails, depending on a given int parameter:

Using Exceptions
Figure 76. Using exceptions
1 public String usingExceptions(int number) throws SimpleException {
2   if (number < 0) throw new SimpleException(number);
3   return "ok";
4 }
Using Results
Figure 77. Using Results
1 public Result<String, SimpleFailure> usingResults(int number) {
2   if (number < 0) return Results.failure(new SimpleFailure(number));
3   return Results.success("ok");
4 }

Complex Scenarios

The next scenarios do something a little bit more elaborate: a method invokes the previous method to retrieve a String; if successful, then converts it to upper case; otherwise transforms the “simple” error into a “complex” error.

Using Exceptions
Figure 78. Using exceptions
1 public String usingExceptions(int number) throws ComplexException {
2   try {
3     return simple.usingExceptions(number).toUpperCase();
4   } catch (SimpleException e) {
5     throw new ComplexException(e);
6   }
7 }
Using Results
Figure 79. Using Results
1 public Result<String, ComplexFailure> usingResults(int number) {
2   return simple.usingResults(number).map(String::toUpperCase, ComplexFailure::new);
3 }

Conclusion

We provided insights into the Result library’s performance through benchmarking. While our metrics corroborate that most codebases could benefit from using this library instead of throwing exceptions, its main goal is to help promote best practices and implement proper error handling.

An icon indicating this blurb contains information

To address performance concerns, benchmark your applications to gain reusable insights. These should guide your decisions on selecting frameworks and libraries.

Demo Projects

Check out some REST APIs that consume and produce Result objects

To help you become familiar with this library, you can explore two demo projects that showcase how to handle and serialize Result objects within popular frameworks like Spring Boot and Micronaut. Each project provides a working example of a “pet store” web service that exposes a REST API for managing pets. They are based on Swagger Petstore Sample and you can interact with them using Swagger-UI.

An icon indicating this blurb contains information
  • Spring Boot is a widely-used, JVM-based framework designed to simplify the development of stand-alone, production-ready Spring applications. It emphasizes convention over configuration, allowing developers to get started quickly with minimal setup. Known for its extensive ecosystem and robust community support, Spring Boot streamlines the creation of microservices and enterprise applications, leveraging the powerful Spring Framework while minimizing boilerplate code.
  • Micronaut is a modern, JVM-based framework for building lightweight microservices and serverless applications. It focuses on fast startup times and low memory usage. Although not as widely adopted as Spring Boot, it has gained popularity for its performance and innovative features.

These projects illustrate how to develop powerful APIs using Result objects. Follow the examples to create resilient web services that elegantly handle success and failure scenarios.

Spring Boot Demo Project

Take a look at a Spring Boot-based REST API leveraging Result objects

This demo project demonstrates how to handle and serialize Result objects within a Spring Boot application. It provides a working example of a “pet store” web service that exposes a REST API for managing pets.

Generating the Project

The project was generated via Spring Initializr including features: web and cloud-feign.

Adding Serialization Support

Then Jackson datatype module for Result objects was manually added as a dependency to serialize and deserialize Result objects.

Figure 80. build.gradle
dependencies {
  // ...
  implementation platform('com.leakyabstractions:result-bom:1.0.0.0')
  implementation 'com.leakyabstractions:result'
  implementation 'com.leakyabstractions:result-jackson'
}

We use a @Bean to register the datatype module.

Figure 81. JacksonConfig.java
1 @Configuration
2 public class JacksonConfig {
3   @Bean
4   public Module registerResultModule() {
5     return new ResultModule();
6   }
7 }

API Responses

API responses contain a Result field, encapsulating the outcome of the requested operation.

Figure 82. ApiResponse.java
1 public class ApiResponse<S> {
2 
3   @JsonProperty String version;
4   @JsonProperty Instant generatedOn;
5   @JsonProperty Result<S, ApiError> result;
6 }

Results have different success types, depending on the specific endpoint. Failures will be encapsulated as instances of ApiError.

Controllers

Controllers return instances of ApiResponse that will be serialized to JSON by Spring Boot.

 1 @RestController
 2 public class PetController {
 3   // ...
 4   @GetMapping("/pet")
 5   ApiResponse<Collection<Pet>> list(@RequestHeader("X-Type") RepositoryType type) {
 6     log.info("List all pets in {} pet store", type);
 7     return response(locate(type)
 8       .flatMapSuccess(PetRepository::listPets)
 9       .ifSuccess(x -> log.info("Listed {} pet(s) in {}", x.size(), type))
10       .ifFailure(this::logError));
11   }
12 }

Since failures are expressed as ApiError objects, endpoints invariably return HTTP status 200.

Running the Application

The application can be built and run with Gradle.

Figure 83. Running the application
./gradlew bootRun

This will start a stand-alone server on port 8080.

Testing the Server

Once started, you can interact with the API.

Figure 84. Testing the server
curl -s -H 'x-type: local' http://localhost:8080/pet/0

You should see a JSON response like this:

Figure 85. JSON response
{
  "version": "1.0",
  "result": {
    "success":{
      "id": 0,
      "name": "Rocky",
      "status": "AVAILABLE"
    }
  }
}

Using Swagger-UI

You can navigate to http://localhost:8080/ to inspect the API using an interactive UI

Micronaut Demo Project

Take a look at a Micronaut-based REST API leveraging Result objects

This demo project demonstrates how to handle and serialize Result objects within a Micronaut application. It provides a working example of a “pet store” web service that exposes a REST API for managing pets.

Generating the Project

The project was generated via Micronaut Launch including features: annotation-api, http-client, openapi, serialization-jackson, swagger-ui, toml, and validation.

Adding Serialization Support

Then Micronaut Serialization for Result objects was manually added as a dependency to serialize and deserialize Result objects.

Figure 86. build.gradle
1 dependencies {
2     // ...
3     implementation(platform("com.leakyabstractions:result-bom:1.0.0.0"))
4     implementation("com.leakyabstractions:result")
5     implementation("com.leakyabstractions:result-micronaut-serde")
6 }

That’s all we need to do to make Micronaut treat results as Serdeable.

API Responses

API responses contain a Result field, encapsulating the outcome of the requested operation.

Figure 87. ApiResponse.java
1 @Serdeable
2 public class ApiResponse<S> {
3 
4   @JsonProperty String version;
5   @JsonProperty Instant generatedOn;
6   @JsonProperty Result<S, ApiError> result;
7 }

Results have different success types, depending on the specific endpoint. Failures will be encapsulated as instances of ApiError.

Controllers

Controllers return instances of ApiResponse that will be serialized to JSON by Micronaut:

Figure 88. PetController.java
 1 @Controller
 2 public class PetController {
 3   // ...
 4   @Get("/pet")
 5   ApiResponse<Collection<Pet>> list(@Header("X-Type") RepositoryType type) {
 6     log.info("List all pets in {} pet store", type);
 7     return response(locate(type)
 8         .flatMapSuccess(PetRepository::listPets)
 9         .ifSuccess(x -> log.info("Listed {} pet(s) in {}", x.size(), type))
10         .ifFailure(this::logError));
11   }
12 }

Since failures are expressed as ApiError objects, endpoints invariably return HTTP status 200.

Running the Application

The application can be built and run with Gradle.

Figure 89. Running the application
./gradlew run

This will start a stand-alone server on port 8080.

Testing the Server

Once started, you can interact with the API.

Figure 90. Testing the server
curl -s -H 'x-type: local' http://localhost:8080/pet/0

You should see a JSON response like this:

Figure 91. JSON response
{
  "version": "1.0",
  "result": {
    "success":{
      "id": 0,
      "name": "Rocky",
      "status": "AVAILABLE"
    }
  }
}

Using Swagger-UI

You can navigate to http://localhost:8080/ to inspect the API using an interactive UI.

License

Feel free to tweak and share — no strings attached

This library is licensed under the Apache License, Version 2.0 (the “License”); you may not use it except in compliance with the License.

You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and limitations under the License.

Permitted
  • Commercial Use: You may use this library and derivatives for commercial purposes.
  • Modification: You may modify this library.
  • Distribution: You may distribute this library.
  • Patent Use: This license provides an express grant of patent rights from contributors.
  • Private Use: You may use and modify this library without distributing it.
Required
  • License and Copyright Notice: If you distribute this library you must include a copy of the license and copyright notice.
  • State Changes: If you modify and distribute this library you must document changes made to this library.
Forbidden
  • Trademark use: This license does not grant any trademark rights.
  • Liability: The library author cannot be held liable for damages.
  • Warranty: This library is provided without any warranty.