I am beginning with REST techniologies, and I choose Spring 3.2 and Jackson 2.2 . I have small question. I created REST API and it looks like this:
#Controller
public class WorkersController {
#Autowired
public DatabaseService dbService;
#ResponseBody
#RequestMapping(value="/workers", method = RequestMethod.GET, produces = "application/json")
public ArrayList<Worker> getAllWorkersFromDatabase() {
}
#ResponseBody
#RequestMapping(value="/workers/new", method = RequestMethod.POST, produces = "application/json", consumes="application/json")
public String saveWorker(#RequestBody final WorkerDTO workerDto) {
}
#ResponseBody
#RequestMapping(value="/workers/{workerid}", method = RequestMethod.GET, produces = "application/json")
public Worker getWOrkerByDatabaseId(#PathVariable Integer workerid) {
}
#ResponseBody
#RequestMapping(value="/workers/{workerid}/edit", method = RequestMethod.PUT, produces = "application/json")
public String editWorker(#PathVariable Integer workerid, #RequestBody Worker worker) {
}
}
When I make HTTP GET all is ok but I have problem with POST. When I am calling saveWorker() method I get:
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method
I imported required libraries:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
I think the main problem is in configuration files and #RequestBody cant map JSON to DTO. It is my Configuration:
#Configuration
#ComponentScan(basePackages = "org.schedule.service")
#EnableWebMvc
#Import(DatabaseSpringConfig.class)
public class ServiceSpringConfig extends WebMvcConfigurationSupport{
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
List<MediaType> jsonTypes = new ArrayList<>(jsonConverter.getSupportedMediaTypes());
jsonTypes.add(MediaType.TEXT_PLAIN);
jsonTypes.add(MediaType.APPLICATION_JSON);
jsonConverter.setSupportedMediaTypes(jsonTypes);
converters.add(jsonConverter);
}
}
My DTO:
public class WorkerDTO implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public String surname;
public WorkerDTO() {
}
}
Json:
{
"name": "asdssss",
"surname": "asdssssss"
}
And http call:
localhost:8080/Schedule-service/workers/new?Content-type=application/json
Thanks for all replies.
The request
localhost:8080/Schedule-service/workers/new?Content-type=application/json
has a request parameter with name Content-Type and value application/json.
HttpMessageConverter classes, and MappingJackson2HttpMessageConverter in particular, don't look for request parameters, they look for headers.
You need to specify a Content-Type header for your request.
Related
I am new to Spring Data REST project and I am trying to create my first RESTful service. The task is simple, but I am stuck.
I want to perform CRUD operations on a user data stored in an embedded database using RESTful API.
But I cannot figure out how to make the Spring framework process the birthData as "1999-12-15" and store it as a LocalDate. The #JsonFormat annotation does not help.
At present I get the error:
HTTP/1.1 400
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 24 Aug 2017 13:36:51 GMT
Connection: close
{"cause":{"cause":null,"message":"Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream#4ee2a60e;
line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"},
"message":"JSON parse error: Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10'); nested exception is com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream#4ee2a60e; line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"}
How to make it work, so that client calls like:
curl -i -X POST -H "Content-Type:application/json" -d "{ \"firstName\" : \"John\", \"lastName\" : \"Johnson\", \"birthDate\" : \"1999-10-10\", \"email\" : \"john#example.com\" }" http://localhost:8080/users
will actually store the entity into the database.
Below is the information about the classes.
The user class:
package ru.zavanton.entities;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDate;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
private String email;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
The UserRepository class:
package ru.zavanton.repositories;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import ru.zavanton.entities.User;
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
User findByEmail(#Param("email") String email);
}
Application class:
package ru.zavanton;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You need jackson dependency for this serialization and deserialization.
Add this dependency:
Gradle:
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4")
Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
After that, You need to tell Jackson ObjectMapper to use JavaTimeModule.
To do that,
Autowire ObjectMapper in the main class and register JavaTimeModule to it.
import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
#SpringBootApplication
public class MockEmployeeApplication {
#Autowired
private ObjectMapper objectMapper;
public static void main(String[] args) {
SpringApplication.run(MockEmployeeApplication.class, args);
}
#PostConstruct
public void setUp() {
objectMapper.registerModule(new JavaTimeModule());
}
}
After that,
Your LocalDate and LocalDateTime should be serialized and deserialized correctly.
Spring Boot 2.2.2 / Gradle:
Gradle (build.gradle):
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
Entity (User.class):
LocalDate dateOfBirth;
Code:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
User user = mapper.readValue(json, User.class);
I had a similar issue which I solved by making two changes
Added below entry in application.yaml file
spring:
jackson:
serialization.write_dates_as_timestamps: false
Add below two annotations to the POJO's LocalDate field
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
Example
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class Customer {
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
protected LocalDate birthdate;
}
Eample request format:
{"birthdate": "2019-11-28"}
Example request format as array
{"birthdate":[2019,11,18]}
As it turns out, one should not forget to include jacson dependency into the pom file. This solved the issue for me:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Well, what I do on every project is a mix of the options above.
First, add the jsr310 dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Important detail: put this dependency on the top of your depedencies list. I already see a project where the Localdate error persists even with this dependency on the pom.xml. But changing the order of the depedency the error was gone.
On your /src/main/resources/application.yml file, setup the write-dates-as-timestamps property:
spring:
jackson:
serialization:
write-dates-as-timestamps: false
And create a ObjectMapper bean as this:
#Configuration
public class WebConfigurer {
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
}
Following this configuration, the conversion always work on Spring Boot 1.5.x without any error.
Bonus: Spring AMQP Queue configuration
Working with Spring AMQP, pay attention if you have a new instance of Jackson2JsonMessageConverter (common thing when creating a SimpleRabbitListenerContainerFactory). You need to pass the ObjectMapper bean to it, like:
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);
Otherwise, you will receive the same error.
I have just wrestled with this for 3 hours. I credit the answer from Dherik (Bonus material about AMQP) for bringing me within striking distance of MY answer, YMMV.
I registered the JavaTimeModule in my object mapper in my SpringBootApplication like this:
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
However my Instants that were coming over the STOMP connection were still not deserialising. Then I realised I had inadvertantly created a MappingJackson2MessageConverter which creates a second ObjectMapper. So I guess the moral of the story is: Are you sure you have adjusted all your ObjectMappers? In my case I replaced the MappingJackson2MessageConverter.objectMapper with the outer version that has the JavaTimeModule registered, and all is well:
#Autowired
ObjectMapper objectMapper;
#Bean
public WebSocketStompClient webSocketStompClient(WebSocketClient webSocketClient,
StompSessionHandler stompSessionHandler) {
WebSocketStompClient webSocketStompClient = new WebSocketStompClient(webSocketClient);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(objectMapper);
webSocketStompClient.setMessageConverter(converter);
webSocketStompClient.connect("http://localhost:8080/myapp", stompSessionHandler);
return webSocketStompClient;
}
I am trying to add #NotNull constraint into my Person object but I still can #POST a new Person with a null email. I am using Spring boot rest with MongoDB.
Entity class:
import javax.validation.constraints.NotNull;
public class Person {
#Id
private String id;
private String username;
private String password;
#NotNull // <-- Not working
private String email;
// getters & setters
}
Repository class:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
}
Application class:
#SpringBootApplication
public class TalentPoolApplication {
public static void main(String[] args) {
SpringApplication.run(TalentPoolApplication.class, args);
}
}
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
...
When I #POST a new object via Postman like:
{
"username": "deadpool",
"email": null
}
I still get STATUS 201 created with this payload:
{
"username": "deadpool",
"password": null,
"email": null
....
....
}
I had the same problem, but just enabling validation didn't work for me, this did work with both JPA and MongoDb to save anyone else spending ages on this. Not only does this get validation working but I get a nice restful 400 error rather than the default 500.
Had to add this to my build.gradle dependencies
compile('org.hibernate:hibernate-validator:4.2.0.Final')
and this config class
#Configuration
public class CustomRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("afterCreate", validator());
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("afterSave", validator());
validatingListener.addValidator("beforeSave", validator());
}
}
i found it better to make my own version of #NotNull annotation which validates empty string as well.
#Documented
#Constraint(validatedBy = NotEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotEmpty {
String message() default "{validator.notEmpty}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, Object> {
#Override
public void initialize(NotEmpty notEmpty) { }
#Override
public boolean isValid(Object obj, ConstraintValidatorContext cxt) {
return obj != null && !obj.toString().trim().equals("");
}
}
You can either use the following code for validating
#Configuration
#Import(value = MongoAutoConfiguration.class)
public class DatabaseConfiguration extends AbstractMongoConfiguration
{
#Resource
private Mongo mongo;
#Resource
private MongoProperties mongoProperties;
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Override
protected String getDatabaseName() {
return mongoProperties.getDatabase();
}
#Override
public Mongo mongo() throws Exception {
return mongo;
}
}
Normally, the #RestRepository will resolve into a controller than handles validation by itself, except if you Override the default behavior or it by including some #HandleBeforeSave, #HandleBeforeCreate, ... into your code.
A solution is to remove the #HandleBeforeSave, #HandleBeforeCreate, ...
and then spring will handle the validation again.
Or if you want to keep them, you can provide a handler for any object validation like this:
#Component
#RepositoryEventHandler
public class EntityRepositoryEventHandler {
#Autowired
private Validator validator;
#HandleBeforeSave
#HandleBeforeCreate
public void validate(Object o) {
Set<ConstraintViolation<Object>> violations = this.validator.validate(o);
if (!violations.isEmpty()) {
ConstraintViolation<Object> violation = violations.iterator().next();
// do whatever your want here as you got a constraint violation !
throw new RuntimeException();
}
}
}
I have this method on my controller:
#RequestMapping(value="/test", method = RequestMethod.POST)
public #ResponseBody String test(#RequestBody Test test) {
return test.getName();
}
My Test class:
public class Test implements Serializable {
private static final long serialVersionUID = -1150931681075770764L;
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
I post this json using:
{"name": "avocado"}
I also have this annotation:
<mvc:annotation-driven />
I'm using Advanced Rest Client to test it.
My request's Content-Type is set to "application/json".
If I set spring version to 4.0.9.RELEASE or earlier I get 200 OK code when post.
If I set spring version to 4.1.0.RELEASE or later I get 415 Unsupported Media Type.
What shoud I do in order to get code 200 setting spring version to 4.2.1.RELEASE?
I suggest you to add
#RequestMapping(value="/test", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String test(#RequestBody Test test) {
return test.getName();
}
Also conversion works with the HttpMessageConverter<?> to be able to serialize/deserialize in JSON format you need to have Jackson libraries in your classpath then spring will be able to instantiate the MappingJackson2HttpMessageConverter
Add this to your dependencies.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-json.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-json.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-json.version}</version>
</dependency>
I am very new for spring mvc and java. i want to return a json data instead of string
#RequestMapping(value = "/ex/foos", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public String getFoosAsJsonFromREST() {
return "{\"name\":\"MyNode\", \"width\":200, \"height\":100}";
}
actual output:
"{\"name\":\"MyNode\", \"width\":200, \"height\":100}"
output i want:
{"name":"MyNode", "width":200, "height":100}
i followed the link but i still can't get literal json output
#RequestMapping(value = "/ex/foos", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public JsonNode getFoosAsJsonFromREST() {
String everything = "{\"a\":2,\"b\":\"astring\",\"c\":6}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(everything);
return node;
}
output
{
"result": false,
"message": "Unexpected end-of-String when base64 content\n at [Source: N/A; line: -1, column: -1]"
}
You're nearly there :)
JSON is just an object format so you must return an object with key:value pairs.
#RequestMapping(value = "/ex/foos", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public MyJSONRespone getFoosAsJsonFromREST() {
MyJSONRespone myResponse = new MyJSONRespone();
myResponse.setName("MyNode");
myResponse.setWidth(200);
myResponse.setHeight(100);
return myResponse;
}
class MyJSONRespone{
private String name;
private Integer width;
private Integer Height;
//setters and getters
}
Also make sure you have the correct dependency in your POM if you are using Maven:
<!-- Jackson/JSON START -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.2</version>
</dependency>
<!-- Jackson/JSON END -->
Sure you can return json output without your own class. First approach same as yours try this one https://stackoverflow.com/a/64482663/10353679
#RequestMapping(value = "/ex/fooss", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public ResponseEntity<JsonNode> getDeneme() {
String everything = "{\"name\":\"MyNode\", \"width\":200, \"height\":100}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(everything);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return ResponseEntity.ok(node);
}
The example above should work
Second approach in your code just return node.toString() with produces = "application/json". json is just format. Client will probably just check the Content-Type which is application/json and if the format of json is correct, Client's parser will just parse it.
#RequestMapping(value = "/ex/foos", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public String getDeneme() {
String everything = "{\"a\":2,\"b\":\"astring\",\"c\":6}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(everything);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return node.toString();
}
IMPORTANT: node.toString(); might return NullPointerException since node can be null you should properly handle it.
And the other point is you should not create new ObjectMapper every time. You should inject ObjectMapper to this Controller class as a field and then just use injected objectMapper
I have a RESTful service with some methods. Methods are annotated by #POST and #Consumes(MediaType.APPLICATION_JSON).
I have wrapper for request parameters:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class RequestWrapper {
#XmlElement
private SomeInfo someInfo = new SomeInfo();
#XmlElement
#XmlElementWrapper
private List<RequestParameter> requestParameters = new ArrayList<>();
}
public class SomeInfo {
public String field1;
public String field2;
}
public class RequestParameter {
public String key;
public String value;
}
I make request to my service. Body of the POST message:
{"someInfo":{"field1":"b","field2":"c"},"requestParameters":[{"key":"1","value":"2"},{"key":"3","value":"4"}]}
I see that someInfo values processed and accesible via RequestWrapper. But List<RequestParameter> requestParameters has zero length.
What should I do to messages been processed normally?
P.S.
I use Glassfish 4.0. Dependencies of the maven module:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
omg, I have found magic bullet
Delete #XmlAccessorType(XmlAccessType.FIELD)
Delete #XmlElementWrapper
make public getters/setters for each field
#XmlRootElement
public class RequestWrapper {
#XmlElement
private SomeInfo someInfo = new SomeInfo();
#XmlElement
private List<RequestParameter> requestParameters = new ArrayList<>();
// getters/setters for each field
}