Json multiparameters by using spring mvc and jackson - json

i created a small json example... all worked quite fine, i'am just not able to send complex multiparameter to a service-method. With just one parameter it works... at the moment i'm not sure that my json call is wrong or something in my source/config is wrong.
First of all my lib-Dependencies:
org.springframework.spring-webmvc 4.0.6.RELEASE
org.springframework.spring-context 4.0.0.RELEASE
com.fasterxml.jackson.core.jackson-databind 2.2.3
Here is my spring-config:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Configure to plugin JSON as request and response in method handler -->
<annotation-driven>
<message-converters>
<beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<beans:property name="objectMapper">
<beans:bean class="com.test.webservice.EnvironmentObjectMapper" />
</beans:property>
</beans:bean>
</message-converters>
</annotation-driven>
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by #Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- only components from this package can be wired by spring -->
<context:component-scan base-package="com.test.*" />
</beans:beans>
Here is the used ObjectMapper:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class EnvironmentObjectMapper extends ObjectMapper{
/**
*
*/
private static final long serialVersionUID = -4831947927759735004L;
public EnvironmentObjectMapper() {
this.setVisibilityChecker(
getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withSetterVisibility(JsonAutoDetect.Visibility.NONE).
withCreatorVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
}
Here is a Simple-Test Code:
#RequestMapping(value = "/rest/test1", method = RequestMethod.POST)
public void test(#RequestBody String name, #RequestBody String name2){
System.out.println("Test - name: "+ name + " name2: " + name2);
}
To call this method i use the Firefox RESTClient. Header is set to Content-Type:application/json
Body is set to: {"name":"karl", "name2:"fritz"}
I get this response:
Status Code: 400 Bad Request
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 1379
Content-Type: text/html;charset=ISO-8859-1
Server: Jetty(7.2.0.v20101020)
and at the server this:
15:32:30.588 [qtp1879112077-17] DEBUG o.s.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing POST request for [/rest/test1]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /rest/test1
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Returning handler method [public void com.test.webservice.JustAServiceController.test(java.lang.String,java.lang.String)]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'justAServiceController'
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Reading [class java.lang.String] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#3d467064]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public void com.test.webservice.JustAServiceController.test(java.lang.String,java.lang.String)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public void com.test.webservice.JustAServiceController.test(java.lang.String,java.lang.String)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public void com.test.webservice.JustAServiceController.test(java.lang.String,java.lang.String)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: org.eclipse.jetty.server.HttpInput#309efc1f; line: 1, column: 1]
15:32:30.588 [qtp1879112077-17] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'appServlet': assuming HandlerAdapter completed request handling
15:32:30.588 [qtp1879112077-17] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
I have tried many different variants to set the parameters into the request body. But nothing worked. Anybody an idea what i did wrong? Thank you very much in advance.

You cannot have multiple #RequestBody annotations. #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object.
You should use different approach such as
Introduce a wrapper object that encapsulates your strings, and change your signature. e.g
Wrapper Object
class MyWrapper{
String name, name2;
//ToDo: Create constructors
String getname(){
return name;
}
String getName2(){
return name2;
}
//ToDo: create Setters
}
Your test code
#RequestMapping(value = "/rest/test1", method = RequestMethod.POST)
public void test(#RequestBody MyWrapper wrapper){
System.out.println("Test - name: "+ wrapper.getName() + " name2: " + wrapper.getName2());
}
Other options would be to pass the data with the URI path, or define custom annotations (which is more complex) see Passing multiple variables in #RequestBody to a Spring MVC controller using Ajax

Related

How to program to create a map schema using springdoc

I want to get a map schema in components, e.g.
components:
schemas:
Response:
type: object
additionalProperties:
type: string
Here is my code:
#Bean
public OpenAPI customOpenAPI() {
MapSchema mySchema = new MapSchema();
mySchema.setAdditionalProperties(new StringSchema());
return new OpenAPI().components(new Components()
.addSchemas("Response", mySchema)
);
}
But it will raise an exception:
{"timestamp":"2022-11-24 14:23:54.686","component":"","level":"WARN","thread-id":"http-nio-8080-exec-1","logger":"org.springdoc.core.OpenAPIService","message":"Json Processing Exception occurred: Cannot deserialize value of type `java.lang.Boolean` from Object value (token `JsonToken.START_OBJECT`)
at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: io.swagger.v3.oas.models.OpenAPI["components"]->io.swagger.v3.oas.models.Components["schemas"]->java.util.LinkedHashMap["Response"])"}
{"timestamp":"2022-11-24 14:23:54.711","component":"","level":"ERROR","thread-id":"http-nio-8080-exec-1","logger":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]","message":"Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause"}
java.lang.NullPointerException: null
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at org.springdoc.core.OpenAPIService.buildOpenAPIWithOpenAPIDefinition(OpenAPIService.java:520)
at org.springdoc.core.OpenAPIService.build(OpenAPIService.java:259)
at org.springdoc.api.AbstractOpenApiResource.getOpenApi(AbstractOpenApiResource.java:314)
at org.springdoc.webmvc.api.OpenApiResource.openapiYaml(OpenApiResource.java:156)
at org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiYaml(OpenApiWebMvcResource.java:133)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
If I changed the code to
#Bean
public OpenAPI customOpenAPI() {
MapSchema mySchema = new MapSchema();
mySchema.setAdditionalProperties(false); <-- I pass a boolean value instead of a schema
return new OpenAPI().components(new Components()
.addSchemas("Response", mySchema)
);
}
SpringDoc can generate the swagger doc successfully.
components:
schemas:
Response:
type: object
additionalProperties: false
Can you help to check what's wrong with my previous code ? Thanks.
I'm using spring-boot 2.7.5 and
org.springdoc/springdoc-openapi-webmvc-core 1.6.11

exception in post request in springboot throwing json phrasing error

i am sending post request through postman request to my spring boot application but it encounters the following error message
2022-08-03 11:30:51.637 WARN 7140 --- [nio-9002-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected character (' ' (code 160)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character (' ' (code 160)): was expecting double-quote to start field name<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 2, column: 3]]
this is my postman request
{   "operationClient": "03",   "module": "03",   "moduleName": "ECH",   "operation": "10",   "ip": "127.0.0.1",   "channelId": "5",   "connectionType": "1" }
my API call request
localhost:9002/cms/online/v1/channel/operation
my controller class of spring-boot
#PostMapping(value = "/operation", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResponseBean> doChannelOperation(#RequestBody #Valid RequestBean requestBean) throws Exception {
System.out.println(requestBean.toString());
ResponseBean responseBean = services.sendRequest(requestBean);
return new ResponseEntity<>(responseBean, HttpStatus.OK);
}

I am unable to post an Add Place API when using POST http method

io.restassured.path.json.exception.JsonPathException: Failed to parse
the JSON document
It is throwing error in the code written for these Feature file steps:
Given Add new location with "<name>" "<language>" "<address>" parameters
Then user calls "AddPlaceAPI" with http "POST" method
Then API successful response with "status" as "OK"
The stepDefinition code for these steps
#Given("Add new location with {string} {string} {string} parameters")
public void Add_new_location_with_three_parameters(String name_G,String address_G,String language_G) throws IOException {
res=given().spec(requestSpecification())
.body(data.googleAddPlace(name_G,address_G,language_G));
}
#Then("user calls {string} with http {string} method")
public void user_calls_with_http_method(String resource_G,String http_method)
{
APIResources resourceAPI = APIResources.valueOf(resource_G);
System.out.println("Resource value" + resourceAPI.getResource());
resspec =new ResponseSpecBuilder().expectStatusCode(200).expectContentType(ContentType.JSON).build();
if(http_method.equalsIgnoreCase("POST"))
response= res.when().post(resourceAPI.getResource());
else if(http_method.equalsIgnoreCase("GET"))
response= res.when().post(resourceAPI.getResource());
}
#Then("API successful response with {string} as {string}")
public void api_successful_response_with_as(String string, String string2) {
// Write code here that turns the phrase above into concrete actions
assertEquals(getJsonPath(response,string),string2);
}
getJsonPath method Definition where it is throwing Error
public String getJsonPath(Response response,String key)
{
String resp=response.asString();
JsonPath js = new JsonPath(resp);
return js.get(key).toString();
}
enter image description here
Junit Error Log
io.restassured.path.json.exception.JsonPathException: Failed to parse the JSON document
at io.restassured.path.json.JsonPath$ExceptionCatcher.invoke(JsonPath.java:1002)
at io.restassured.path.json.JsonPath$4.doParseWith(JsonPath.java:967)
at io.restassured.path.json.JsonPath$JsonParser.parseWith(JsonPath.java:1047)
at io.restassured.path.json.JsonPath.get(JsonPath.java:202)
at resources.Utils.getJsonPath(Utils.java:56)
at stepDefinations.stepDefination.api_successful_response_with_as(stepDefination.java:117)
at ✽.API successful response with "status" as "OK"(file:///C:/Users/gaura/eclipse-workspaceNew/APIFramework/src/test/java/features/googlemapsValidation.feature:6)
Caused by: groovy.json.JsonException: Lexing failed on line: 1, column: 1, while reading '<', no possible valid JSON value or
punctuation could be recognized.
at groovy.json.JsonLexer.nextToken(JsonLexer.java:86)
at groovy.json.JsonLexer$nextToken.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130)
at io.restassured.internal.path.json.ConfigurableJsonSlurper.parse(ConfigurableJsonSlurper.groovy:97)
at io.restassured.internal.path.json.ConfigurableJsonSlurper$parse.callCurrent(Unknown
Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:171)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:185)
at io.restassured.internal.path.json.ConfigurableJsonSlurper.parseText(ConfigurableJsonSlurper.groovy:83)
at io.restassured.path.json.JsonPath$4$1.method(JsonPath.java:965)
at io.restassured.path.json.JsonPath$ExceptionCatcher.invoke(JsonPath.java:1000)
at io.restassured.path.json.JsonPath$4.doParseWith(JsonPath.java:967)
at io.restassured.path.json.JsonPath$JsonParser.parseWith(JsonPath.java:1047)
at io.restassured.path.json.JsonPath.get(JsonPath.java:202)
at resources.Utils.getJsonPath(Utils.java:56)
at stepDefinations.stepDefination.api_successful_response_with_as(stepDefination.java:117)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown
Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown
Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at io.cucumber.java.Invoker.invoke(Invoker.java:27)
at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:27)
at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:63)
at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:64)
at io.cucumber.core.runner.TestStep.run(TestStep.java:49)
at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:46)
at io.cucumber.core.runner.TestCase.run(TestCase.java:51)
at io.cucumber.core.runner.Runner.runPickle(Runner.java:66)
at io.cucumber.junit.PickleRunners$NoStepDescriptions.run(PickleRunners.java:149)
at io.cucumber.junit.FeatureRunner.runChild(FeatureRunner.java:83)
at io.cucumber.junit.FeatureRunner.runChild(FeatureRunner.java:24)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at io.cucumber.junit.Cucumber.runChild(Cucumber.java:185)
at io.cucumber.junit.Cucumber.runChild(Cucumber.java:83)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at io.cucumber.junit.Cucumber$RunCucumber.evaluate(Cucumber.java:219)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
I tried combination of following versions for required jar files in pom.xml file and its working fine for me. You can also try the same:
JUnit- version: 4.1.2
Cucumber - java - info.cukes - version: 1.2.4
Cucumber - junit - info.cukes - version: 1.2.3
Cucumber - pico container - info.cukes - version: 1.2.4
Cucumber - core - info.cukes - version: 1.2.6
Cucumber - jvm deps - info.cukes - version: 1.0.5
Rest Assurd - version: 3.0.2

How to use datasource conditionally in applicationContext.xml

I'm creating a GAE project with Spring which will also use cloud SQL. While testing this app in local I'm pointing to my local MySQL environment but when I'll deploy it to GAE, it will point to cloud SQL instance. So I want to configure my driverName in datasource bean depending on environment. To this we generally use following in our java code
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
// Load the class that provides the new "jdbc:google:mysql://" prefix.
Class.forName("com.mysql.jdbc.GoogleDriver");
url = "jdbc:google:mysql://<your-project-id>:<your-instance-name>/<your-database-name>?user=root";
} else {
// Local MySQL instance to use during development.
Class.forName("com.mysql.jdbc.Driver");
url = "jdbc:mysql://127.0.0.1:3306/<your-database-name>?user=root";
}
Now I want to achieve the same in applicationcontext.xml using Spring Expression language. I haven't done this before and not able to achieve it. Please guide me. This is what I tried
<bean id="isDev" class="java.lang.Boolean">
<constructor-arg value="#{ systemProperties[environment.value] == SystemProperty.Environment.Value.Development ? true : false }" />
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="#{ isDev ? com.mysql.jdbc.Driver : com.mysql.jdbc.GoogleDriver }" />
.
.
.
But I'm getting exception, attaching a part of exception
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'feedbackFormController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.sandeepapplabs.custengage.services.FeedbackFormService com.sandeepapplabs.custengage.controllers.FeedbackFormController.feedbackFormService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'feedbackFormService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.sandeepapplabs.custengage.daos.FeedbackFormDAO com.sandeepapplabs.custengage.services.impl.FeedbackFormServiceImpl.feedbackFormDAO; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'feedbackFormDAO' defined in ServletContext resource [/WEB-INF/custengage-servlet.xml]: Cannot resolve reference to bean 'dataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in ServletContext resource [/WEB-INF/custengage-servlet.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is java.lang.NullPointerException
I believe you need to quote the class names to make them literals...
... 'com.mysql.jdbc.Driver' ...
However it is easier to use Spring Profiles](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html#beans-definition-profiles-xml) and enable the profile you want.
You can use Spring profiles to change concrete bean classes. Let's say you have 2 profiles: "prod" and "dev". Your bean methods should have #Profile annotation as below:
#Configuration
public class AppConfig {
...
#Bean
#Profile("prod")
public Object prodDataSource() {
return new ...
}
#Bean
#Profile("dev")
public Object getDataSource() throws Exception {
return new ...
}
}
If you are using Maven, you can select profile through pom.xml:
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profile>dev</spring.profile>
</properties>
</profile>
<profile>
<id>prod</id>
<activation>
<activeByDefault>false</activeByDefault>
<property>
<name>prod</name>
</property>
</activation>
<properties>
<spring.profile>prod</spring.profile>
</properties>
</profile>
</profiles>
You can pass profile selection argument by -D like: mvn -Dprod clean compile test install
EDIT:
In application.properties following property selects profile:
spring.profiles.active=${spring.profile}

Posting JSON to REST API

I'm creating a REST API that will accept JSON requests.
I'm testing it out using CURL:
curl -i -POST -H 'Accept: application/json' -d '{"id":1,"pan":11111}' http://localhost:8080/PurchaseAPIServer/api/purchase
But getting the following error:
HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1051
Date: Wed, 25 Apr 2012 21:36:14 GMT
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
When debugging it never even gets into my create action in the controller.
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.app.model.Purchase;
import com.app.service.IPurchaseService;
#Controller
public class PurchaseController {
#Autowired
private IPurchaseService purchaseService;
#RequestMapping(value = "purchase", method = RequestMethod.GET)
#ResponseBody
public final List<Purchase> getAll() {
return purchaseService.getAll();
}
#RequestMapping(value = "purchase", method = RequestMethod.POST)
#ResponseStatus( HttpStatus.CREATED )
public void create(#RequestBody final Purchase entity) {
purchaseService.addPurchase(entity);
}
}
UPDATE
I added Jackson config to AppConfig.java:
#Configuration
#ComponentScan(basePackages = "com.app")
public class AppConfig {
#Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
{
final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };
String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
return annotationMethodHandlerAdapter;
}
}
My GETs are working correctly now:
curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT
[{"id":1,"pan":111}]
But I get the following when attempting a POST:
curl -i -X POST -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close
The request sent by the client was syntactically incorrect ().
My Model:
#Entity
#XmlRootElement
public class Purchase implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6603477834338392140L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long pan;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getPan() {
return pan;
}
public void setPan(Long pan) {
this.pan = pan;
}
}
Any ideas where I'm going wrong?
Thanks
As sdouglass suggested, Spring MVC automatically detects Jackson and sets up a MappingJacksonHttpMessageConverter to handle conversion to/from JSON. But I did need explicity configure the converter to get it to work as he also pointed out.
I added the following and my CURL GET requests were working..Hooray.
AppConfig.java
#Configuration
#ComponentScan(basePackages = "com.app")
public class AppConfig {
#Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
{
final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };
String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
return annotationMethodHandlerAdapter;
}
}
curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT
[{"id":1,"pan":111}]
But the following CURL POST was still not working (Never hitting the controller action and giving no console debug info.
curl -i -X POST -H "Content-Type:application/json" http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close
The request sent by the client was syntactically incorrect ().
So I added Logback to get some detailed debugging started.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/home/thomas/springApps/purchaseapi.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
</pattern>
</encoder>
</appender>
<logger name="org.hibernate" level="DEBUG" />
<logger name="org.springframework" level="TRACE" />
<logger name="org.springframework.transaction" level="INFO" />
<logger name="org.springframework.security" level="INFO" /> <!-- to debug security related issues (DEBUG) -->
<logger name="org.springframework.web.servlet.mvc" level="TRACE" /> <!-- some serialization issues are at trace level here: org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod -->
<!-- our service -->
<logger name="com.app" level="DEBUG" />
<!-- <logger name="com.app" level="INFO" /> --><!-- to follow if setup is being executed -->
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
Adding TRACE level debugging to org.springframework.web.servlet.mvc gave me the answer to the problem.
2012-04-28 14:17:44,579 DEBUG [http-bio-8080-exec-3] o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [AbstractMessageConverterMethodArgumentResolver.java:117] Reading [com.app.model.Purchase] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#74a14fed]
2012-04-28 14:17:44,604 TRACE [http-bio-8080-exec-3] o.s.w.s.m.m.a.ServletInvocableHandlerMethod [InvocableHandlerMethod.java:159] Error resolving argument [0] [type=com.app.model.Purchase]
HandlerMethod details:
Controller [com.app.controller.PurchaseController]
Method [public void com.app.controller.PurchaseController.create(com.app.model.Purchase)]
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unexpected character ('p' (code 112)): was expecting double-quote to start field name
I changed my CURL POSTs to the following an it all worked:
curl -i -X POST -H "Content-Type:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase -d '{"pan":11111}'
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Sat, 28 Apr 2012 13:19:40 GMT
Hopefully someone finds this useful.
If I recall correctly the Spring docs say that Spring MVC will automatically detect Jackson on the classpath and set up a MappingJacksonHttpMessageConverter to handle conversion to/from JSON, but I think I have experienced situations where I had to manually/explictly configure that converter to get things to work. You may want to try adding this to your MVC config XML:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
</list>
</property>
</bean>
UPDATE: It was this plus properly formatting the JSON being posted, see https://stackoverflow.com/a/10363876/433789
Its 2014 and I wanted to add a few updates to this question which helped me solve the same problem.
Code update to replace deprecated AnnotationMethodHandlerAdapter in Spring 3.2
#Configuration
public class AppConfig {
#Bean
public RequestMappingHandlerAdapter annotationMethodHandlerAdapter()
{
final RequestMappingHandlerAdapter annotationMethodHandlerAdapter = new RequestMappingHandlerAdapter();
final MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
List<HttpMessageConverter<?>> httpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
httpMessageConverter.add(mappingJacksonHttpMessageConverter);
String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
return annotationMethodHandlerAdapter;
}
}
HTTP/1.1 415 Unsupported Media Type error
After spending many hours trying to figure out why I am STILL GETTING a 415 error even after adding the correct JSON configuration I finally realized that the problem was NOT with the server side but with the client side. In order for Spring to accept your JSON you MUST make sure that you are sending both "Content-Type : application/json" and "Accept: application/json" as part of your http headers. for me specifically it was an android application HttpUrlConnection which I had to set as:
public static String doPost(final String urlString,final String requestBodyString) throws IOException {
final URL url = new URL(urlString);
final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("POST");
urlConnection.setChunkedStreamingMode(0);
urlConnection.connect();
final PrintWriter out = new PrintWriter(urlConnection.getOutputStream());
out.print(requestBodyString);
out.close();
final InputStream in = new BufferedInputStream(urlConnection.getInputStream());
final String response = readIt(in);
in.close(); //important to close the stream
return response;
} finally {
urlConnection.disconnect();
}
}
Try adding a descriptor of what's in your POST request. That is, add to curl the header:
Content-Type: application/json
If you don't add it, curl will use the default text/html regardless of what you actually send.
Also, in PurchaseController.create() you have to add that the type accepted is application/json.
I had the same problem, which was solved by two changes in my code :
Missing #PathVariable in my method argument, my method didn't have any
Following method in my SpringConfig class since the one I had with handler interceptor was deprecated and giving some issue:
public RequestMappingHandlerAdapter RequestMappingHandlerAdapter()
{
final RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
final String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
requestMappingHandlerAdapter.getMessageConverters().add(0, mappingJacksonHttpMessageConverter);
requestMappingHandlerAdapter.setSupportedMethods(supportedHttpMethods);
return requestMappingHandlerAdapter;
}
Here is a unit test solution similar to yoram givon's answer - https://stackoverflow.com/a/22516235/1019307.
public class JSONFormatTest
{
MockMvc mockMvc;
// The controller used doesn't seem to be important though YMMV
#InjectMocks
ActivityController controller;
#Before
public void setup()
{
MockitoAnnotations.initMocks(this);
this.mockMvc = standaloneSetup(controller).setMessageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
#Test
public void thatSaveNewDataCollectionUsesHttpCreated() throws Exception
{
String jsonContent = getHereJSON02();
this.mockMvc
.perform(
post("/data_collections").content(jsonContent).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isCreated());
}
private String getHereJSON01()
{
return "{\"dataCollectionId\":0,\"name\":\"Sat_016\",\"type\":\"httpUploadedFiles\"," ...
}
}
Run the unit test and the print() should print out the MockHttpServletRequest including the Exception.
In Eclipse (not sure about how to do this in other IDEs), click on the Exception link and a properties dialog for that exception should open. Tick the 'enabled' box to break on that exception.
Debug the unit test and Eclipse will break on the exception. Inspecting it should reveal the problem. In my case it was because I had two of the same entity in my JSON.
I experienced once and finally solved it by adding the jar file jackson-mapper-asl.jar. Go check if you have included all these dependencies although the exception itself does not tell u that.
And you really don't need to explicitly configure the bean, and you don't need to put "consumes" in #RequestMapping statement. I'm using Spring 3.1 btw.
contentType : "application/json" is the only one you need to configure. yes, client side.
Try to add the following code in your app configuration
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</mvc:message-converters>
I had the same problem and I resolved it.
1 add MappingJackson2HttpMessageConverter as described in that thread (see also section 4 http://www.baeldung.com/spring-httpmessageconverter-rest)
2 use correct command (with escape symbols):
curl -i -X POST -H "Content-Type:application/json" -d "{\"id\":\"id1\",\"password\":\"password1\"}" http://localhost:8080/user