I ended up solving the problem as below
public class TypeInferenceRequestWrapper extends HttpServletRequestWrapper {
// https://stackoverflow.com/a/63073783/2870572
private final String body;
public TypeInferenceRequestWrapper(HttpServletRequest request) throws IOException {
// So that other request method behave just like before
super(request);
String requestBody = readBody(request);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(requestBody);
String requestURI = request.getRequestURI();
String requestMethod = request.getMethod();
if (jsonNode.getNodeType() == JsonNodeType.OBJECT) {
Map.Entry<String, String> entry = TypeInference.inferType(requestURI, requestMethod);
((ObjectNode) jsonNode).put(entry.getKey(), entry.getValue());
}
body = jsonNode.toString();
}
private String readBody(HttpServletRequest request) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException ex) {
throw new ApiServiceException("Error while reading payload body", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw new ApiServiceException("Error while closing the buffered reader", ex);
}
}
}
return stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Then adding a new filter
@Component
public class TypeInferenceRequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
TypeInferenceRequestWrapper wrappedRequest = new TypeInferenceRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
@Override
public void destroy() {
}
}
And the class where it'll be actual logic which will change with requirements
public class TypeInference {
public static Map.Entry<String, String> inferType(String requestURI, String requestMethod) {
if (requestURI.matches("\\/api\\/v1\\/cars\\/[0-9]+")
&& requestMethod.equals(HttpMethod.PATCH.toString())) {
// Get transmission type from database as this is already saved in db
long carId = getCarId(requestURI);
TransmissionType type = getTransmissionTypeForCar(carId)
return new AbstractMap.SimpleEntry<>("transmissionType", type.toString());
}
return new AbstractMap.SimpleEntry<>("", "");
}
}