A Custom Media Type for a Spring REST API

1. Overview

In this tutorial, we’re going to take a look at defining custom media types and producing them by Spring REST controller.

A good use case for using custom media type is versioning an API.

2. API – Version 1

Let’s start with a simple example – an API exposing a single Resource by id.

We’re going to start with a Version 1 of the Resource we’re exposing to the client. In order to do that, we’re going to use a custom HTTP header – “application/vnd.baeldung.api.v1+json”.

The client will ask for this custom media type via the Accept header.

Here’s our simple endpoint:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/public/api/items/{id}", 
  produces = "application/vnd.baeldung.api.v1+json"
)
@ResponseBody
public BaeldungItem getItem( @PathVariable("id") String id ) {
    return new BaeldungItem("itemId1");
}

Notice the produces parameter here – specifying the custom media type that this API is able to handle.

Now, the BaeldungItem Resource – which has a single field – itemId:

public class BaeldungItem {
    private String itemId;
    
    // standard getters and setters
}

Last but not least let’s write an integration test for endpoint:

@Test
public void givenServiceEndpoint_whenGetRequestFirstAPIVersion_then200() {
    given()
      .accept("application/vnd.baeldung.api.v1+json")
    .when()
      .get(URL_PREFIX + "/public/api/items/1")
    .then()
      .contentType(ContentType.JSON).and().statusCode(200);
}

3. API – Version 2

Now let’s assume that we need to change the details that we’re exposing to the client with our Resource.

We used to expose a raw id – let’s say that now we need to hide that and expose a name instead, to get a bit more flexibility.

It’s important to understand that this change is not backwards compatible; basically – it’s a breaking change.

Here’s our new Resource definition:

public class BaeldungItemV2 {
    private String itemName;

    // standard getters and setters
}

And so, what we’ll need to do here is – migrate our API to a second version.

We’re going to do that by creating the next version of our custom media type and defining a new endpoint:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/public/api/items/{id}", 
  produces = "application/vnd.baeldung.api.v2+json"
)
@ResponseBody
public BaeldungItemV2 getItemSecondAPIVersion(@PathVariable("id") String id) {
    return new BaeldungItemV2("itemName");
}

And so we now have the exact same endpoint, but capable of handling the new V2 operation.

When the client will ask for “application/vnd.baeldung.api.v1+json” – Spring will delegate to the old operation and the client will receive a BaeldungItem with a itemId field (V1).

But when the client now sets the Accept header to “application/vnd.baeldung.api.v2+json” – they’ll correctly hit the new operation and get back the Resource with the itemName field (V2):

@Test
public void givenServiceEndpoint_whenGetRequestSecondAPIVersion_then200() {
    given()
      .accept("application/vnd.baeldung.api.v2+json")
    .when()
      .get(URL_PREFIX + "/public/api/items/2")
    .then()
      .contentType(ContentType.JSON).and().statusCode(200);
}

Note how the test is similar but is using the different Accept header.

4. Custom Media Type on Class Level

Finally, let’s talk about a class-wide definition of the media type – that’s possible as well:

@RestController
@RequestMapping(
  value = "/", 
  produces = "application/vnd.baeldung.api.v1+json"
)
public class CustomMediaTypeController

As expected, the @RequestMapping annotation easily works on class level and allows us to specify the valueproduces and consumes parameters.

5. Conclusion

This articles illustrated examples when defining Custom Media Types could be useful in versioning public API.

The implementation of all these examples and code snippets can be found in the GitHub project.

Related posts:

Running Spring Boot Applications With Minikube
Java Program to Implement Affine Cipher
A Guide to the finalize Method in Java
A Guide to Apache Commons Collections CollectionUtils
Spring Security Registration – Resend Verification Email
Converting Between a List and a Set in Java
Làm thế nào tạo instance của một class mà không gọi từ khóa new?
Java Program to Check Whether an Input Binary Tree is the Sub Tree of the Binary Tree
Java Program to Implement Fibonacci Heap
Java Program to Solve the Fractional Knapsack Problem
Java Program to Implement Max Heap
Guide to Spring 5 WebFlux
Java Program to Create a Balanced Binary Tree of the Incoming Data
Case-Insensitive String Matching in Java
Loại bỏ các phần tử trùng trong một ArrayList như thế nào?
Spring MVC Custom Validation
Tránh lỗi ConcurrentModificationException trong Java như thế nào?
Get and Post Lists of Objects with RestTemplate
Java InputStream to String
Hướng dẫn sử dụng luồng vào ra nhị phân trong Java
Getting Started with Custom Deserialization in Jackson
Handle EML file with JavaMail
@Order in Spring
Documenting a Node.js REST API using Swagger
Spring Boot With H2 Database
Quick Guide to Spring MVC with Velocity
Java Program to Generate All Possible Combinations of a Given List of Numbers
Removing all Nulls from a List in Java
Java Program to Use Above Below Primitive to Test Whether Two Lines Intersect
Upload and Display Excel Files with Spring MVC
Guava – Join and Split Collections
Java Program to Implement the MD5 Algorithm