6.19 Writing Response Data

Reactively Writing Response Data

Micronaut’s HTTP server supports writing chunks of response data by returning a Publisher the emits objects that can be encoded to the HTTP response.

The following table summarizes example return type signatures and the behaviour the server exhibits to handle each of them:

Return TypeDescription

Flowable<byte[]>

A Flowable that emits each chunk of content as a byte[] without blocking

Flux<ByteBuf>

A Reactor Flux that emits each chunk as a Netty ByteBuf

Publisher<String>

A Publisher that emits each chunk of content as a String

Flowable<Book>

When emitting a POJO each emitted object is encoded as JSON by default without blocking

When returning reactive type the server will use a Transfer-Encoding of chunked and keep writing data until the Publisher‘s onComplete method is called.

The server will request a single item from the Publisher, write the item and then request the next item, controlling back pressure.

It is up to the implementation of the Publisher to schedule any blocking I/O work that may be done as a result of subscribing to the publisher.

Performing Blocking I/O

In some cases you may wish to integrate with a library that does not support non-blocking I/O.

In this case you can return a Writable object from any controller method. The Writable has various signatures that allowing writing to traditional blocking streams like Writer or OutputStream.

When returning a Writable object the blocking I/O operation will be shifted to the I/O thread pool so that the Netty event loop is not blocked.

See the section on configuring Server Thread Pools for details on how to configure the I/O thread pool to meet the requirements of your application.

The following example demonstrates how to use this API with Groovy’s SimpleTemplateEngine to write a server side template:

Performing Blocking I/O

  1. import groovy.text.SimpleTemplateEngine;
  2. import groovy.text.Template;
  3. import io.micronaut.core.io.Writable;
  4. import io.micronaut.core.util.CollectionUtils;
  5. import io.micronaut.http.MediaType;
  6. import io.micronaut.http.annotation.Controller;
  7. import io.micronaut.http.annotation.Get;
  8. import io.micronaut.http.server.exceptions.HttpServerException;
  9. @Controller("/template")
  10. public class TemplateController {
  11. private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
  12. private final Template template;
  13. public TemplateController() {
  14. template = initTemplate(); (1)
  15. }
  16. @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
  17. Writable render() { (2)
  18. return writer -> template.make( (3)
  19. CollectionUtils.mapOf(
  20. "firstName", "Fred",
  21. "lastName", "Flintstone"
  22. )
  23. ).writeTo(writer);
  24. }
  25. private Template initTemplate() {
  26. Template template;
  27. try {
  28. template = templateEngine.createTemplate(
  29. "Dear $firstName $lastName. Nice to meet you."
  30. );
  31. } catch (Exception e) {
  32. throw new HttpServerException("Cannot create template");
  33. }
  34. return template;
  35. }
  36. }

Performing Blocking I/O

  1. import groovy.text.SimpleTemplateEngine;
  2. import groovy.text.Template;
  3. import io.micronaut.core.io.Writable;
  4. import io.micronaut.core.util.CollectionUtils;
  5. import io.micronaut.http.MediaType;
  6. import io.micronaut.http.annotation.Controller;
  7. import io.micronaut.http.annotation.Get;
  8. import io.micronaut.http.server.exceptions.HttpServerException;
  9. @Controller("/template")
  10. class TemplateController {
  11. private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine()
  12. private final Template template
  13. TemplateController() {
  14. template = initTemplate() (1)
  15. }
  16. @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
  17. Writable render() { (2)
  18. { writer ->
  19. template.make( (3)
  20. CollectionUtils.mapOf(
  21. "firstName", "Fred",
  22. "lastName", "Flintstone"
  23. )
  24. ).writeTo(writer)
  25. }
  26. }
  27. private Template initTemplate() {
  28. Template template
  29. try {
  30. template = templateEngine.createTemplate(
  31. 'Dear $firstName $lastName. Nice to meet you.'
  32. )
  33. } catch (Exception e) {
  34. throw new HttpServerException("Cannot create template")
  35. }
  36. template
  37. }
  38. }

Performing Blocking I/O

  1. import groovy.text.SimpleTemplateEngine
  2. import groovy.text.Template
  3. import io.micronaut.core.io.Writable
  4. import io.micronaut.core.util.CollectionUtils
  5. import io.micronaut.http.MediaType
  6. import io.micronaut.http.annotation.Controller
  7. import io.micronaut.http.annotation.Get
  8. import io.micronaut.http.server.exceptions.HttpServerException
  9. import java.io.Writer
  10. @Controller("/template")
  11. class TemplateController {
  12. private val templateEngine = SimpleTemplateEngine()
  13. private val template: Template
  14. init {
  15. template = initTemplate() (1)
  16. }
  17. @Get(value = "/welcome", produces = [MediaType.TEXT_PLAIN])
  18. internal fun render(): Writable { (2)
  19. return { writer: Writer ->
  20. val writable = template.make( (3)
  21. CollectionUtils.mapOf(
  22. "firstName", "Fred",
  23. "lastName", "Flintstone"
  24. )
  25. )
  26. writable.writeTo(writer)
  27. } as Writable
  28. }
  29. private fun initTemplate(): Template {
  30. val template: Template
  31. try {
  32. template = templateEngine.createTemplate(
  33. "Dear \$firstName \$lastName. Nice to meet you."
  34. )
  35. } catch (e: Exception) {
  36. throw HttpServerException("Cannot create template")
  37. }
  38. return template
  39. }
  40. }
1The controller creates a simple template
2The controller method returns a Writable
3The returned function receives a Writer and calls writeTo on the template.

404 Responses

Often, you want to respond 404 (Not Found) when you don’t find an item in your persistence layer or in similar scenarios.

See the following example:

  1. @Controller("/books")
  2. public class BooksController {
  3. @Get("/stock/{isbn}")
  4. public Map stock(String isbn) {
  5. return null; (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. public Maybe<Map> maybestock(String isbn) {
  9. return Maybe.empty(); (2)
  10. }
  11. }
  1. @Controller("/books")
  2. class BooksController {
  3. @Get("/stock/{isbn}")
  4. Map stock(String isbn) {
  5. null (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. Maybe<Map> maybestock(String isbn) {
  9. Maybe.empty() (2)
  10. }
  11. }
  1. @Controller("/books")
  2. class BooksController {
  3. @Get("/stock/{isbn}")
  4. fun stock(isbn: String): Map<*, *>? {
  5. return null (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. fun maybestock(isbn: String): Maybe<Map<*, *>> {
  9. return Maybe.empty() (2)
  10. }
  11. }
1Returning null triggers a 404 (Not Found) response.
2Returning an empty Maybe triggers a 404 (Not Found) response.
Responding with an empty Publisher or Flowable will result in an empty array being returned if the content type is JSON.