I need some assistance on the correct way to POST a json body with RestTemplate within a RestController. I am just not familiar enough as to what I am doing wrong. I have spent to much time looking at this and I am not getting the result I need. Hopefully the info I provide is clear. As of right now I am just trying to POST the JSON body to the below URL API
CONTROLLER
#RestController
#RequestMapping("/api/feedback")
public class FeedbackController {
private final RestTemplate restTemplate;
#Autowired
public FeedbackController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#RequestMapping(
method = RequestMethod.POST
)
public IncidentReport createIncident()
{
return restTemplate.exchange(
"URL_API/create",
HttpMethod.POST, null,
new ParameterizedTypeReference<IncidentReport>(){}
).getBody();
}
JSON POST BODY
{
"group": "my_group",
"short_description":"this is a test for the short description",
"contact_type":"Alert",
}
exchange is the low-level method, and the more specific ones are usually more friendly as long as they cover your use case. You're looking for a postForObject (postForEntity if you need the headers):
return restTemplate.postForObject(url, incidentObject, IncidentReport.class);
StackTrace would have been more helpful. I think you should instantiate RestTemplate in below manner.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
Then AutoWire it in your Controller class
#Autowired
private RestTemplate myRestTemplate;
Ensure Jackson library is present in the classpath. Try to use myRestTemplate.postForObject(). There is not much to this. If you are still getting an issue try to analyze stack trace, you will get some hint.
Since spring boot autoconfigures RestTemplate and ObjectMapper to serialize/deserialize in your #RestController endpoints you don't have to stick with RestTemplate at all and can use functionality as show in the following code snippet:
#RestController
#RequestMapping("/api/feedback")
public class FeedbackController {
#Autowired private FeedbackFacade feedbackFacade;
#PostMapping("/")
public ResponseEntity<IncidentReport> createIncidentReport(
#RequestBody IncidentReport incidentReport)
{
log.info("Create incident report {}", incidentReport);
var createdIncidentReport = feedbackFacade.create(incidentReport);
log.info("Created incident report: {}", createdIncidentReport);
return new ResponseEntity<>(createdIncidentReport, HttpStatus.CREATED)
}
}
Related
I'm trying to test a 'patch request' from my CompanyController that has a Map and Id as a parameters. I expected get a http status 200, but I get a http status 400.
Can someone explain to me what I'm doing wrong? thank you
CompanyController (some parts of the code are omitted):
#RestController
public class CompanyController {
#Autowired
private CompanyService companyService;
#PatchMapping("companies/{id}")
public ResponseEntity<CompanyDTO> patchUpdateCompany(#PathVariable Integer id,
#RequestBody Map<String, Object> updates) throws JsonMappingException {
Optional<CompanyDTO> optionalCompanyDTO = this.companyService.patchUpdateCompany(updates, id);
return ResponseEntity.ok(optionalCompanyDTO.get());
}
}
CompanyControllerTest (some parts of the code are omitted)
#WebMvcTest(CompanyController.class)
public class CompanyControllerTest {
#MockBean
private CompanyService companyService;
#Autowired
private MockMvc mockMvc;
private static List<CompanyDTO> companyDTOList;
#BeforeAll
public static void beforeAll(){
companyDTOList = new ArrayList<>();
CompanyDTO companyDTO1 = CompanyDTO.builder().id(1).name("xavi").build();
CompanyDTO companyDTO2 = CompanyDTO.builder().id(2).name("marteta").build();
companyDTOList.add(companyDTO1);
companyDTOList.add(companyDTO2);
}
#Test
void givenMapAndIdWhenPatchUpdateCompanyThenReturnHttpStatusOk() throws Exception {
Mockito.when(this.companyService.getCompanyById(1)).thenReturn(Optional.of(companyDTOList.get(0)));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("name", "xavi2");
this.mockMvc.perform(patch("/companies/1")
.contentType(MediaType.APPLICATION_JSON)
.params(requestParams))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", Matchers.is("xavi2")));
}
}
Your problem is here
this.mockMvc.perform(patch("/companies/1")
.contentType(MediaType.APPLICATION_JSON)
.params(requestParams)) <---------------------
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", Matchers.is("xavi2")));
}
You pass the data as request parameters. But in your API you have #RequestBody meaning it expects to get the data in http request body and not as request parameters.
This is why you face 400 error meaning Bad Request which is caused by Spring having matched the URL path and also the http method type but something additional which was in the signature of API method was not provided in your request.
So you should use the .content(requestParams) method to set the content you want in the body of the request which you will send
Related documentation
Otherwise your API should have used #RequestParam instead of #RequestBody to receive the input as request parameters as previously sent from test.
I am learning Spring Boot, I have managed to deploy an API on my computer which fetches data from Oracle and when I paste the link http://localhost:8080/myapi/ver1/table1data in browser it returns me the data. Below is my controller code :
#CrossOrigin(origins = "http://localhost:8080")
#RestController
#RequestMapping("/myapi/ver1")
public class Table1Controller {
#Autowired
private ITable1Repository table1Repository;
#GetMapping("/table1data")
public List<Table1Entity> getAllTable1Data() {
return table1Repository.findAll();
}
Now this scenario is working fine. I want to do another thing. There is an API https://services.odata.org/V3/Northwind/Northwind.svc/Customers which returns some customers data. Does spring boot provide any way so that I can re-host/re-deploy this API from my own controller such that instead of hitting this the above link in the browser, I should hit http://localhost:8080/myapi/ver1/table1data and it will return me the same customers data.
Yes the spring boot provides a way to hit an external URL from your app via a RestTemplate. Below is a sample implementation of getting the response as string or you can also use a data structure of desired choice depending on the response,
#RestController
#RequestMapping("/myapi/ver1")
public class Table1Controller {
#Autowired
private RestTemplate restTemplate;
#GetMapping("/table1data")
public String getFromUrl() throws JsonProcessingException {
return restTemplate.getForObject("https://services.odata.org/V3/Northwind/Northwind.svc/Customers",
String.class);
}
}
You can create a config class to define the Bean for the rest controller. Below is the snippet,
#Configuration
public class ApplicationConfig{
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
You can use RestTemplate for third party API call and return the response from your API
final String uri = "https://services.odata.org/V3/Northwind/Northwind.svc/Customers";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
This website has some nice examples for using spring's RestTemplate
Create a #Bean of RestTemplate
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
By using the above RestTemplate you can fetch the data from ur own localhost
String url = "https://services.odata.org/V3/Northwind/Northwind.svc/Customers";
restTemplate.getForObject(url,String.class);
I need to mock a service. i m getting null in ResponseEntity<?> resp while mocking the class.
Method which need to mock:
public List<Expression> getExpression(String expressView, Localdate date) {
List<Expression> =new ArrayList<>();
Map<String, Object> uri = new HashMap<>();
UriComponenetsBuilder build =
UriComponentsBuilder.fromHttpUrl("someUrl" + "/" + expressView);
build.queryParam(someParameter, someParameter);
build.queryParam(someParameter, someParameter);
build.queryParam(someParameter, someParameter);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
RestClient client = new RestClient(
build.build().encode.toUriString, HttpMethod.GET.Uri, header
);
ResponseEntity<?> resp = restC.SomeMethod(client);
if (resp = !null) {
//it goes to these line
}
}
In my mock method:
when(restC.SomeMethod(client)).thenReturn(resp);
So above method call a service get some data get the value of expressView and save as list. when i mocked the method when(restC.SomeMethod(client)).thenReturn(resp); it hit the URL but the value i m getting as response resp is null .
So here i m getting the resp value as null. I checked the URL(someUrl) in postman its returning the value.
How to mock the ResponseEntity<?>?
Thanks.
First, create a ResponseEntity object:
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<?> responseEntity = new ResponseEntity<>(
"some response body",
header,
HttpStatus.OK
);
Then build a mock to return the responseEntity object:
when(restC.SomeMethod(client)).thenReturn(responseEntity);
Point to pay attention:
Avoid to use ResponseEntity inside #Service class. You should use ResponseEntity in #RestController class.
And you can Inject your #Service class using #Autowired annotation, like:
#RestController
public class YourControllerClass {
#Autowired
private YourServiceClass yourServiceClass;
Or using constructor, like:
#RestController
public class YourControllerClass {
private YourServiceClass yourServiceClass;
public YourControllerClass(YourServiceClass yourServiceClass) {
this.yourServiceClass= yourServiceClass;
}
So:
#Service class will handle business or data objects and #RestController class will handle Response and Request objects. Thus we have Single Responsibility principle.
Some nice links:
Spring MVC - Using RequestEntity and ResponseEntity
How do I mock a REST template
Building REST services with Spring
Understanding the Single Responsibility Principle
Hope this helps!
I am using Spring Boot 1.5.2.RELEASE and not able to incorporate JSR - 349 ( bean validation 1.1 ) for #RequestParam & #PathVariable at method itself.
For POST requests, if method parameter is a Java POJO then annotating that parameter with #Valid is working fine but annotating #RequestParam & #PathVariable with something like #NotEmpty, #Email not working.
I have annotated controller class with Spring's #Validated
There are lots of questions on SO and I have commented on this answer that its not working for me.
Spring Boot includes - validation-api-1.1.0.Final.jar and hibernate-validator-5.3.4.Final.jar .
Am I missing anything?
Example code ,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(#Email #NotEmpty #RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
response.setResponse(Constants.SUCCESS);
response.setMessage("testValidated");
logger.error("Validator Not called");
return response;
}
Below handler is never called when I send empty values or not well formed email address for email & control always goes to with in testValidated method.
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBean handle(ConstraintViolationException exception) {
StringBuilder messages = new StringBuilder();
ResponseBean response = new ResponseBean();
exception.getConstraintViolations().forEach(entry -> messages.append(entry.getMessage() + "\n"));
response.setResponse(Constants.FAILURE);
response.setErrorcode(Constants.ERROR_CODE_BAD_REQUEST);
response.setMessage(messages.toString());
return response;
}
ResponseBean<T> is my application specific class.
I had asked the question after more than two days of unsuccessful hit & trial. Lots of confusing answers are out there because of confusions around Spring Validations and JSR validations, how Spring invokes JSR validators, changes in JSR standards & types of validations supported.
Finally, this article helped a lot.
I solved problem in two steps,
1.Added following beans to my Configuration - without these beans , nothing works.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor mvProcessor = new MethodValidationPostProcessor();
mvProcessor.setValidator(validator());
return mvProcessor;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
return validator;
}
2.Placed Spring's #Validated annotation on my controller like below,
#RestController
#RequestMapping("/...")
#Validated
public class MyRestController {
}
Validated is - org.springframework.validation.annotation.Validated
This set up doesn't affected #Valid annotations for #RequestBody validations in same controller and those continued to work as those were.
So now, I can trigger validations like below for methods in MyRestController class,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated" , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(
#Email(message="email RequestParam is not a valid email address")
#NotEmpty(message="email RequestParam is empty")
#RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
....
return response;
}
I had to add another handler in exception handler for exception - ConstraintViolationException though since #Validated throws this exception while #Valid throws MethodArgumentNotValidException
Spring #Validated #Controller did not mapped when adding #Validated. Removal of any inheritance from controller itself did help. Otherwise Sabir Khan's answer worked and did help.
I'm developing an application in Java/Spring MVC and have no problem with testing my GET methods. The problem occur then I try to test the POST using #RequestBody.
The error:
HTTP 415 The server refused this request because the request entity is in a format not supported by the requested resource for the requested method.
I created a simple test to show my problem:
#RestController
#RequestMapping("/test")
public class ConcreteTestController implements TestController {
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
#Override
public void add(#RequestBody Dummy dummy) {
System.out.println(dummy);
}
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(value = HttpStatus.OK)
#Override
public Dummy get() {
Dummy dummy = new Dummy();
dummy.setName("apa");
return dummy;
}
}
The Dummy class is very simple:
public class Dummy {
private String name;
public Dummy() {}
// Omitted setters and getters.
}
The jsonresponse from the GET looks like this:
{"name":"apa"}
I'm starting the IntelliJ REST client and using the json above as request body. I've tried using both application/json and / under Accept in the header with no difference in result.
Any idea what could cause this? I'm stuck and would appreciate help.
By default you have to add Content-Type manually in the REST client in IntelliJ. I had forgotten to do so and to set it to application/json. After having done so it is working fine.