1. Introduction

Retrofit is a type-safe HTTP client for Java and Kotlin, used for the purpose of implementing REST APIs as interfaces. Often, we need to convert enums to their corresponding string values during serialization and deserialization. This ensures that our enums are handled by our application to communicate with the REST API.

In this tutorial, we’ll explore how to serialize enums using Retrofit.

2. Creating the Enum

First, we’ll define a simple enum to represent the status of an order:

enum class OrderStatus { 
    PENDING,
    COMPLETED,
    CANCELLED
}

Along with OrderStatus, let’s also create the Order data class which depends on our OrderStatus for the status value:

data class Order(val id: String, val status: OrderStatus)

And finally, let’s create an OrderService interface to handle requests:

interface OrderService {
    @GET("orders/{id}")
    suspend fun getOrder(@Path("id") orderId: String): Order

    @POST("orders")
    suspend fun createOrder(@Body order: Order): Order
}

3. Understanding Retrofit’s Converter

Retrofit relies on converters to handle the serialization and the deserialization of data. These converters transform data between raw JSON or XML formats and the Kotlin objects we use in our code.

Retrofit supports multiple converters, with Gson being one of the most popular options. Other alternatives include Moshi and Jackson.

The common interface behind all Retrofit converters is the Converter interface. This interface allows Retrofit to plug in different serialization and deserialization libraries, each implementing its own factory class. This enables us to swap JSON serialization libraries easily.

3.1. Using Gson as the Converter

Gson is a popular library for JSON serialization and deserialization, and it integrates seamlessly with Retrofit. Retrofit provides a GsonConverterFactory to use Gson as the converter:

val gson: Gson = GsonBuilder().create()

val gsonRetrofit: Retrofit = Retrofit.Builder()
  .baseUrl("http://localhost:8080")
  .addConverterFactory(GsonConverterFactory.create(gson))
  .build()

3.2. Using Moshi as the Converter

Additionally, Moshi is another JSON library created by Retrofit that we can leverage. To use Moshi with Retrofit, we need a MoshiConverterFactory:

val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()

val moshiRetrofit: Retrofit = Retrofit.Builder()
  .baseUrl("http://localhost:8080")
  .addConverterFactory(MoshiConverterFactory.create(moshi))
  .build()

3.3. Using Jackson as the Converter

Finally, Jackson is another JSON library that is quite common and offers advanced features. To use Jackson with Retrofit, we need the JacksonConverterFactory:

val objectMapper: ObjectMapper = jacksonObjectMapper()

val jacksonRetrofit: Retrofit = Retrofit.Builder()
  .baseUrl("http://localhost:8080")
  .addConverterFactory(JacksonConverterFactory.create(objectMapper))
  .build()

4. Handling Enum Serialization

Now that we’ve set up our converters, let’s use them to serialize and deserialize our Order class with the OrderStatus enum.

Furthermore, all of these converters have built-in support for enums. By default, they serialize enums using their name() method, which converts them to their string representation. We’ll demonstrate this with tests using WireMock.

Additionally, enum serialization behavior can be customized following the specific patterns of each underlying serialization library.

4.1. WireMock Setup

WireMock is a tool to test HTTP interactions that helps us validate our Retrofit clients. Let’s set it up in a JUnit test class:

class WireMockTest {    
    @Rule
    @JvmField
    val wireMockRule = WireMockRule(8080)

    @BeforeEach
    fun setup() {
        if(wireMockRule.isRunning) return
        wireMockRule.start()
    }

    @AfterEach
    fun tearDown() {
        wireMockRule.stop()
    }
}

Therefore, to set up WireMock we’ll register it as a JUnit rule and ensure we reset it after each test.

4.2. Gson Serialization

First, we’ll use Gson with Retrofit to test enum serialization by setting up a Gson converter and simulating HTTP responses with WireMock:

@Test
fun `test Gson default serialization`() = runBlocking {
    val service = gsonRetrofit.create(OrderService::class.java)
    val order = Order("1", OrderStatus.PENDING)
    wireMockRule.stubFor(
        WireMock.post(WireMock.urlEqualTo("/orders"))
          .willReturn(WireMock.aResponse()
            .withBody("""{"id":"1","status":"PENDING"}""")
            .withStatus(200)
          )
    )

    val response = service.createOrder(order)

    assertEquals(order, response)
}

In this test, we use our gsonRetrofit instance to create our OrderService. We then create an Order object and make a POST request to our mock server. The server responds with a JSON representation of the order which we verify matches the original order object.

4.3. Moshi Serialization

Next, we’ll also validate enum serialization by using our Moshi converter:

@Test
fun `test Moshi default serialization`() = runBlocking {
    val service = moshiRetrofit.create(OrderService::class.java)
    val order = Order("1", OrderStatus.PENDING)
    wireMockRule.stubFor(
        WireMock.post(WireMock.urlEqualTo("/orders"))
          .willReturn(WireMock.aResponse()
            .withBody("""{"id":"1","status":"PENDING"}""")
            .withStatus(200)
          )
    )

    val response = service.createOrder(order)

    assertEquals(order, response)
}

This test follows a similar structure as the previous one but uses Moshi as the converter. We also create an Order object and send a POST request to the mock server. Finally, we verify that the server’s response matches the original order object.

4.4. Jackson Serialization

Lastly, we’ll confirm Retrofit enum serialization with Jackson, using our Jackson converter:

@Test
fun `test Jackson default serialization`() = runBlocking {
    val service = jacksonRetrofit.create(OrderService::class.java)
    val order = Order("1", OrderStatus.PENDING)
    wireMockRule.stubFor(
        WireMock.post(WireMock.urlEqualTo("/orders"))
          .willReturn(WireMock.aResponse()
            .withBody("""{"id":"1","status":"PENDING"}""")
            .withStatus(200)
          )
    )

    val response = service.createOrder(order)

    assertEquals(order, response)
}

Specifically, the setup and test flow remain consistent with the previous examples. We create an Order object, send a POST request, and verify the response matches our expected Order.

Obviously, with all the examples, we see that the name() method of the enum is used for serialization and deserialization.

5. Conclusion

In this article, we’ve explored how to serialize enums using Retrofit in Kotlin. We specifically discussed the use of Gson, Moshi, and Jackson converters and saw their default behavior with tests using WireMock. Finally, this approach simplifies the handling of enum serialization and ensures seamless integration with REST APIs.

As always, the code used in this article is available over on GitHub.