Custom Media Types in Spring REST API

Guide to define Spring REST API with Custom Media Types and how to use the Custom Media Types for versioning Spring REST API.

Overview

This tutorial takes us though Custom media types in Spring REST API. We will define Custom Media types and use them to create different versions of an API in a Spring REST Controller.

Before we do that, we will have a quick overview of Media Types, their benefits, and How to define Custom Media Types.

What are Media Types?

The Client and Server mechanism in a REST API uses Media Types to define the structure or the format of the resources. In other words, it defines format of the models and helps both the server and the client to understand and parse the payloads that are exchanged. For example, when a REST API endpoint returns a .txt file resource, the client needs to know that it is a text file so that it can act accordingly.

On a high level, we can say the media types helps defining a contract between the client and the server. However, in REST APIs there are other elements like the REST URL and the protocol that defines the contracts more prominently.

Media Types in Spring REST APIs

In Spring REST APIs, Spring uses ‘application/json‘ as a default media type. That is why, a REST controller can consume or produce JSON format payloads without having to specify the media types explicitly. Thus, in order to consume or produce data in a different form, the controller needs to specify that explicitly.

@PostMapping(
        value = "/students",
        produces = MediaType.APPLICATION_XML_VALUE,
        consumes = MediaType.APPLICATION_XML_VALUE
)
public ResponseEntity<InputStreamResource> postStudent(Code language: Java (java)

In the snippet above, the POST students endpoint is capable of parsing resources into XML format. Without specifying the produces and consumes attributes, the endpoint will always parse Student model in the form of JSON.

Benefits of Having Media Types

As stated earlier Media Types are one of the elements that defines a contract between Server and Client. While both the server and client follow the contract, they can have a smooth communication. In other words, having fixed the interchanged data formats, both the server and the client can process the data accurately.

The Media Types also helps us versions REST APIs without changing their URI. For example,

a POST /students endpoint, that consumes XML input and produces output in XML format.

@PostMapping(
        value = "/students",
        produces = MediaType.APPLICATION_XML_VALUE,
        consumes = MediaType.APPLICATION_XML_VALUE
)Code language: Java (java)

Or, a POST /students endpoint which supports JSON Media Type.

@PostMapping(
        value = "/students",
        produces = MediaType.APPLICATION_JSON_VALUE,
        consumes = MediaType.APPLICATION_JSON_VALUE
)Code language: Java (java)

If a REST API exposes both of these endpoints, their clients can use the media type to chose the version one they want to deal with.

Custom Media Type

Most of the REST APIs use the default ‘application/json‘ media type. This is because, the default media type is flexible and serves in most of the cases. However, having a Custom media type tightens the server and client contract by making them more tightly coupled.

We can create custom media types, that are based on any other basic media types. If you want to learn more about custom media types and their conventions consider reading this.

Next is an example of custom media type that we are going to use. The name of the media type tells the API vendor name, API name, API version and the base media type.

application/vnd.amitph.students.v1+json

Custom Media Types for Versioning

In this section, we will create a Spring REST API and use Custom Media Type for creating versioned APIs.

API Version 1

Consider, in the version 1 of a Student Service we have the Student model looking like this.

@Data
public class StudentV1 {
    @JsonIgnore
    private Long studentId;
    private String firstName;
    private String lastName;
    private Integer year;
}Code language: Java (java)

Note that, the studentId field is hidden and the API will not expose it to the outside world.

Next, we have a GET endpoint for the students, which looks like this.

@GetMapping(
        value = "/students", 
        produces = "application/vnd.amitph.students.v1+json"
)
public List<StudentV1> getStudentsV1() {
    return serviceV1.getAllStudents();
}Code language: Java (java)

Note that the endpoint uses a custom media type, that denotes the version number.

Next, consider you want to start exposing the studentId field so that clients can use it for accessing a particular student. For example GET /students/{studentId}/.

However, if you make this change in the above endpoint, the existing clients will break unless they change themselves. Also, as a best practice, API changes should always be backward compatible. Thus, instead of modifying the endpoint, we will create a new endpoint and name it Version 2.

API Version 2

Next is an example of our Model class version 2. Note, it doesn’t use @JsonIgnore the studentId field.

@Data
public class StudentV2 {

    private Long studentId;
    private String firstName;
    private String lastName;
    private Integer year;
}Code language: Java (java)

Next, we will create a new endpoint in the REST Controller. Note that, both of the endpoints have exactly same resource identifier (URI). However, the version 2 endpoint uses a different media type.

@GetMapping(
        value = "/students",
        produces = "application/vnd.amitph.students.v2+json")
public List<StudentV2> getStudentsV2() {
    return serviceV2.getAllStudents();
}Code language: Java (java)

Consume APIs

In order to consume these endpoints, client has to specify the media type it can consume.

For example, an old client who doesn’t want to see studentId in the response can specify the exact media type in the accept header.

curl --location --request GET 'localhost:8080/students/' \
     --header 'Accept: application/vnd.amitph.students.v1+json'

[{"firstName":"Jon","lastName":"Snow","year":2023}]Code language: Bash (bash)

The snippet shows an example of using curl to execute the endpoint, and the output, on the next line.

Or, a client who can consume the version 2 response, can specify the new media type in the accept header.

curl --location --request GET 'localhost:8080/students/' \
     --header 'Accept: application/vnd.amitph.students.v2+json'

[{"studentId":13,"firstName":"Jon","lastName":"Snow","year":2023}]Code language: Java (java)

The curl request and the respective response shows that the client correctly received response from the version 2 endpoint.

Summary

This tutorial discussed How to use Custom Media Types in Spring REST API. We started by understanding the concepts of media types, custom media types and how can they be used to create versioned APIs. Having custom media types based versioning, different versions of endpoints can have same URI. Lastly, we created Custom Media Type based Versioned APIs and consumed them.

For more on Spring and Spring Boot, please visit Spring Tutorials.