Injecting HTTP Headers in Spring Rest Controller

Watch the video about this article at YouTube or use link https://www.youtube.com/watch?v=sh2rvIZ7vx0

1. Introduction

In this article, I will discuss how to inject the request headers in the Spring Framework. We will use @RequestHeader annotation provided in the Spring.

2. Content

We will create an interface and use its implementation for checking the headers’ injection. The interface name is IHello, and the implementation is HelloImpl. We will look at all the methods one by one. At the end, I will provide the entire codebase for interface and implementation. I will use POSTMAN to run GET requests.

3. Define interface

Request path is /v1/hello.

@RequestMapping(path = "/v1/hello")
public interface IHello {

}

4. Define class

HelloImpl class implements the IHello interface. I annotated HelloImpl class with @RestController.

@RestController
public class HelloImpl implements IHello {

}

5. Read all headers

Let us get right into it. We will read all the headers that came as part of the request. For this we will use org.springframework.web.bind.annotation.RequestHeader annotation.

But we want to do it in a responsible way. Never annotate this annotation in the implementation class. Put it in the interface. Why? Because restful web services are a contract. So the contract of accessing the required headers must be in interface, not in implementation class.

Now, there are 3 different ways you can read all the headers.

  1. Using java.util.Map<String, String>
  2. Using org.springframework.util.MultiValueMap<String, String>
  3. Using org.springframework.http.HttpHeaders

Let us put methods in the IHello interface to access headers in all the 3 ways.

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-map")
public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader Map<String, String> headers);

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-multi-map")
public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader MultiValueMap<String, String> headers);

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-httpheaders")
public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader HttpHeaders headers);

Let us now implement these methods in HelloImpl class.

@Override
public ResponseEntity<String> sayHelloAllHeaders(
			Map<String, String> headers) {
	headers.forEach((k, v) -> System.out.println(k + " : " + v));
	return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id"), HttpStatus.OK);
}

@Override
public ResponseEntity<String> sayHelloAllHeaders(
			MultiValueMap<String, String> headers) {
	headers.forEach((k, v) -> System.out.println(k+" : "+v));
	return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id").get(0), HttpStatus.OK);
}

@Override
public ResponseEntity<String> sayHelloAllHeaders(
			HttpHeaders headers) {
	headers.forEach((k, v) -> System.out.println(k+" : "+v));
	return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id").get(0), HttpStatus.OK);
}

If the headers are present below, get printed on the console for all the above 3 methods.

request-id : c4e6c422-4553-4e4c-ab35-fe93f3cedef5
user-agent : PostmanRuntime/7.25.0
accept : */*
cache-control : no-cache
postman-token : c887c01e-93ff-415f-9308-9522b132a074
host : localhost:8080
accept-encoding : gzip, deflate, br
connection : keep-alive

In POSTMAN, we will get below string as a response

request-id : c4e6c422-4553-4e4c-ab35-fe93f3cedef5

What if the headers are not present? We will get 400 BAD REQUEST error.

6. Read one header at a time using the name

We could read all the headers in the previous section. What if we want to read just one header? We can do so by passing a name field in @RequestHeader annotation. You can also use a value field instead of name. It will work just fine. We can add either of the below methods in the IHello interface. Both of the below methods are identical.

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/one-header")
public ResponseEntity<String> sayHelloSpecificHeader(
			@RequestHeader(name = "request-id") String requestId);

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/one-header")
public ResponseEntity<String> sayHelloSpecificHeader(
			@RequestHeader(value = "request-id") String requestId);

The implementation in class HelloImpl looks like this.

@Override
public ResponseEntity<String> sayHelloSpecificHeader(
			String requestId) {
	return new ResponseEntity<String>(requestId, HttpStatus.OK);
}

If the request id exists, then it will be populated in ResponseEntity and returned as a response. 

Output in POSTMAN when we provide request-id in header in request : 

request-id : d2c32a91-776c-4c79-b572-80d0aef6eb93

What if the header is not present? Then Spring returns with 400 BAD REQUEST error.

{
   "timestamp": 1594171400166,
   "status": 400,
   "error": "Bad Request",
   "message": "Missing request header 'request-id' for method parameter of type String",
   "path": "/v1/hello/one-header"
}

You will see this error in your log.

Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header ‘request-id’ for method parameter of type String]

7. Reading optional header using name and required

In the previous section, we saw that if the header we are expecting is not present, 400 error is thrown. What if you still want to process the request with the header? You can do so by using an attribute called required of @RequestHeader annotation.

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/optional-header")
public ResponseEntity<String> sayHelloOptionalHeaderNotRequired(
		@RequestHeader(name = "request-id", required = false) 
		String requestId);

Implementation in class HelloImpl is done as below : 

@Override
public ResponseEntity<String> sayHelloOptionalHeaderNotRequired(
			String requestId) {

	final ResponseEntity<String> responseEntity;

	if (requestId != null) {
		responseEntity = new ResponseEntity<String>(
					"request-id : " + requestId, HttpStatus.OK);
	} else {
		responseEntity = new ResponseEntity<String>(
					"request-id : " + requestId, HttpStatus.OK);
	}
	return responseEntity;
}

If the request-id is present, then it is displayed as below in POSTMAN.

request-id : 62b834b2-206b-4ce1-824f-7a1d4e09810f

If the request-id is not present, then it is displayed as below in POSTMAN.

request-id : null

8. Providing default header value using defaultValue

If the header is not present, then we want to provide a default value for that header. We can do so using defaultValue parameter in @RequestHeader parameter. 

Below is the method that goes in the IHello interface.

@RequestMapping(
			method = RequestMethod.GET, 
			path = "/optional-header-default-value")
public ResponseEntity<String> sayHelloOptionalHeaderDefaultValue(
			@RequestHeader(
			name = "request-id", 
			defaultValue = "default-value") String requestId);

HelloImpl class’s implementation of this method is as below: 

@Override
public ResponseEntity<String> sayHelloOptionalHeaderDefaultValue(
			String someHeader) {
	return new ResponseEntity<String>(
					"request-id : " + requestId, HttpStatus.OK);
}

If the request-id parameter is passed in header POSTMAN prints this : 

request-id : cd5e454e-302b-43f2-888e-24b9556647a3

If the request-id parameter is not passed in header POSTMAN prints this : 

request-id : default-value

9. Entire Implementation

IHello Interface

import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@RequestMapping(path = "/v1/hello")
public interface IHello {

	/**
	 * Prints all the headers and returns {@link HttpStatus#OK}.
	 * 
	 * Returns {@link HttpStatus#OK} if headers are present else
	 * returns {@link HttpStatus#BAD_REQUEST}.
	 */
	@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-map")
	public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader Map<String, String> headers);

	/**
	 * @see #sayHelloAllHeaders(Map)
	 */
	@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-multi-map")
	public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader MultiValueMap<String, String> headers);

	/**
	 * @see #sayHelloAllHeaders(Map)
	 */
	@RequestMapping(
			method = RequestMethod.GET, 
			path = "/all-headers-httpheaders")
	public ResponseEntity<String> sayHelloAllHeaders(
			@RequestHeader HttpHeaders headers);

	/**
	 * Returns {@link HttpStatus#OK} if Request-ID header is present
	 * else returns {@link HttpStatus#BAD_REQUEST}.
	 */
	@RequestMapping(
			method = RequestMethod.GET, 
			path = "/one-header")
	public ResponseEntity<String> sayHelloSpecificHeader(
			@RequestHeader(name = "request-id") String requestId);

	/**
	 * Returns {@link HttpStatus#OK} along with request-id if 
	 * request-id header is present else returns 
	 * {@link HttpStatus#OK} and null request-id.
	 */
	@RequestMapping(
			method = RequestMethod.GET, 
			path = "/optional-header")
	public ResponseEntity<String> sayHelloOptionalHeaderNotRequired(
			@RequestHeader(
						name = "request-id", 
						required = false) String requestId);

	/**
	 * Returns {@link HttpStatus#OK} with the value of header in 
	 * request. If the value doesn't exists then it will return 
	 * default value.
	 */
	@RequestMapping(
				method = RequestMethod.GET, 
				path = "/optional-header-default-value")
	public 
	ResponseEntity<String> sayHelloOptionalHeaderDefaultValue(
				@RequestHeader(
				name = "some-header", 
				defaultValue = "some-header-default-value") 
				String someHeader);

}

HelloImpl class

import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloImpl implements IHello {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseEntity<String> sayHelloAllHeaders(
				Map<String, String> headers) {
		headers.forEach((k, v) -> System.out.println(k+" : "+v));
		return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id"), HttpStatus.OK);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseEntity<String> sayHelloAllHeaders(
				MultiValueMap<String, String> headers) {
		headers.forEach((k, v) -> System.out.println(k+" : "+v));
		return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id").get(0), HttpStatus.OK);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseEntity<String> sayHelloAllHeaders(
				HttpHeaders headers) {
		headers.forEach((k, v) -> System.out.println(k+" : "+v));
		return new ResponseEntity<String>("request-id : " 
				+ headers.get("request-id").get(0), HttpStatus.OK);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseEntity<String> sayHelloSpecificHeader(
				String requestId) {
		return new ResponseEntity<String>(
				"request-id : " + requestId, HttpStatus.OK);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseEntity<String> sayHelloOptionalHeaderNotRequired(
				String requestId) {

		final ResponseEntity<String> responseEntity;

		if (requestId != null) {
			responseEntity = new ResponseEntity<String>(
				"request-id : " + requestId, HttpStatus.OK);
		} else {
			responseEntity = new ResponseEntity<String>(
				"request-id : " + requestId, HttpStatus.OK);
		}
		return responseEntity;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public 
	ResponseEntity<String> sayHelloOptionalHeaderDefaultValue(
				String someHeader) {
		return new ResponseEntity<String>(
				someHeader, HttpStatus.OK);
	}

}

10. Conclusion

That is all on accessing the HTTP headers in the RestController of Spring Framework.

Code can be found on Github.

Leave a Reply

Your email address will not be published. Required fields are marked *