AWS Developer Tools Blog

Consumer Builders in the AWS SDK for Java v2

The AWS SDK for Java v2 introduced immutable models which in turn necessitated using a builder to create request/response objects. Builders are a common pattern for working with immutable objects, they allow building up the state of an object over time and then calling build to create an immutable representation of the object. They also have the benefit of enabling an API to evolve (e.g. new properties added) without breaking existing customers as would be the case with an all-arguments constructor. However there is a downside – a loss of brevity; using builders can become verbose, especially when trying to create nested structures. Let’s take a look at an example using the AWS SDK for Java v2’s builder pattern to create a Simple Email Service request:

Classic Builder Pattern

sesClient.sendEmail(
    SendEmailRequest.builder()
                    .destination(Destination.builder()
                                            .toAddresses("to-email@domain.com")
                                            .bccAddresses("bcc-email@domain.com")
                                            .build())
                    .replyToAddresses("reply-to@domain.com")
                    .message(Message.builder()
                                    .subject(Content.builder()
                                                    .charset("UTF-8")
                                                    .data("Subject Line")
                                                    .build())
                                    .body(Body.builder()
                                              .text(Content.builder()
                                                           .data("The body of the email")
                                                           .charset("UTF-8")
                                                           .build())
                                              .build())
                                    .build())
                    .build());

Here each sub-structure in the hierarchy needs to have its own builder created (unless it’s a simple scalar type like a String), then at the end a chain of repeated build methods need to be called in order to actually create desired object.

The above snippet of code uses the standard sendEmail method on the SesClient which has the following signature:

SendEmailResponse sendEmail(SendEmailRequest sendEmailRequest)

Consumer Builder Pattern

The AWS SDK for Java v2 includes overloaded helper methods, nicknamed ‘consumer builder’ methods, that accept a java.util.function.Consumer of the corresponding complex-type Builder class for the method in question. The above sendEmail method has an overload that looks like:

SendEmailResponse sendEmail(Consumer<SendEmailRequest.Builder> sendEmailRequest)

Using the power of Java 8’s short-hand lambda syntax we can re-write the above example as follows:

sesClient.sendEmail(
    email -> email.destination(d -> d.toAddresses("to-email@domain.com")
                                     .bccAddresses("bcc-email@domain.com"))
                  .replyToAddresses("reply-to@domain.com")
                  .message(m -> m.subject(s -> s.charset("UTF-8")
                                                .data("Subject Line"))
                                 .body(b -> b.text(t -> t.data("The body of the email")
                                                         .charset("UTF-8")))));

Here there’s no need to find the Builder class for each type that needs to be built, and no chain of ugly build methods. The SDK takes care of instantiating the builder object, and then calling build on it at the end. This pattern also plays nicely with JVM languages that have implicit parameter patterns like Kotlin and Groovy:

sesClient.sendEmail {
    it.destination {
        it.toAddresses("to-email@domain.com")
          .bccAddresses("bcc-email@domain.com")
    }.replyToAddresses("reply-to@domain.com")
        .message {
            it.subject {
                it.charset("UTF-8")
                  .data("Subject Line")
            }.body {
                it.text {
                    it.data("The body of the email")
                      .charset("UTF-8")
                }
            }
        }
}

Conclusion

As always check out the code on GitHub, chat to the SDK team on Gitter or provide any feedback/comments/issues via the GitHub issues page.