SpringBoot 2.6.7
The annotation #PostMapping allows multiple values for the consumes attribute to specify different media types / content-types, but it seems that it is impossible to implement a single method that actually allows multiple content/media types.
In the sample code below, separate methods enroll1 and enroll2 uses the same POJO class to capture the request ( Ping class in the sample below ) from either application/x-www-form-urlencoded or application/json, respectively.
Note that the application/x-www-form-urlencoded is also mapped to a POJO, unlike similar questions in stackoverflow where they are mapped to a java.util.Map.
NOTE: Yes, I looked at Supporting application/json and application/x-www-form-urlencoded simultaneously from Spring's rest controller ... but that one uses MultiValueMap for the parameter while I am using a POJO for both media types.
package org.example.demo;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RestController
#RequestMapping("test")
public class SampleController {
// #PostMapping(value = "ping", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE})
// public #ResponseBody Pong enroll(#Valid #ModelAttribute #RequestBody Ping ping) {
// Pong pong = new Pong();
// pong.setEcho(ping.getText());
// pong.setSuccess(true);
// return pong;
// }
#PostMapping(value = "ping", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public #ResponseBody Pong enroll1(#Valid Ping ping) {
return doBoth(ping);
}
#PostMapping(value = "ping", consumes = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Pong enroll2(#Valid #RequestBody Ping ping) {
return doBoth(ping);
}
private Pong doBoth(Ping ping) {
Pong pong = new Pong();
pong.setEcho(ping.getText());
pong.setSuccess(true);
return pong;
}
}
Notice that the method method accepting application/x-www-form-urlencoded does not need to have the #ModelAttribute annotation on the ping parameter.
package org.example.demo;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
#Getter
#Setter
public class Ping {
#NotNull
private String text;
}
package org.example.demo;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class Pong {
private String echo;
private boolean success;
}
While the above works OK, what I would have wanted is for that commented out code to work ( and commenting out / removing the other two methods enroll1() and enroll2() above ):
#PostMapping(value = "ping", consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE,
MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Pong enroll(#Valid #ModelAttribute #RequestBody Ping ping) {
Pong pong = new Pong();
pong.setEcho(ping.getText());
pong.setSuccess(true);
return pong;
}
In real life, this method and others similar like it have other annotations ( like Swagger / OpenAPI plus other custom annotations ) that would be best to have in just one method instead of being repeated in two methods.
But you cannot annotate the method parameter with both #ModelAttribute and #RequestBody at the same time. If you do, you get :
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/x-www-form-urlencoded' -d text=Hello
{"echo":"Hello","success":true}
$
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/json' -d '{"text":"Hello"}'
{"timestamp":1651307540135,"status":400,"error":"Bad Request","path":"/test/ping"}
You get the above results as well ( only application/x-www-form-urlencoded works ) if you:
Remove the #RequestBody annotation but keep the #ModelAttribute annotation, OR
Remove both #RequestBody and #ModelAttribute annotations.
If you remove the #ModelAttribute annotation and leave the #RequestBody annotation on the parameter, you get the results reversed ( but get an HTTP 415 instead of an HTTP 400 ) .The log says Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported:
#PostMapping(value = "ping", consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE,
MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Pong enroll(#Valid #RequestBody Ping ping) {
Pong pong = new Pong();
pong.setEcho(ping.getText());
pong.setSuccess(true);
return pong;
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/x-www-form-urlencoded' -d text=Hello
{"timestamp":1651307931120,"status":415,"error":"Unsupported Media Type","path":"/test/ping"}
$
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/json' -d '{"text":"Hello"}'
{"echo":"Hello","success":true}
Does anyone have a working implementation where a single method have multiple values for the consumes attribute of the #PostMapping annotation, where the request is mapped to a POJO ?
I would like to think that there are, otherwise, it would make no sense to allow it as an array.
I'm trying to use spring webflux to build a reactive long-polling API service, I have implemented a service that produces a data stream from my data resource(Kafka). And the caller of this long-polling API is supposed to get/wait for the first data with the requested key. But somehow the API is not returning the data if data received after the application gets the data. Am I using the correct reactor methods? Thanks so much!
My service:
#Service
public class KafkaService {
// the processor is a in-memory cache that keeps tracking of the old messages
#Autowired private KafkaMessageProcessor kafkaMessageProcessor;
#Autowired private ApplicationEventService applicationEventService;
private Flux<KafkaMessage> eventFlux;
#PostConstruct
public void setEventFlux() {
this.eventFlux = Flux.create(applicationEventService).share();
}
public Flux<KafkaMessage> listenToKeyFlux(String key) {
return kafkaMessageProcessor
.getAll()
.concatWith(this.eventFlux.share())
.filter(x -> x.getKey().equals(key));
}
}
My handler:
#RestController
#RequestMapping("/longpolling")
public class LongPollingController {
private static final String MSG_PREFIX = "key_";
#Autowired private KafkaService kafkaService;
#GetMapping("/message/{key}")
#CrossOrigin(allowedHeaders = "*", origins = "*")
public CompletableFuture<KafkaMessage> getMessage(#PathVariable String key) {
return kafkaService.listenToKeyFlux(MSG_PREFIX + key).shareNext().toFuture();
}
}
I'm trying to test an application with the following binding configured:
spring:
cloud:
stream:
bindings:
accountSource:
destination: account
producer:
useNativeEncoding: true
kafka:
binder:
brokers: ${KAFKA_BOOTSTRAP_ADDRESSES}
producer-properties:
schema.registry.url: ${KAFKA_SCHEMA_REGISTRY_URL}
value.subject.name.strategy: io.confluent.kafka.serializers.subject.RecordNameStrategy
bindings:
accountSource:
producer:
configuration:
key:
serializer: org.apache.kafka.common.serialization.StringSerializer
value:
serializer: io.confluent.kafka.streams.serdes.avro.SpecificAvroSerializer
When running the application normally, AbstractMessageChannel.interceptorList is empty and sending message to broker works fine.
When running the test (with spring-cloud-stream-test-support binder), AbstractMessageChannel.interceptorList gets populated with MessageConverterConfigurer and message is being converted using content-type serialization mechanisms (Avro object is converted to JSON). This is the test code:
#RunWith(SpringRunner.class)
public class AccountServiceImplTest {
#Autowired
private AccountService accountService;
#Autowired
private MessageCollector messageCollector;
#Autowired
private MessageChannel accountSource;
#Test
public void create() {
// Simplified code
AccountCreationRequest accountCreationRequest = AccountCreationRequest.builder().company(company).subscription(subscription).user(user).build();
accountCreationRequest = accountService.create(accountCreationRequest);
Message<?> message = messageCollector.forChannel(accountSource).poll();
// execute asserts on message
}
#TestConfiguration
#ComponentScan(basePackageClasses = TestSupportBinderAutoConfiguration.class)
static protected class AccountServiceImplTestConfiguration {
#EnableBinding({KafkaConfig.AccountBinding.class})
public interface AccountBinding {
#Output("accountSource")
MessageChannel accountSource();
}
}
Am I missing something to disable spring-cloud-stream serialization mechanisms?
Don't use the test binder; use the Kafka binder with an embedded kafka broker instead.
I uploaded a file to Corda node and got the following hex value as string back:
854AAE9BE6607CE0B15A70EEBEF19C553557103FB051413F2AA35E70F5B44313
Now I need to pass this as a secureHash parameter to transaction builder:
txBuilder.addAttachment(??).
How to build secure hash from the hex string result obtained from file upload as input parameter to addAttachment..?
SecureHash has toString() function that returns hash as hex string above. I need to create secure hash using the hex string above.
Thanks.
Tried the following update to code:
Added attachId parameter to IOUFlow in Hello World tutorial. Added attachment as txBuilder.addAttachment(attachId). See code below:
class IOUFlow(val iouValue: Int,
val otherParty: Party, val attachId: SecureHash.SHA256) :
FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of
the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
#Suspendable
override fun call() {
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val txBuilder = TransactionBuilder(notary = notary)
txBuilder.addAttachment(attachId)
....
}
Uploaded attachment to server and got following hash:
C5C84DADD15B2359EBDF0DFC6CCCAA48A0DBA3A04EFD8F03EB117186CC0B2D08
Started flow with following shell command:
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US”,
attachId: C5C84DADD15B2359EBDF0DFC6CCCAA48A0DBA3A04EFD8F03EB117186CC0B2D08
Shell just responds with '>' and nothing happens. Have to use CTRL-C to get back shell prompt.
Use SecureHash.parse() to convert the string to a SecureHash.
When sending JSON requests to the server, I'm often greeted by this message:
The request sent by the client was syntactically incorrect ().
Usually it's an incorrect attribute that was passed that the controller didn't expect, since the object the JSON maps to doesn't contain it.
Finding the parameter is needlessly time consuming - is there a way to get more information, perhaps even a stack trace of the exception? I've tried running in debug mode and I'm using Jackson as my JSON (de)serialiser.
To get more info / stack trace turn on debug logging for spring web in log4j.properties
log4j.logger.org.springframework.web=debug
If the data that your consuming is from an external api and if you want to shield your controller from unnecessary elements/properties that you dont need
you can use below annotation on POJO class
#JsonIgnoreProperties(ignoreUnknown = true)
or you could set it globally
//jackson 2.0
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Old case, my be irrelevant by now, but for others with similar trouble I'll add this entry.
Check that you have double quotes, not single quotes. Also try escaping them.
I used the app curl for my test json requests.
My JSON was like this
{ 'name' : 'somedata', 'town':'some town' }
and it just blew up in my eyes.
"The request sent by the client was syntactically incorrect" was the general error. So I changed it from single quotes to double quotes, no go. I then tried having a backslash infront of the single quotes, still no go. Then changed it to backslash infront of the double quotes and presto!
curl -i -H "Accept: application/json" -H "Content-Type: application/json" \
http://localhost:8077/dra/dump.json \
-X PUT -d "{\"name\" : \"My Name\", \"town\":\"My Town\"}"
I was facing the same error since couple of days, finally enabled the debug logging as mentioned by #Farm and was able to see the cause of this issue.
I had a Person Object being posted in json from jsp to spring controller as shown below:-
#RequestMapping(value = "/get", method = RequestMethod.POST)
public #ResponseBody
Person getPerson(#RequestBody Person person) {
// System.out.println("inside get::::::::");
System.out.println("city:=" + person.getResidenceAddress().getCity()+" "
+ "name:= " + person.getFullName().getFirstName());
// Person prsn = personService.getPerson(person);
/*
* Person prsn = new Person(); prsn.setId(1); ResponseDecorator rd = new
* ResponseDecorator(prsn, true);
*/return person;
}
// Person Object
class Person{
private Name fullName;
private Address residenceAddress;
private Address mailingAddress;
private Gender gender;
private MaritalStatus maritalStatus;
private Integer creditRating;
}
//Address Object
public class Address {
private String streetNo;
private String streetName;
private String suburb;
private String city;
private String state;
private String country;
private Integer pinCode;
private AddressType addressType;
}
json post from jsp:-
//create JSON
var json = {
"id": id,
"fullName":{"firstName":firstName,"middleName":middleName,"surName": surName},
"residenceAddress":{ "streetNo": streetNo,
"streetName": streetName,
"suburb": suburb,
"city": city,
"state": state,
"country": country,
"pinCode": pincode
}
};
var dataString = JSON.stringify(json);
//alert(dataString);
$.ajax({
headers: {
Accept : "text/plain; charset=utf-8, application/json"},
url: "/myservice/get",
type: 'POST',
dataType: 'json',
//data : "{ }",
data: dataString,
contentType: 'application/json',
mimeType: 'application/json',
success: function(data) {
alert(data);
},
error:function(data,status,er) {
alert("error: "+data+" status: "+status+" er:"+er);
}
});
The error on submit was 400 Bad request and there was no other clue as to why it was failing.
On enabling the debug mode on the server, I could see the below error stack trace:-
**00:53:42,294 DEBUG RequestResponseBodyMethodProcessor:117 - Reading [com.foo.assignment.model.Person] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#1813fb]
00:53:42,310 DEBUG ServletInvocableHandlerMethod:159 - Error resolving argument [0] [type=com.foo.assignment.model.Person]
HandlerMethod details:
Controller [com.foo.assignment.controller.PersonController]
Method [public com.foo.assignment.model.Person com.foo.assignment.controller.PersonController.getPerson(com.foo.assignment.model.Person)]
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: No suitable constructor found for type [simple type, class com.foo.assignment.model.Address]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: org.apache.catalina.connector.CoyoteInputStream#f264df; line: 1, column: 98] (through reference chain: com.foo.assignment.model.Person["residenceAddress"]); nested exception is org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class com.foo.assignment.model.Address]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: org.apache.catalina.connector.CoyoteInputStream#f264df; line: 1, column: 98] (through reference chain: com.foo.assignment.model.Person["residenceAddress"])
at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:127)
at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:153)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:120)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:91)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:71)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:74)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:155)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:900)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:722)
Caused by: org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class com.foo.assignment.model.Address]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: org.apache.catalina.connector.CoyoteInputStream#f264df; line: 1, column: 98] (through reference chain: com.foo.assignment.model.Person["residenceAddress"])
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObjectUsingNonDefault(BeanDeserializer.java:746)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:683)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2732)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1923)
at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:124)
... 37 more
00:53:42,310 DEBUG ExceptionHandlerException**
Adding a default constructor in the Address object solved the error. And using Google add-on Postman(plugin https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) really helped in testing my changes pretty fast.
The issue is with your POJO, keep the server log level to Debug level and check the error. This will be mostly related to some field and field-type mismatch.
This error can also occur when using nested classes
e.g.
class One {
some code..
public class NestedOne {
private attr;
public NestedOne() {
// default constructor
}
}
}
and then having
#RequestMapping(value = "/something")
public String someControllerMethod(#RequestBody One.NestedOne nested) {
...
}
jackson then doesn't recognize NestedOne() as default constructor and searches for One.NestedOne() constructor.
In my case I had passed String value into a Integer type field.
I have solved this by creating another field of type String in the entity class and in the database Table and pass the value into this field.