LogoLogo
GitHubFree book
  • 🏠Introduction
  • Using the Library
    • ðŸŒąGetting Started
      • Adding Result to Your Build
      • Creating Results
    • ðŸŠīBasic Usage
      • Checking Success or Failure
      • Unwrapping Values
      • Conditional Actions
    • 🚀Advanced Usage
      • Screening Results
      • Transforming Results
    • 🏁Recap
  • Add-ons
    • ðŸ’ĪLazy Results
    • ðŸ—ĢïļFluent Assertions
    • 📜Jackson Module
    • 🧑‍🚀Micronaut Serialization
  • Other resources
    • ðŸ“ĶBill of Materials
    • 📈Benchmarks
    • ðŸĪ–Demo Projects
      • Spring Boot Demo Project
      • Micronaut Demo Project
    • ⚖ïļLicense
Powered by GitBook
LogoLogo

Source Code

  • GitHub
  • License

Quality

  • SonarCloud
  • Benchmarks

Documentation

  • Free book
  • Javadoc

Releases

  • Maven Central
  • Bill of Materials

Copyright 2024 Guillermo Calvo

On this page
  • How to Use this Add-On
  • Test Scenario
  • Problem Overview
  • Serialization Problem
  • Deserialization Problem
  • Solution Implementation
  • Adding the Serde Imports to the Classpath
  • Serializing Results
  • Deserializing Results
  • Conclusion

Was this helpful?

Edit on GitHub
Export as PDF
  1. Add-ons

Micronaut Serialization

How to serialize Result objects with Micronaut

PreviousJackson ModuleNextBill of Materials

Last updated 6 months ago

Was this helpful?

When using Result objects with , we might run into some problems. The support for Result solves them by making Micronaut treat results as (so they can be serialized and deserialized).

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 , it has gained popularity for its performance and innovative features.

How to Use this Add-On

Add this Maven dependency to your build:

Group ID
Artifact ID
Latest Version

com.leakyabstractions

result-micronaut-serde

provides snippets for different build tools to declare this dependency.

Test Scenario

Let's start by creating a record ApiOperation containing one ordinary and one Result field.

/** Represents an API operation */
@Serdeable
public record ApiOperation(String name, Result<String, String> result) {
}

Problem Overview

We will take a look at what happens when we try to serialize and deserialize ApiOperation objects with Micronaut.

Serialization Problem

Now, let's create a Micronaut controller that returns an instance of ApiOperation containing a successful result.

@Controller("/operations")
public class ApiController {

    @Get("/last")
    ApiOperation lastOperation() {
        return new ApiOperation("setup", Results.success("Perfect"));
    }
}

And finally, let's run the application and try the /operations/last endpoint we just created.

curl 'http://localhost:8080/operations/last'
No serializable introspection present for type Success.
 Consider adding Serdeable. Serializable annotate to type Success.
 Alternatively if you are not in control of the project's source code,
 you can use @SerdeImport(Success.class) to enable serialization of this type.
@Test
void testSerializationProblem(ObjectMapper objectMapper) {
  // Given
  ApiOperation op = new ApiOperation("setup", success("Perfect"));
  // Then
  SerdeException error = assertThrows(SerdeException.class,
      () -> objectMapper.writeValueAsString(op));
  assertTrue(error.getMessage().startsWith(
      "No serializable introspection present for type Success."));
}

This is Micronaut's default serialization behavior. But we'd like to serialize the result field like this:

{
  "name": "setup",
  "result": {
    "failure": null,
    "success": "Perfect"
  }
}

Deserialization Problem

Now, let's reverse our previous example, this time trying to receive an ApiOperation as the body of a POST request.

@Controller("/operations")
public class ApiController {

    @Post("/notify")
    Map<String, String> notify(@Body ApiOperation op) {
        return op.result()
                .mapSuccess(s -> Map.of("message", op.name() + " succeeded: " + s))
                .orElseMap(f -> Map.of("error", op.name() + " failed: " + f));
    }
}
No bean introspection available for type
 [interface com.leakyabstractions.result.api.Result].
 Ensure the class is annotated with
 io.micronaut.core.annotation.Introspected
@Test
void testDeserializationProblem(ObjectMapper objectMapper) {
  // Given
  String json = """
      {"name":"renew","result":{"success":"OK"}}""";
  // Then
  IntrospectionException error = assertThrows(IntrospectionException.class,
      () -> objectMapper.readValue(json, ApiOperation.class));
  String errorMessage = error.getMessage(); // Extract error message
  // Verify error message
  assertTrue(errorMessage.startsWith("No bean introspection available " +
      "for type [interface com.leakyabstractions.result.api.Result]."));
} // End

Solution Implementation

What we want, is for Micronaut to treat Result values as JSON objects that contain either a success or a failure value. Fortunately, there's an easy way to solve this problem.

Adding the Serde Imports to the Classpath

Serializing Results

Now, let's try and serialize our ApiOperation object again.

@Test
void serializeSuccessfulResult(ObjectMapper objectMapper)
    throws IOException {
  // Given
  ApiOperation op = new ApiOperation("clean", success("All good"));
  // When
  String json = objectMapper.writeValueAsString(op);
  // Then
  assertEquals("""
      {"name":"clean","result":{"success":"All good"}}""", json);
}

If we look at the serialized response, we'll see that this time the result field contains a success field.

{
  "name": "clean",
  "result": {
    "failure": null,
    "success": "All good"
  }
}

Next, we can try serializing a failed result.

@Test
void serializeFailedResult(ObjectMapper objectMapper)
    throws IOException {
  // Given
  ApiOperation op = new ApiOperation("build", failure("Oops"));
  // When
  String json = objectMapper.writeValueAsString(op);
  // Then
  assertEquals("""
      {"name":"build","result":{"failure":"Oops"}}""", json);
}

We can verify that the serialized response contains a non-null failure value and a null success value:

{
  "name": "build",
  "result": {
    "failure": "Oops",
    "success": null
  }
}

Deserializing Results

@Test
void deserializeSuccessfulResult(ObjectMapper objectMapper)
    throws IOException {
  // Given
  String json = """
      {"name":"check","result":{"success":"Yay"}}""";
  // When
  ApiOperation response = objectMapper.readValue(json, ApiOperation.class);
  // Then
  assertEquals("check", response.name());
  assertEquals("Yay", response.result().orElse(null));
}

Finally, let's repeat the test again, this time with a failed result. We'll see that yet again we don't get an exception, and in fact, have a failed result.

@Test
void deserializeFailedResult(ObjectMapper objectMapper)
    throws IOException {
  // Given
  String json = """
      {"name":"start","result":{"failure":"Nay"}}""";
  // When
  ApiOperation response = objectMapper.readValue(json, ApiOperation.class);
  // Then
  assertEquals("start", response.name());
  assertEquals("Nay", response.result().getFailure().orElse(null));
}

Conclusion

We'll see that we get a Micronaut CodecException caused by a .

Although this may look strange, it's actually what we should expect. Even though we annotated ApiOperation as , Micronaut doesn't know how to serialize result objects yet, so the data structure cannot be serialized.

We'll see that now we get an . Let's inspect the stack trace.

This behavior again makes sense. Essentially, Micronaut cannot create new result objects, because Result is not annotated as or .

All we need to do now is . Once the is in the classpath, all functionality is available for all normal Micronaut operations.

Now, let's repeat our tests for deserialization. If we read our ApiOperation again, we'll see that we no longer get an .

We learned how to serialize and deserialize Result objects using , demonstrating how the provided enables Micronaut to treat Results as objects.

The full source code for the examples is .

🧑‍🚀
Micronaut
Micronaut serialization
Serdeable
Micronaut
Spring Boot
Maven Central
SerdeException
@Serdeable
IntrospectionException
@Introspected
@Serdeable
IntrospectionException
Micronaut
@SerdeImport
Serdeable
available on GitHub
@SerdeImport
add Result-Micronaut-Serde as a Maven dependency