This question already has answers here:
Calling spring data rest repository method doesn't return links
(2 answers)
Closed 6 years ago.
I'm using Spring Data Rest to create a RESTful api. I want to handle an exception returning an entity representation like the ones generated by the Spring Data Rest repositories (with HATEOAS links). The method from where I need to return the entity representation is the following:
#ExceptionHandler(value = {ExistentUGVException.class})
#ResponseBody
protected ResponseEntity<UGV> existentUGVHandler(HttpServletRequest request, HttpServletResponse response, ExistentUGVException ex) {
return new ResponseEntity<UGV>(ex.ugv, HttpStatus.OK);
}
This implementation returns the UGV representation without links:
{
"title" : "Golden Eagle Snatches Kid",
"publishDate" : "2012-12-19T13:55:28Z",
"url" : "https://www.youtube.com/watch?v=Xb0P5t5NQWM"
}
But it would be:
{
"title" : "Golden Eagle Snatches Kid",
"publishDate" : "2012-12-19T13:55:28Z",
"url" : "https://www.youtube.com/watch?v=Xb0P5t5NQWM",
"_links" : {
"self" : {
"href" : "http://localhost/youTubeVideos/Xb0P5t5NQWM"
},
"youTubeVideo" : {
"href" : "http://localhost/youTubeVideos/Xb0P5t5NQWM{?projection}",
"templated" : true
},
"user" : {
"href" : "http://localhost/youTubeVideos/Xb0P5t5NQWM/user"
}
}
}
You'll have to transform your ResponseEntity to Resource first and then add the links manually.
It should be something like this :
#ExceptionHandler(value = {ExistentUGVException.class})
#ResponseBody
protected ResponseEntity<Resource<UGV>> existentUGVHandler(HttpServletRequest request, HttpServletResponse response, ExistentUGVException ex) {
final Resource<UGV> resource = getResource(ex.ugv);
return new ResponseEntity<Resource<UGV>>(resource, HttpStatus.OK);
}
public Resource<T> getResource(T object, Link... links) throws Exception {
Object getIdMethod = object.getClass().getMethod("getId").invoke(object);
Resource<T> resource = new Resource<T>(object); // The main resource
final Link selfLink = entityLinks.linkToSingleResource(object.getClass(), getIdMethod).withSelfRel();
String mappingRel = CLASSMAPPING.getMapping(this.getClass());
final Link resourceLink = linkTo(this.getClass()).withRel(mappingRel);
resource.add(selfLink, resourceLink);
resource.add(links);
return resource;
}
Take a look here, there's all you need : spring hateoas documentation
Related
First of all, the api works as intended locally, when deploying to azure functions app, the api endpoint keeps loading and it will eventually show HTTP.504(Gateway Timeout)
page keeps loading, no response from azure functions
Integration
I'm looking to fetch all data from the collection when I call HttpTrigger
Function.java
#FunctionName("get")
public HttpResponseMessage get(
#HttpTrigger(name = "req",
methods = {HttpMethod.GET, HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
#CosmosDBInput(name = "database",
databaseName = "progMobile",
collectionName = "news",
partitionKey = "{Query.id}",
connectionStringSetting = "CosmosDBConnectionString")
Optional<String> item,
final ExecutionContext context) {
// Item list
context.getLogger().info("Parameters are: " + request.getQueryParameters());
context.getLogger().info("String from the database is " + (item.isPresent() ? item.get() : null));
// Convert and display
if (!item.isPresent()) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("Document not found.")
.build();
}
else {
// return JSON from Cosmos. Alternatively, we can parse the JSON string
// and return an enriched JSON object.
return request.createResponseBuilder(HttpStatus.OK)
.header("Content-Type", "application/json")
.body(item.get())
.build();
}
}
Function.json
{
"scriptFile" : "../ProgMobileBackend-1.0-SNAPSHOT.jar",
"entryPoint" : "com.function.Function.get",
"bindings" : [ {
"type" : "httpTrigger",
"direction" : "in",
"name" : "req",
"methods" : [ "GET", "POST" ],
"authLevel" : "ANONYMOUS"
}, {
"type" : "cosmosDB",
"direction" : "in",
"name" : "database",
"databaseName" : "progMobile",
"partitionKey" : "{Query.id}",
"connectionStringSetting" : "CosmosDBConnectionString",
"collectionName" : "news"
}, {
"type" : "http",
"direction" : "out",
"name" : "$return"
} ]
}
Azure Functions monitor log does not show any error
Running the function in the portal(Code + Test menu) does not show any error either
httpTrigger I'm using: https://johnmiguel.azurewebsites.net/api/get?id=id
I added CosmosDBConnectionString value to Azure Functions App configuration(did not check on "Deployment slot" option)
I'm using an instance of CosmosDB for NoSQL
Functions App runtime is set to Java and version set to Java 8
figured it out. Java function was in Java 17 and Function App in Java 8.
Is it possible to concisely implement a single HAL-JSON & JSON endpoints in Spring Boot 2? The goal is to have:
curl -v http://localhost:8091/books
return this application/hal+json result:
{
"_embedded" : {
"bookList" : [ {
"title" : "The As",
"author" : "ab",
"isbn" : "A"
}, {
"title" : "The Bs",
"author" : "ab",
"isbn" : "B"
}, {
"title" : "The Cs",
"author" : "cd",
"isbn" : "C"
} ]
}
and for this (and/or the HTTP Accept header since this is a REST API):
curl -v http://localhost:8091/books?format=application/json
to return the plain application/json result:
[ {
"title" : "The As",
"author" : "ab",
"isbn" : "A"
}, {
"title" : "The Bs",
"author" : "ab",
"isbn" : "B"
}, {
"title" : "The Cs",
"author" : "cd",
"isbn" : "C"
} ]
with minimal controller code. These endpoints work as expected:
#GetMapping("/asJson")
public Collection<Book> booksAsJson() {
return _books();
}
#GetMapping("/asHalJson")
public CollectionModel<Book> booksAsHalJson() {
return _halJson(_books());
}
#GetMapping
public ResponseEntity<?> booksWithParam(
#RequestParam(name="format", defaultValue="application/hal+json")
String format) {
return _selectedMediaType(_books(), format);
}
#GetMapping("/asDesired")
public ResponseEntity<?> booksAsDesired() {
return _selectedMediaType(_books(), _format());
}
with the following helpers:
private String _format() {
// TODO: something clever here...perhaps Spring's content-negotiation?
return MediaTypes.HAL_JSON_VALUE;
}
private <T> static CollectionModel<T> _halJson(Collection<T> items) {
return CollectionModel.of(items);
}
private <T> static ResponseEntity<?> _selectedMediaType(
Collection<T> items, String format) {
return ResponseEntity.ok(switch(format.toLowerCase()) {
case MediaTypes.HAL_JSON_VALUE -> _halJson(items);
case MediaType.APPLICATION_JSON_VALUE -> items;
default -> throw _unknownFormat(format);
});
}
but the booksWithParam implementation is too messy to duplicate for each endpoint. Is there a way to get to, or close to, something like the booksAsDesired implementation or something similarly concise?
One way you could tell Spring that you want to support plain JSON is by adding a custom converter for such media types. This can be done by overwriting the extendMessageConverters method of WebMvcConfigurer and adding your custom converters there like in the sample below:
import ...PlainJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSuport;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servelt.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import javax.annotation.Nonnull;
#Configuration
#EnableSpringeDataWebSupport
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void extendMessageConverters(#Nonnull final List<HttpMessageConverter<?>> converters) {
converters.add(new PlainJsonHttpMessageConverter());
}
}
The message converter itself is also no rocket-science as can be seen by the PlainJsonHttpMessageConverter sample below:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsr310.JavaTimeModule;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
#Component
public class PlainJsonHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public PlainJsonHttpMessageConverter() {
super(new ObjectMapper(), MediaType.APPLICATION_JSON);
// add support for date and time format conversion to ISO 8601 and others
this.defaultObjectMapper.registerModule(new JavaTimeModule());
// return JSON payload in pretty format
this.defaultObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
#Override
protected boolean supports(#Nonnull final Class<?> clazz) {
return RepresentationModel.class.isAssignableFrom(clazz);
}
}
This should enable plain JSON support besides HAL-JSON without you having to do any further branching or custom media-type specific conversion within your domain logic or service code.
I.e. let's take a simple task as example case. Within a TaskController you might have a code like this
#GetMapping(path = "/{taskId:.+}", produces = {
MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE,
MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE
})
public ResponseEntity<?> task(#PathVariable("taskId") String taskId,
#RequestParam(required = false) Map<String, String> queryParams,
HttpServletRequest request) {
if (queryParams == null) {
queryParams = new HashMap<>();
}
Pageable pageable = RequestUtils.getPageableForInput(queryParams);
final String caseId = queryParams.get("caseId");
...
final Query query = buildSearchCriteria(taskId, caseId, ...);
query.with(pageable);
List<Task> matches = mongoTemplate.find(query, Task.class);
if (!matches.isEmpty()) {
final Task task = matches.get(0);
return ResponseEntity.ok()
.eTag(Long.toString(task.getVersion())
.body(TASK_ASSEMBLER.toModel(task));
} else {
if (request.getHeader("Accept").contains(MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.contentType(MediaTypes.HTTP_PROBLEM_DETAILS_JSON)
.body(generateNotFoundProblem(request, taskId));
} else {
final String msg = "No task with ID " + taskId + " found";
throw new ResponseStatusException(HttpStatus.NOT_FOUND, msg);
}
}
}
which simply retrieves an arbitrary task via its unique identifier and returns the representation for it according to the one specified in the Accept HTTP header. The TASK_ASSEMBLER here is just a custom Spring HATEOAS RepresentationModelAssembler<Task, TaskResource> class that converts task objects to task resources by adding links for certain related things.
This can now be easily tested via Spring MVC tests such as
#Test
public void halJson() throws Exception {
given(mongoTemplate.find(any(Query.class), eq(Task.class)))
.willReturn(setupSingleTaskList());
final ResultActions result = mockMvc.perform(
get("/api/tasks/taskId")
.accept(MediaTypes.HAL_JSON_VALUE)
);
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON_VALUE));
// see raw payload received by commenting out below line
// System.err.println(result.andReturn().getResponse().getContentAsString());
verifyHalJson(result);
}
#Test
public void plainJson() throws Exception {
given(mongoTemplate.find(any(Query.class), eq(Task.class)))
.willReturn(setupSingleTaskList());
final ResultActions result = mockMvc.perform(
get("/api/tasks/taskId")
.accept(MediaType.APPLICATION_JSON_VALUE)
);
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE));
// see raw payload received by commenting out below line
// System.err.println(result.andReturn().getResponse().getContentAsString());
verifyPlainJson(result);
}
...
private void verifyHalJson(final ResultActions action) throws Exception {
action.andExpect(jsonPath("taskId", is("taskId")))
.andExpect(jsonPath("caseId", is("caseId")))
...
.andExpect(jsonPath("_links.self.href", is(BASE_URI + "/tasks/taskId")))
.andExpect(jsonPath("_links.up.href", is(BASE_URI + "/tasks")));
}
rivate void verifyPlainJson(final ResultActions action) throws Exception {
action.andExpect(jsonPath("taskId", is("taskId")))
.andExpect(jsonPath("caseId", is("caseId")))
...
.andExpect(jsonPath("links[0].rel", is("self")))
.andExpect(jsonPath("links[0].href", is(BASE_URI + "/tasks/taskId")))
.andExpect(jsonPath("links[1].rel", is("up")))
.andExpect(jsonPath("links[1].href", is(BASE_URI + "/tasks")));
}
Note how links are presented here differently depending on which media type you've selected.
I want to generate a swagger from a JAX-RS endpoint with an external enumeration definition however the generated swagger directly includes the enumeration into the definition of the model. It implies that the enumeration documentation is not generated but also that the same enumeration is duplicated on the client side.
I use the swagger-jaxrs dependency to scan my endpoint and generate the swagger json file. This GitHub repository can be used to reproduce the problem. I also have created a GitHub issue on the swagger-core repository.
The JAX-RS endpoint
#Api("hello")
#Path("/helloSwagger")
public class HelloSwagger {
#ApiOperation(value = "Get all unique customers", notes = "Get all customers matching the given search string.", responseContainer = "Set", response = User.class)
#GET
#Path("/getUniqueUsers")
#Produces(MediaType.APPLICATION_JSON)
public Set<User> getUniqueUsers(
#ApiParam(value = "The search string is used to find customer by their name. Not case sensitive.") #QueryParam("search") String searchString,
#ApiParam(value = "Limits the size of the result set", defaultValue = "50") #QueryParam("limit") int limit
) {
return new HashSet<>(Arrays.asList(new User(), new User()));
}
}
The model with the enumeration
public class User {
private String name = "unknown";
private SynchronizationStatus ldap1 = SynchronizationStatus.UNKNOWN;
private SynchronizationStatus ldap2 = SynchronizationStatus.OFFLINE;
#ApiModelProperty(value = "The user name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ApiModelProperty(value = "The synchronization status with the LDAP1")
public SynchronizationStatus getLdap1() {
return ldap1;
}
public void setLdap1(SynchronizationStatus ldap1) {
this.ldap1 = ldap1;
}
public SynchronizationStatus getLdap2() {
return ldap2;
}
public void setLdap2(SynchronizationStatus ldap2) {
this.ldap2 = ldap2;
}
}
#ApiModel("The synchronization status with LDAP instance.")
public enum SynchronizationStatus {
UNKNOWN,
SYNC,
OFFLINE,
CONFLICT
}
An extract of the swagger generated
{
(...)
},
"definitions" : {
"User" : {
"type" : "object",
"properties" : {
"name" : {
"type" : "string",
"description" : "The user name"
},
"ldap1" : {
"type" : "string",
"description" : "The synchronization status with the LDAP1",
"enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ]
},
"ldap2" : {
"type" : "string",
"enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ]
}
}
}
}
}
Expected result
{
(...)
"definitions" : {
"SynchronizationStatus" : {
"description" : "The synchronization status with LDAP instance.",
"enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ],
"type" : "string"
},
"User" : {
"type" : "object",
"properties" : {
"name" : {
"type" : "string",
"description" : "The user name"
},
"ldap1" : {
"$ref" : "#/definitions/SynchronizationStatus"
},
"ldap2" : {
"$ref" : "#/definitions/SynchronizationStatus"
}
}
}
}
}
Am I doing something wrong or is it a 'feature' of the swagger-jaxrs library ?
Thanks for your help
Am I doing something wrong or is it a 'feature' of the swagger-jaxrs
library ?
Enum value are treat as primitive value type by swagger and swagger out-of-the-box does not generate model definition for enum type (see code line 209 under). So the is a feature and not related with swagger-jaxrs.
However, you can generate the swagger definition, as per your expectation, by providing the custom model converter(io.swagger.converter.ModelConverter).
But it seems to me a nice feature to be available in swagger out-of-the-box.
Following is a ruff implementation which can help you to generate the expected swagger definition.
package nhenneaux.test.swagger.ext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.databind.JavaType;
import io.swagger.annotations.ApiModel;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.jackson.ModelResolver;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import io.swagger.util.Json;
public class EnumAsModelAwareResolver extends ModelResolver {
static final EnumAsModelAwareResolver INSTANCE = new EnumAsModelAwareResolver();
public EnumAsModelAwareResolver() {
super(Json.mapper());
}
#Override
public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations,
Iterator<ModelConverter> chain) {
if (isEnumAnApiModel(type)) {
String name = findName(type);
// ask context to resolver enum type (for adding model definition
// for enum under definitions section
context.resolve(type);
return new RefProperty(name);
}
return chain.next().resolveProperty(type, context, annotations, chain);
}
private String findName(Type type) {
JavaType javaType = _mapper.constructType(type);
Class<?> rawClass = javaType.getRawClass();
ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
String name = annotation.value();
if (name == null || name.length() == 0) {
name = rawClass.getSimpleName();
}
return name;
}
private boolean isEnumAnApiModel(Type type) {
JavaType javaType = _mapper.constructType(type);
return javaType.isEnumType()
&& javaType.getRawClass().isAnnotationPresent(ApiModel.class);
}
#Override
public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
JavaType javaType = Json.mapper().constructType(type);
if (javaType.isEnumType()) {
ModelImpl model = new ModelImpl();
Class<?> rawClass = javaType.getRawClass();
ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
String name = annotation.value();
if (name == null || name.length() == 0) {
name = rawClass.getSimpleName();
}
model.setName(name);
model.setDescription(annotation.description());
model.setType(StringProperty.TYPE);
List<String> constants = findEnumConstants(rawClass);
model.setEnum(constants);
return model;
}
return chain.next().resolve(type, context, chain);
}
private List<String> findEnumConstants(Class<?> rawClass) {
StringProperty p = new StringProperty();
_addEnumProps(rawClass, p);
return p.getEnum();
}
}
package nhenneaux.test.swagger.ext;
import io.swagger.converter.ModelConverters;
import io.swagger.jaxrs.config.BeanConfig;
import nhenneaux.test.swagger.ext.EnumAsModelAwareResolver;
public class EnumModelAwareBeanConfig extends BeanConfig {
public EnumModelAwareBeanConfig() {
registerResolver();
}
private void registerResolver() {
ModelConverters modelConverters = ModelConverters.getInstance();
// remove and add; in case it is called multiple times.
// should find a better way to register this.
modelConverters.removeConverter(EnumAsModelAwareResolver.INSTANCE);
modelConverters.addConverter(EnumAsModelAwareResolver.INSTANCE);
}
}
In your test use:
final BeanConfig beanConfig = new nhenneaux.test.endpoint.model.EnumModelAwareBeanConfig();
Hops this helps.
You could try the reference attribute of the #ApiModelProperty annotation:
#ApiModelProperty(reference = "#/definitions/SynchronizationStatus")
public SynchronizationStatus getLdap1() {
return ldap1;
}
Based on this mailing list post from last year I believe it is not trivial and one may have to extend the appropriate Swagger resources. The only other option would be to manually reference the model as per Cássio Mazzochi Molin's answer (just be careful that renaming SynchronizationStatus doesn't break the API docs due to the forced use of a non-generated string)
I was able to achieve this with (using swagger-jaxrs-2.1.3)
System.setProperty(ModelResolver.SET_PROPERTY_OF_ENUMS_AS_REF, "true");
Reader reader = new Reader();
OpenAPI api = reader.read(...);
I have designed login module in RESTFul API using jersey.
whenever any error occurred while login it will return error code and message like,
{
"errorFlag": 1,
"errorMessage": "Login Failed"
}
but whenever I get successful results it returns
{
"apiKey": "3942328b-fa65-496c-bf32-910aafbc1b0e",
"email": "caXXXX#gmail.inl",
"name": "Chandrakant"
}
I'm looking for results like below
{
"errorFlag": 0,
"errorMessage":{
"apiKey": "3942328b-fa65-496c-bf32-910aafbc1b0e",
"email": "caXXXX#gmail.inl",
"name": "Chandrakant"}
}
Use structure like below,
{
status/statusCode : 200/400, //eg. 200 for success, any other for failure.
statusMessage : "Success/<failureMessage>",
errorDetails : "Failed due to <reason>" //optional
data :{ //data will exists only in case of success call.
}
}
you can achieve this like below,
#GET
#Path("/images/{image}")
#Produces("image/*")
public Response getImage(#PathParam("image") String image) {
File f = new File(image);
if (!f.exists()) {
throw new WebApplicationException(404);
}
String mt = new MimetypesFileTypeMap().getContentType(f);
return Response.ok(f, mt).build();
}
You can return all the attributes in HashMap as key value .
Below piece of code worked for me
#POST
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public HashMap check(InputStream inputJsonObj) {
HashMap map = new HashMap();
map.put("key1", "value1");
return map;
}
I am using a ISBNdB to get info about the books.The reponse type is application/octet-stream. A sample json response I get looks as follows
{
"index_searched" : "isbn",
"data" : [
{
"publisher_id" : "john_wiley_sons_inc",
"publisher_name" : "John Wiley & Sons, Inc",
"title_latin" : "Java programming interviews exposed",
"language" : "eng",
"summary" : "",
"physical_description_text" : "1 online resource (xvi, 368 pages) :",
"author_data" : [
{
"name" : "Markham, Noel",
"id" : "markham_noel"
},
{
"id" : "greg_milette",
"name" : "Greg Milette"
}
],
"title_long" : "Java programming interviews exposed",
"urls_text" : "",
"publisher_text" : "New York; John Wiley & Sons, Inc",
"book_id" : "java_programming_interviews_exposed",
"awards_text" : "; ",
"subject_ids" : [],
"isbn13" : "9781118722862",
"lcc_number" : "",
"title" : "Java programming interviews exposed",
"isbn10" : "1118722868",
"dewey_decimal" : "005.13/3",
"edition_info" : "; ",
"notes" : "\"Wrox programmer to programmer\"--Cover.; Acceso restringido a usuarios UCM = For UCM patrons only.",
"marc_enc_level" : "",
"dewey_normal" : "5.133"
}
]
}
I am using Jackson to convert this reponse. My Pojo looks as follows
#JsonIgnoreProperties(ignoreUnknown = true)
public class value {
private String index_searched;
// Another pojo in different file with ignore properties
private data[] dat;
public value(){
}
public data[] getDat() {
return dat;
}
public void setDat(data[] dat) {
this.dat = dat;
}
public String getIndex_searched() {
return index_searched;
}
public void setIndex_searched(String index_searched) {
this.index_searched = index_searched;
}
}
When I tried following
value book = restTemplate.getForObject(FINAL_URL, value.class);
I get this exception
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.rocketrenga.mylibrary.domain.value] and content type [application/octet-stream]
But I am able to map the response to String
String book = restTemplate.getForObject(FINAL_URL, String.class);
ObjectMapper mapper = new ObjectMapper();
value val = mapper.readValue(book, value.class);
System.out.println(val.getIndex_searched());
How to go about mapping the response directly POJO instead of String and converting back to POJO
You need to conifigure restTemplate with message converters. In your configuration do the following:
#Bean
public RestOperations restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(
Arrays.asList(new MediaType[]{MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM}));
restTemplate.setMessageConverters(Arrays.asList(converter, new FormHttpMessageConverter()));
return restTemplate;
}
I guess the better solution is to just add another converter, not to modify current ones:
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(jacksonSupportsMoreTypes());
return restTemplate;
}
private HttpMessageConverter jacksonSupportsMoreTypes() {//eg. Gitlab returns JSON as plain text
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.parseMediaType("text/plain;charset=utf-8"), MediaType.APPLICATION_OCTET_STREAM));
return converter;
}