In my Spring boot application, I'm sending the POST data with the following (e.g) params:
data: {
'title': 'title',
'tags': [ 'one', 'two' ],
'latitude': 20,
'longitude': 20,
'files': [ ], // this comes from a file input and shall be handled as multipart file
}
In my #Controller I have:
#RequestMapping(
value = "/new/upload", method = RequestMethod.POST,
produces = BaseController.MIME_JSON, consumes = BaseController.MIME_JSON
)
public #ResponseBody HttpResponse performSpotUpload(final SpotDTO spot) {
// ...
}
where SpotDTO is a non-POJO class with all getters and setters.
public class SpotDTO implements DataTransferObject {
#JsonProperty("title")
private String title;
#JsonProperty("tags")
private String[] tags;
#JsonProperty("latitude")
private double latitude;
#JsonProperty("longitude")
private double longitude;
#JsonProperty("files")
private MultipartFile[] multipartFiles;
// all getters and setters
}
Unfortunately all fields are null when I receive the request. Spring is unable to map the parameters to my DTO object.
I guess I'm missing some configuration but I do not know which one.
Other similar questions are resolved by just setting fields accessors on the DTO class. This does not work for me.
Also I have noticed that if I specify each parameter in the method:
#RequestParam("title") final String title,
the method is not even reached by the request. I can see the incoming request in a LoggingInterceptor preHandle method, but nothing in postHandle. A 404 response is sent back.
I think you're just missing the #RequestBody annotation on your parameter:
#RequestMapping(
value = "/new/upload", method = RequestMethod.POST,
produces = BaseController.MIME_JSON, consumes = BaseController.MIME_JSON
)
public #ResponseBody HttpResponse performSpotUpload(#RequestBody final SpotDTO spot) {
// ...
}
You should add #RequestBody annotation before SpotDTO spot. i.e
#RequestBody SpotDTO spot
public #ResponseBody HttpResponse performSpotUpload(#RequestBody SpotDTO spot) {
// ...
}
Related
I'm writing a small demo application with Spring Boot and Spring Data Rest. I have the following model and corresponding repository:
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String jobTitle;
public Employee() {
}
... // getters and setters
}
#RepositoryRestResource(collectionResourceRel = "employees", path = "employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
#RestResource(path = "by-last-name", rel = "by-last-name")
Page<Employee> findByLastNameIgnoreCase(Pageable pageable, #Param("lastName") String lastName);
#RestResource(path = "by-job-title", rel = "by-job-title")
Page<Employee> findByJobTitleIgnoreCase(Pageable pageable, #Param("jobTitle") String jobTitle);
}
If I make the following request through Postman:
POST localhost:8080/employees
{"firstName":"Test","lastName":"McTest","jobTitle":"Tester"}
I receive a full response body with my newly created entity:
{
"firstName": "Test",
"lastName": "McTest",
"jobTitle": "Tester",
"_links": {
"self": {
"href": "http://localhost:8080/employees/120"
},
"employee": {
"href": "http://localhost:8080/employees/120"
}
}
}
However, when I make the same request through my tests as shown below, I get an empty response body:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
#AutoConfigureMockMvc
public class EmployeeIntegrationTest {
#Autowired
private MockMvc mvc;
#Autowired
ObjectMapper objectMapper;
#Test
public void testAddEmployee() throws Exception {
Employee employee = new Employee();
employee.setFirstName("Test");
employee.setLastName("McTest");
employee.setJobTitle("Tester");
MockHttpServletRequestBuilder requestBuilder = post("/employees")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee));
mvc
.perform(requestBuilder)
.andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName", Matchers.is("Test"))); // Fails, because content is empty.
}
}
For what it's worth, if I then perform a GET /employees in my test, I do in fact see the entity in the response body so I know it's being created.
My expectation is that I would get the same response through either method, alas that's not the case currently, and it seems as though POST requests with MockMvc aren't returning a body. Am I potentially missing a configuration setting somewhere?
Setting the HTTP Accept header in the request should cause a response body to be returned:
MockHttpServletRequestBuilder requestBuilder = post("/employees")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee));
By default Spring Data REST will only return a response body if the HTTP Accept header is specified. This behavior is documented in the Spring Data REST Reference Guide:
The POST method creates a new entity from the given request body. By default, whether the response contains a body is controlled by the Accept header sent with the request. If one is sent, a response body is created. If not, the response body is empty and a representation of the resource created can be obtained by following link contained in the Location response header. This behavior can be overridden by configuring RepositoryRestConfiguration.setReturnBodyOnCreate(…) accordingly.
My guess is that your Postman request is setting the "Accept" header, which is why you hadn't seen this behavior in Postman.
I was able to solve the issue by explicitly setting
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setReturnBodyForPutAndPost(true);
}
inside of a #Configuration class that implements RepositoryRestConfigurer
My guess would be that this is set implicitly for main code, but not for tests.
I want to send an object to the controller that has several lists with files and several fields with plain text.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I am sending data using postman. However, he throws me away
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundarywY7ByvgonAjDoaCT;charset=UTF-8' not supported
What should I set the Content-type for this controller so that I can send an object that has fields of plain text and lists with files?
If I set the header in the header
Content-type: multipart/form-data; charset=utf-8
it throws me in the console
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
As said dknight #RequestBody means use of JSON or XML data with maps your DTO bean.
In case of MultipartFile you can't use JSON data so you can't use #RequestBody.
Try with #ModelAttribute annotation.
Working sample :
#PostMapping("/promoters")
#Timed
public ResponseEntity<PromoterDTO> createPromoter(#ModelAttribute PromoterDTO promoterDTO) throws URISyntaxException { ... }
With PromoterDTO like this :
public class PromoterDTO implements Serializable {
private Long id;
private String name;
private String address;
private MultipartFile logo;
}
In Postman, you need to set the body to be of type raw, and from the drop down you can select JSON, I had a similar issue, this fixed my issue.
Instead of #RequestBody, use #RequestParam!
use #ModelAttribute instead of #ResponseBody as this takes up data in key value pairs and the later is used for an object like, json.
While hitting the api simply pass the multipart type and json key value pairs of the object. It works fine!
stack overflow question on this
Instead of #RequestBody, use #ModelAttribute like,
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#ModelAttribute #Valid final ContributionNew<Photo> contribution
) {
import org.springframework.web.bind.annotation.ModelAttribute;
Use #ModelAttribute instead of #RequestBody. It worked for me.
produces = { "application/json" } has to written in the controller instead of consumes = { "application/json" }
Hi Folks simply change : #RequestBody to #ModelAttribute
public ResponseEntity<DTO> exemple(#ModelAttribute Dto dto) throws TechnicalException
Happy coding.
Here's a full code sample written in Kotlin using Spring Boot 2.1.7
Example uses a ProfileRepository that would be something you implement of course.
Kotlin is nice, because the data class implements serializable already.
Take note, that you have to use var not val for the model objects properties otherwise the field values will be null in the log message.
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
#RestController
class ExampleController(private val myProfileRepository: ProfileRepository) {
#PostMapping("/api/uploadFile")
fun createProvider(#ModelAttribute request: CreateProfileRequest): Provider {
println("received create request with photo: ${request.photo} for the following person ${request.name}")
return myProfileRepository.save(Provider(name = request.name!!))
}
}
data class CreateProfileRequest(
var name: String? = null,
var photo: MultipartFile? = null
)
I have method in Spring RestController:
#RequestMapping(value = "/mapping", method = { RequestMethod.POST }, consumes = "multipart/form-data")
public ResponseEntity fileQuery(MyRequest request) {}
And a class, MyRequest:
public class MyRequest{
private String url;
private String method;
private String body;
private MultipartFile file;
// I have tried with MultiValueMap and no result
private Map<String,String> formData;
//getters and setters here
}
I want to create a method that accepts multipart / form-data requests with the following known arguments:
URL,
Method,
Body,
File,
Other String:String type arguments.
My solution is not working. Map is null in this case. How can I achieve something like this?
For better understanding I attached a screenshot from postman:
I am building web service with spring and come across with following problem.
There is a post service as follow.
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
//post data
//return response
}
public class ADto{
private String firstParam;
private String secondParam;
// getter setter
}
So, my question is how can I know whether value of firstParam and secondParam is provided in request body or not.
RequestBody: { paramFirst: null, paramSecond: null}
Edit1:
Sorry for incomplete question:
For RequestBody: {paramFirst: first Value} and for above request value of paramSecond will be null.
So, how would I know whether paramSecond is included in request or not.
Edit2:
I don't want to validate. What I want to know is whether
request contains a particular parameter or not.
Because there are two different cases, one is value of a parameter is given null and other is paramter is not included in request.
You could use the #Valid annotation like so (pseudo code, didn't test it):
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#Valid #RequestBody ADto aDto){
// MethodArgumentNotValidException will be thrown if validation fails.
}
You'll need an exception handler to handle the validation error.
#ExceptionHandler
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public Error handleException(MethodArgumentNotValidException exception) {
//do something with the validation message: exception.getBindingResult()
}
And your class.
public class ADto{
#NotNull(message = "First parameter can not be null")
private String firstParam;
#NotNull(message = "Second parameter can not be null")
private String secondParam;
// getter setter
}
Try using Hibernate Validator (http://hibernate.org/validator/), it's really easy to integrate it with Spring.
That way, you'll need to annotate your Dto to enforce validation of required params and then call validate.
public class ADto{
#NotNull
private String firstParam;
#NotNull
private String secondParam;
// getter setter
}
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
validator.validate(aDto)
//post data
//return response
}
You could make firstParam and secondParam type Optional:
ADto class
public class ADto {
private Optional<String> firstParam;
private Optional<String> secondParam;
// getter setter
}
postSomething method
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto) {
if (Optional.ofNullable(aDto.getFirstParam()).isPresent()) {
// firstParam is provided in the request
} else {
// firstParam is not provided in the request
}
if (Optional.ofNullable(aDto.getSecondParam()).isPresent()) {
// secondtParam is provided in the request
} else {
// secondtParam is not provided in the request
}
}
Note that isPresent() will return false if and only if firstParam (as well as for secondParam) is not present in the request. Otherwise, even if the value is set to null, it will return true.
The JSONObject is always coming as empty for the method below.
#RequestMapping(value = "/package/{id}", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public SPackage updatePackage(#PathVariable String id, #RequestBody JSONObject
sPackage) {
}
and my ajax is like this. I am ways getting the object as empty map on the server side
var jsonObject= {"customerName":$('#customerName').val()}
var jsonData = JSON.stringify(jsonObject);
$.ajax({
type: "PUT",
url: "http://localhost:8081/someproj/package/" + $('#id').val(),
dataType: "json",
data: jsonData,
async: false,
contentType: "application/json; charset=utf-8",
beforeSend : function() {
openModal();
},
success: function(data) {
closeModal();
$('#success').show();
console.log(data);
}
});
I guess spring doesn't know to convert your json to JSONObject, the best thing would be to accept a POJO object which has similar structure to your json,
#RequestMapping(value = "/package/{id}", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public SPackage updatePackage(#PathVariable String id, #RequestBody YourJsonPOJO
sPackage) {
}
Are you sure there're no exceptions occurring in your Spring code. When converting from JSON to custom object in Spring, you need to specify a custom class that has same fields & format as the JSON coming in. Otherwise, Spring doesn't know who to convert HTTP POST data into a Java object.
In your case, you could do define a POJO like this:
public class MyRequestObj {
private String customerName;
// add more fields for any other keys from JSON
}
And put this in your Controller class:
#RequestMapping(value = "/package/{id}", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public SPackage updatePackage(#PathVariable String id, #RequestBody MyRequestObj
myRequestObj) {
String customerName = myRequestObj.getCustomerName();
}
Of course, if you only want to pass in the customer name as a String to your Controller, then you could also pass it as query string (append ?customerName=someCustomer) and you can retrieve it in Spring as:
#RequestMapping(value = "/package/{id}", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public SPackage updatePackage(#PathVariable String id, #RequestParam("customerName") String customerName) {
}
You can use this workaround:
#RequestBody Map<String, String> json
That way you can continue using Jackson HttpMessageConverter and work with custom objects in payload.
You can check extended explanaition why this happens at answer here
#RequestBody gives empty JsonObject when making a POST Request