No suitable HttpMessageConverter for content type application/json;charset=UTF-8 - spring-boot

I am getting the exception in the title, even after adding all the code found online in other solutions (I've added the HttpMessageConverters as well as the APPLICATION_JSON accept header.)
public MyObject myMethod(String arg) {
String uri = BASE_URI + "/var?arg=" + arg;
restTemplate.setMessageConverters(getMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<MyObject> response = restTemplate.exchange(uri, HttpMethod.GET, entity, MyObject.class);
MyObject resource = response.getBody();
return resource;
}
private List<HttpMessageConverter<?>> getMessageConverters() {
List<HttpMessageConverter<?>> converters =
new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return converters;
}
MyObject:
public class MyObject {
private String test;
//more fields
#JsonCreator
public MyObject(String test) { //more args
this.test = test;
//more assignments
}
Anyone have any idea?
EDIT: relevant dependencies in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>

By default spring or springboot configures the following message converters during startup :
ByteArrayHttpMessageConverter – converts byte arrays
StringHttpMessageConverter – converts Strings
ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream
SourceHttpMessageConverter – converts javax.xml.transform.Source
FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML
MappingJackson2HttpMessageConverter – converts JSON
MappingJacksonHttpMessageConverter – converts JSON
AtomFeedHttpMessageConverter – converts Atom feeds
RssChannelHttpMessageConverter – converts RSS feeds
But for the Jackson converters to be added , spring has to detect that jackson is present in the classpath, so by adding jackson dependency to your application,the converter should be automatically configured, unless you are explicitly preventing the auto configuration by using the #EnableWebMVC annotation.
Also,ensure that if you are using a rest endpoint, the method is annotated correctly, that is either use #RestController for the class or else you will have to provide the #ResponseBody annotation to indicate spring that it is a rest endpoint.
From the documentation:
Annotation indicating a method parameter should be bound to the body of the web request. The body of the request is passed through an
HttpMessageConverter to resolve the method argument depending on the
content type of the request. Optionally, automatic validation can be
applied by annotating the argument with #Valid. Supported for
annotated handler methods in Servlet environments.

Note that for the HttpMessageConverter to be eligible for mapping, it's canRead method must return true.
In the case of AbstractJackson2HttpMessageConverter (parent of MappingJackson2HttpMessageConverter), it checks not only MediaType, but also if Jackson can deserialize the object.
#Override
public boolean canRead(Type type, #Nullable Class<?> contextClass, #Nullable MediaType mediaType) {
if (!canRead(mediaType)) {
return false;
}
JavaType javaType = getJavaType(type, contextClass);
AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (this.objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
Now, I believe your JsonCreator is incorrect.
NOTE: when annotating creator methods (constructors, factory methods), method must either be:
Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to
See also Why when a constructor is annotated with #JsonCreator, its arguments must be annotated with #JsonProperty?

Related

CamelHttp multipart/form-data upload. Changes between camel-http4 and camel-http-starter

I have a Spring Boot app that uses Camel to POST a multipart/form-data request to a REST endpoint. The request includes a text part and a file part. The code that builds the request is as follows:
#Override
public void process(Exchange exchange) throws Exception {
#SuppressWarnings("unchecked")
Map<String, String> body = exchange.getIn().getBody(Map.class);
String fileName = body.get("FILE_NAME");
String filePath = body.get("FILE_PATH");
MultipartEntityBuilder entity = MultipartEntityBuilder.create();
entity.addTextBody("name", fileName, ContentType.DEFAULT_TEXT);
entity.addBinaryBody("file", new File(filePath),
ContentType.APPLICATION_OCTET_STREAM, fileName);
exchange.getIn().setBody(entity.build());
}
.to("https4://<endpoint>")
This code works nicely.
In my pom.xml file I am importing the camel-http4 component:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http4</artifactId>
<version>${camel.version}</version>
</dependency>
I have tried replacing the camel-http4 component with camel-http-starter, as suggested by the latest Camel documentation at https://camel.apache.org/components/latest/http-component.html
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http-starter</artifactId>
<version>${camel.version}</version>
</dependency>
and replace all http4 endpoints with http, but the code above now fails because there is no type converter available between HttpEntity and InputStream in the camel-http component.
I have tried using Camel's MIME Multipart DataFormat:
.setHeader("name", simple("${body[FILE_NAME]}"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
#SuppressWarnings("unchecked")
Map<String, String> body = exchange.getIn().getBody(Map.class);
String filePath = body.get("FILE_PATH");
exchange.getIn().setBody(new File(filePath));
}
})
// Add only "name" header in multipart request
.marshal().mimeMultipart("form-data", true, true, "(name)", true)
.to("https://<endpoint>")
But I keep getting HTTP 400 errors from the server, meaning it doesn't understand the request.
So my question is:
How to use the MIME Multipart DataFormat (or any other way) to obtain the same multipart request as the previous working code?

How to fix multipart/form-data MediaType not being set with Jackson Spring MVC

I'm trying to send a Product and product images from Angular 7 frontend to a SpringMVC backend.
To add support for Multipart files I've added this bean inside my AppConfig.
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
Since I want to receive the Product object separately inside the controller I'm using #RequestPart to fetch both separately like this:
#RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
On the frontend I'm adding the image to FormData like this:
let formData = new FormData();
formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);
this.http.post(this.appService.getApiUrl() + "api/product/save/", product);
The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type.
I tried debugging this issue by setting breakpoints inside CommonsMultipartResolver class and after tracing the request through the code I've found that when the getSupportedMediaTypes() is called it returns only two media types:
application/json
application/*+json
Inside the following method in AbstractHttpMessageConverter:
protected boolean canRead(#Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
Finding this I tried adding MediaType.MULTIPART_FORM_DATA like this inside AppConfig:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
List<MediaType> types = new ArrayList<>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_JSON_UTF8);
types.add(MediaType.MULTIPART_FORM_DATA);
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(types);
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(hibernate5Module);
}
}
}
But it still wouldn't work. When the app starts up, I do see the constructor of AbstractJackson2HttpMessageConverter being called with my MediaTypes but they get overwritten by more calls to the same constructor after it.
Is there any way I can get the MediaType to persist? I might be looking in the wrong direction so any insight will be helpful.
The Jackson library is required on the classpath. Spring does not declare this by default. Make sure that at least com.fasterxml.jackson.core:jackson-databind is available in the classpath of the Spring MVC application. Example for Apache Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Example for the jackson.version value: 2.9.0
1) You need to give input data supported at server end. Since you are sending File, means server is consuming the Multipart Data.
For multipart we need to set consumes = "multipart/form-data"
#RequestMapping(value = "save", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
2) Since form is sending multipart data we need to set content-type at front end too in http header in post call.
content-type: multipart/form-data"

spring boot with restcontroller using jsonobject not working

I am using spring boot, and while making a restcontroller or controller if I use the jsonobject type request then it doesnt work, whereas same works when I change type to string.
#Controller
#RequestMapping("rest/dummy")
public class CustomerController {
#GetMapping("test")
public ResponseEntity test(#RequestParam("req") JSONObject inputData) {
org.json.JSONObject response = new org.json.JSONObject();
response.put("abc", "123");
return new ResponseEntity(inputData.toString(), HttpStatus.OK);
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20171018</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
I do want to use it both GET and POST type and also I want to use jsonobject for both request and response as the data can change on fly and its type.
In RequestParam , we send key values which added in URL,
To send Json object send it in RequestBody .
Use #RequestBody and send your Json in body part of your request.
Using real POJOs as params and return values is the better approach imo. Use Jackson annotations to configure those POJOs.
Anyways. This should work:
#GetMapping("test")
public ResponseEntity<String> test(#RequestParam("req") JSONObject inputData) {
org.json.JSONObject response = new org.json.JSONObject();
response.put("abc", "123");
return ResponseEntity.ok(inputData.toString());
}
alternatively
#GetMapping("test")
public ResponseEntity<SomeOutputDto> test(#RequestParam("req") String inputData) {
SomeOutputDto out = new SomeOutputDto();
out.setAbc(123);
return ResponseEntity.ok(dto);
}
this requires a additional class: SomeOutputDto, but on the other hand, you have more control over your code.
public class SomeOutputDto {
private int abc = 0;
public void setAbc(int v) {
this.abc = v;
}
public int getAbc() { return this.abc; }
}
Got it working by using apache-tomcat 8.0.15, the same doesnt work with apache-tomcat 8.0.49

Angular2 does not pass parameters in POST request

I am trying to pass a parameter using Angular POST request to Tomcat server, Spring Framework. Somehow I see that the parameter is there when it is sent, but it somehow does not arrive/properly retrieved on the backend. Here is the Angular2 code:
addCompany() {
console.log("addCompany button clicked!");
console.log("company name: " + this.input);
let nameId = this.input;
let body = JSON.stringify({ input: nameId });
let headers = new Headers({ 'Content-Type': 'application/json', 'X-CSRF-TOKEN':this.getToken() });
console.log("csrf token: " + this.getToken());
let options = new RequestOptions({ headers: headers });
this.http.post('http://localhost:8080/views/addcompany', body, options).toPromise()
.then(() => {
console.log("company added!");
this.reloadList();
});;
}
When I am trying to get it in Spring I am getting null for the parameter:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(HttpServletRequest request,
HttpServletResponse response) {
String nameId = request.getParameter("input");
eventService.addCompany(nameId);
}
I tried also this way:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(Model model, #RequestParam("input") String nameId) {
eventService.addCompany(nameId);
}
And in Angular code I have been trying to change commas everywhere, like:
let nameId = this.input;
let body = JSON.stringify({ 'input': nameId });
etc.
I tried this one: Angular2 - Http POST request parameters
Following the suggestion JB Nizet I tried to create POJO:
public class Company {
public String input;
public Company() {
this.input = "";
}
public String getInput() {
return this.input;
}
public void setInput(String input) {
this.input = input;
}
}
Register it in my #Configuration file:
#Bean
public Company getCompany(){
return new Company();
}
And changed the request method to the following:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(Company company) {
eventService.addCompany(company.input);
}
After that I am getting Company object in the method with input=null.
Then I tried to deregister the Company #Bean from #Configuration and change the request method to the following:
#RequestMapping(value = "/addcompany", method = RequestMethod.POST)
#ResponseBody
public void addCompany(#RequestBody Company company) {
eventService.addCompany(company.input);
}
But after that I am getting 415 (Unsupported Media Type) error.
In pom.xml I have the following jackson import:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.10</version>
</dependency>
Substituting it for second jackson version solved the issue:
<properties>
...
<jackson.version>2.7.5</jackson.version>
</properties>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
You're sending a JSON object as body, and you expect to get a request parameter containing an attribute of this JSON object.
That won't happen. You need a POJO that matches with the structure of the JSON sent, take that POJO as argument and annotate it with #RequestBody. Jackson will unmarshal the JSON to the POJO and the POJO will be passed to your method.
Request parameters can be used if the request contains an application/x-www-form-urlencoded payload: the kind of payload you send when submitting a HTML form, without doing any JavaScript.
Instead of
let body = JSON.stringify({ input: nameId });
try
let body = { input: nameId };
Try to use :
let body:string='input='+nameId;
And use this header
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN':this.getToken() });
Only for other readers if you want to send more than 1 parameter. use something & fro separating parameter . like below code
let body :string='username='+username+'&password='+password;

Spring MVC 4 response body serialization works with JSON but not with XML

I am working on setting up REST API with Spring 4. The HTTP Message converters are present by default for JSON & XML. I try to setup two end-points, one for returning JSON & another for XML. The JSON object seems to be returned as expected but when i try to hit xml, i end up with 406 exception,
The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.
I have included the Maven dependencies for both JSON & XML. Below is the Snippet of pom.xml,
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api.version}</version>
</dependency>
Below is the controller code,
#RestController
#RequestMapping(value="/employee")
public class HelloController {
#RequestMapping(method=RequestMethod.GET , produces="application/json",value="/hello.json")
public List<Employee> getEmployeeJson(){
Employee emp = new Employee();
emp.setId(1);
emp.setName("x");
Employee emp1 = new Employee();
emp1.setId(2);
emp1.setName("y");
List<Employee> res = new ArrayList<Employee>();
res.add(emp);
res.add(emp1);
return res;
}
#RequestMapping(method=RequestMethod.GET , produces="application/xml",value="/hello.xml")
public List<Employee> getEmployeeXml(){
Employee emp = new Employee();
emp.setId(1);
emp.setName("x");
Employee emp1 = new Employee();
emp1.setId(2);
emp1.setName("y");
List<Employee> res = new ArrayList<Employee>();
res.add(emp);
res.add(emp1);
return res;
}
}
Do share your thoughts on what am missing here
According to the documentation you should add jackson-dataformat-xml dependency to enable response body XML serialization. In case you are using Maven just add:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Resources