RestTemplate: no suitable HttpMessageConverter found exception - java

I am trying to parse following xml response in spring boot:
Response structure:
<!DOCTYPE RESULT SYSTEM 'http://www.example.com/example/exampleV1.00.dtd'>
<RESULT REQID ='10961549902'>
<MID SUBMITDATE='2017-08-14 17:17:29' ID='1' TAG = 'null' TID = '24180566483'></MID>
</RESULT>
Api call from Service:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject("http://www.exampleUrl.com",ResponseXml.class);
ResponseXml.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "RESULT")
public class ResponseXml implements Serializable{
#XmlAttribute(name="REQID")
private long requestId;
#XmlElement(name = "MID")
private long MID;
public ResponseXml() {
super();
}
public ResponseXml(long requestId) {
super();
this.requestId = requestId;
}
//getter setter
public class MID {
#XmlAttribute(name="SUBMITDATE")
private Date submitDate;
#XmlAttribute(name="ID")
private Long id;
#XmlAttribute(name="TAG")
private Long tag;
#XmlAttribute(name="TID")
private Long tid;
//getter setter
}
}
Exception thrown:
Method threw 'org.springframework.web.client.RestClientException' exception.
Detailed message: Could not extract response: no suitable HttpMessageConverter found
for response type [class com.hk.response.sms.NetcoreResponseXml]
and content type [text/plain;charset=UTF-8]
Please advice a fix, as to how do I parse xml response in JAVA spring boot?
Why am I getting HttpMessageConverter exception despite adding #XmlRootElement annotation?

If it is not possible to fix server then configure message converted in a client application to accept plain/text:
// Create converter which supports text/plain mime type.
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
List<MediaType> mediaTypes = jsonConverter.getSupportedMediaTypes();
mediaTypes.add(MediaType.TEXT_PLAIN);
jsonConverter.setSupportedMediaTypes(mediaTypes);
// Alternatively get mapper as injected bean:
// #Inject
// private XmlMapper mapper;
XmlMapper mapper = new XmlMapper();
jsonConverter.setObjectMapper(mapper);
RestTemplate restTemplate = new RestTemplate();
// Register converter with RestTemplate
restTemplate.setMessageConverters(Arrays.asList(jsonConverter));

Related

How can accept both JSON and XML?

I saw similar question, and added to consumes parameters both MediaType. But it can't help me :(
I want to accept requests in json and xml formats.
There is my controller:
#RequestMapping(value = "/api/client", produces = "application/json;charset=UTF-8",
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public class ClientController {
#Autowired
private ClientService clientService;
#PostMapping("/create")
public String createClient(#Valid #RequestBody ClientDto clientDto) {
return clientService.createUser(clientDto);
}
}
Dto class:
import javax.validation.constraints.NotNull;
public class ClientDto {
private long client_id;
#NotNull
private String first_name;
#NotNull
private String last_name;
private List<AccountDto> accounts;
getter//setters//constructor//
}
I sending requests from Postman. I haven't problems with json.
But I can't accept xml format.
2021-08-15 20:08:43.600 WARN 15820 --- [nio-8081-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/xml;charset=UTF-8' not supported]
My problem was solved by #XmlRootElement annotation
#XmlRootElement
public class ClientDto {
private long client_id;
private String first_name;
private String last_name;
private List<AccountDto> accounts;
...
}
and
#XmlRootElement
public class AccountDto {
...
}

Spring Boot Test: UserControllerTest Fails When Using Jackson ObjectMapper To Convert Model To JSON String

I'm testing a #RestContoller in Spring Boot which has a #PostMapping method and the method #RequestBody is validated using #Valid annotation. To test it, I'm using MockMvc and in order to populate request body content I'm using Jackson ObjectMapper; however, when the model is passed, the test fails:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/user/register
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.springboottutorial.todoapp.controller.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.String> com.springboottutorial.todoapp.controller.UserController.register(com.springboottutorial.todoapp.dao.model.User)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :400
User Model:
#Entity
#Table(name = "users",
uniqueConstraints = #UniqueConstraint(columnNames = {"EMAIL"}))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "FIRST_NAME")
#NotNull
private String firstName;
#Column(name = "LAST_NAME")
#NotNull
private String lastName;
#Column(name = "EMAIL")
#NotNull
#Email
private String emailAddress;
#Column(name = "PASSWORD")
#NotNull
private String password;
#Column(name = "CREATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime createdAt;
#Column(name = "UPDATED_AT")
#NotNull
#Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime updatedAt;
public User(#NotNull String firstName, #NotNull String lastName,
#NotNull #Email String emailAddress, #NotNull String password,
#NotNull LocalDateTime createdAt, #NotNull LocalDateTime updatedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
this.password = password;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
//setters and getters: omitted
UserController:
#RestController
#RequestMapping("/api/user")
public class UserController {
#Autowired
UserService userService;
#PostMapping("/register")
public ResponseEntity<String> register(#RequestBody #Valid User user){
userService.createUser(user);
return ResponseEntity.ok().build();
}
}
UserControllerTest:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
When I build JSON string manually, the test passes:
String json = "{\n" +
"\t\"firstName\" : \"John\",\n" +
"\t\"lastName\" : \"QPublic\",\n" +
"\t\"password\" : \"123456789\",\n" +
"\t\"createdAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"updatedAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"emailAddress\" : \"john.public#gmail.com\"\n" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
Spring is not necessarily providing you with a "vanilla" ObjectMapper instance. By having Spring inject the ObjectMapper instance into the test instead of creating an ObjectMapper with the default constructor, you will get an instance that matches your actual run-time environment, provided that your spring profile for your unit tests is set up correctly.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper objectMapper;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(objectMapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
As chrylis mentioned in comments, the problem occurred due to Java 8 Date & Time API and Jackson serialization conflicts. By default, ObjectMapper doesn't understand the LocalDateTime data type, so I need to add com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency to my maven which is a datatype module to make Jackson recognize Java 8 Date & Time API data types. A stackoverflow question and a blog post helped me to figure out what actually my problem was.
If LocalDateTime is not a problem, I think we should implementequals in User class.
This is how I implement my rest controllers test. Maybe It could help you.
I've this abstract class to encapsulate common tests functionalities regarding to the JSON mapping.
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
public abstract class RestControllerTest {
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
StandardCharsets.UTF_8);
private HttpMessageConverter messageConverter;
protected MediaType getContentType() {
return contentType;
}
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
messageConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
messageConverter);
}
#SneakyThrows
protected String toJson(Object data) {
val mockHttpOutputMessage = new MockHttpOutputMessage();
messageConverter.write(
data, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
You could use it in your test class like this
#WebMvcTest(UserController.class)
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest extends RestControllerTest {
#Autowired
MockMvc mockMvc;
#Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public#gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(toJson(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
I hope it works for you

Binding data to RequestBody (415 error)

I have the following in my Controller:
#Controller
public class GreetingController {
#PostMapping("/register")
public String registerUser(#RequestBody UserEntity request) throws ServletException, IOException {
System.out.println(request.getId());
return "register";
}
}
The UserEntity is:
#Entity
#Table(name = "users")
public class UserEntity {
private int id;
private String name;
private String email;
private String password;
I get the following error:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
Note that I have Jackson installed (from this question: Jackson Databind classpath issue).
Additionally, I am able to use the public String registerUser(HttpServletRequest request) fine, but when I try using #RequestBody it just gives me that error.
How would I get the #RequestBody to be the UserEntity?
You are using the header value "application/x-www-form-urlencoded;charset=UTF-8" in the request while you should use "application/json"

Testing Spring Data Rest endpoints using RestTemplate not working in SpringBoot 2.0.0.BUILD-SNAPSHOT

I am using Spring Data REST for exposing Spring Data JPA repositories as REST endpoints.
#Entity
#Table(name = "USERS")
public class User implements Serializable
{
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String email;
private String password;
}
public interface UserRepository extends JpaRepository<User, Integer>
{
}
While using SpringBoot 1.5.3.RELEASE I could test the endpoint http://localhost:8080/users using RestTemplate as follows:
#Test
public void testGetUsers()
{
RestTemplate restTemplate = restTemplate();
ResponseEntity<PagedResources<User>> responseEntity =
restTemplate.exchange(
"http://localhost:8080/users",
HttpMethod.GET,
null,
new ParameterizedTypeReference<PagedResources<User>>()
{},
Collections.emptyMap()
);
PagedResources<User> userResource = responseEntity.getBody();
Collection<User> users = userResource.getContent();
assertNotNull(users);
}
protected RestTemplate restTemplate()
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
converter.setObjectMapper(mapper);
return new RestTemplate(Collections.<HttpMessageConverter<?>> singletonList(converter));
}
But when I upgrade to SpringBoot 2.0.0.BUILD-SNAPSHOT, I am getting the following exception.
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [org.springframework.hateoas.PagedResources<com.myapp.entities.User>] and content type [application/hal+json;charset=UTF-8]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:117)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:946)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:930)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:684)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:650)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:598)
at com.myapp.demo.SpringbootDataRestDemoApplicationTest.testGetUsers(SpringbootDataRestDemoApplicationTest.java:53)
Any pointers to fix the issue?
PS:
I tried using #Autowired TestRestTemplate which internally uses configured ObjectMapper but same issue, working fine with 1.5.3.RELEASE but failing with 2.0.0.BUILD-SNAPSHOT.
#RunWith(SpringRunner.class)
#SpringBootTest(classes=DemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests
{
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testGetUsers()
{
ResponseEntity<PagedResources<User>> responseEntity =
restTemplate.exchange("/users",
HttpMethod.GET,
null,
new ParameterizedTypeReference<PagedResources<User>>() {}
);
assertTrue(responseEntity.getStatusCode() == HttpStatus.OK);
PagedResources<User> userResource = responseEntity.getBody();
Collection<User> users = userResource.getContent();
assertNotNull(users);
assertEquals(3, users.size());
}
}
With this remaining data ((links, page) is being populated in ResponseBody correctly but content is becoming as emptylist.

JAX-RS JAXB Jackson not using #XmlRootElement name

I am developing a restful application with JAX-RS and JAXB. I want to send following Entity as JSON to my client:
#XmlRootElement(name = "user")
#XmlAccessorType(XmlAccessType.FIELD)
public class UserDTO implements Serializable
{
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String firstname;
private String lastname;
// getter & setter
}
The method in my WebService is defined as follows:
#POST
#Path("users/{id}")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public UserAccountDTO login(#PathParam("id") Long id)
{
UserAccountDTO userAccount = loadUserAccount(id);
return userAccount;
}
First problem was, that the root node was not send via JSON. Therefore I have added following Class:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class SkedFlexContextResolver implements ContextResolver<ObjectMapper>
{
private ObjectMapper objectMapper;
public SkedFlexContextResolver() throws Exception
{
this.objectMapper = new ObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType)
{
return objectMapper;
}
}
Now, the root node is send with the data. In case of XML everything is fine (root node is equal to name of #XmlRootElement). See following XML response:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user>
<id>10</id>
<username>Admin</username>
<firstname>Administrator</firstname>
</user>
But in case of JSON the root node is the Classname of the POJO:
{
"UserAccountDTO":
{
"id": 10,
"username": "Admin",
"firstname": "Administrator",
"lastname": null
}
}
Why differs the output between XML and JSON? What do I need to change to get the specified name in the #XmlRootElement-Annotation
I had to register Jaxb module to the xml mapper like this, otherwise the #XmlRootElement(name = "myname") was igonerd.
JaxbAnnotationModule module = new JaxbAnnotationModule();
xmlMapper.registerModule(module);
Changing .configure(SerializationFeature.WRAP_ROOT_VALUE, true) to .configure(SerializationFeature.WRAP_ROOT_VALUE, false) should help.
According to javadoc:
Feature that can be enabled to make root value <..> wrapped within a single property JSON object, where key as the "root name"
Maybe it can help
#Configuration
public class SpringConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new Jaxb2RootElementHttpMessageConverter());
}
}

Categories