Prevent Jackson auto convert time zone in spring boot - java

I using RestController in Spring Boot and I have problem with Jackson auto converting time zone. When I try to send date to RestController, the date is auto convert to my current time zone and I don't want to prevent it to happened.
If I send
"NGAY": "2022-07-29T17:00:00.000+00:00",
I want to receive
2022-07-29
no matter what the time zone is
Not
2022-07-30
This is code in model class
#Column(name = "NGAY")
private java.util.Date ngay;
And this is code in my RestController
#RestController
#RequestMapping("/api/sanluong/ngay")
public class Controller {
#PostMapping("/savedata")
public ResponseEntity<?> uploadFile(#RequestBody List<MLoadprofileView> lstData) {
ResponseData result = sanLuongNgayService.onSaveData(lstData);
return ResponseEntity.ok(result);
}
}
I tried in application.properties but it has no effect
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE = false
EDIT: I want to applied to all java.ulti.Date field in all class

Related

java sql timestamp coming as milliseconds in rest api response

Hi I am seeking for some help,
previously I used to get my datetime field as 2022-06-30T22:39:22.235+0000 format but after adding one internal library(Org level) datetime fields are coming as 1656628762235 in json response.
I have tried to add #JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX") in my dto level but no luck also I tried adding below properties in yaml files.
spring.jackson.serialization.write-dates-as-timestamps: true
jackson-datatype-jsr310 version is : 2.9.10
jackson-databind : 2.9.10
spring-boot-2.1.11.RELEASE
please suggest what other option I can try to get the timestamp as in iso format.
#Setter
#Getter
public class TestRequestFormDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private UUID trfId;
private String testType;
private ItemDto item;
private List<RepItemsMappingDto> repItems;
private AdditionalQuestionsDto additionalQuestions;
private String hasRepItems;
private TestRequestInfoDto testRequestInfo;
private ItemInformationDto itemInformation;
private LabDto lab;
private Timestamp createdDate;
private String createdBy;
private String status;
private Timestamp modifiedDate;
private String modifiedBy;
private String pageLeftFrom;
private String referenceNumber;
private TrfVendorDto trfVendor;
private TrfFactoryDto trfFactory;
private String originCountry;
private String itemsVbu;
private String versionNumber;
}
previous date format:
{"createdDate": "2022-06-30T22:39:22.235+0000"}
current date format:
{"createdDate": 1656628762235}
Use modern Time API
java.util.Date and its subclasses are legacy.
Since Java 8 (which was released about 10 years ago) we have modern Time API which includes Instant, LocalDateTime and other classes from the java.time package.
java.sql.Timestamp as well as Date is obsolete and discouraged to be used.
You're advised to use java.time.Instant instead (if you have the ability to change the types in your DTO).
Seconds to Instant
The task would be simple if your timestamp were represented in seconds (unfortunately it's not, but for completeness I'll describe this option).
In this case, you would need to have a Bean of type JavaTimeModule in the Spring Contexts. Spring-Boot will do the rest for you. JacksonAutoConfiguration will grab the module and register it automatically.
#Configuration
public class JsonConfig {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
}
Note: if the target field is of type Instant no extra annotations required.
Milliseconds to Instant
In case if your timestamp represented in milliseconds and autoconfiguration would not work (you can try it, the parsed date would be very far in the future).
To resolve this problem, we need to configure ObjectMapper manually by registering JavaTimeModule module and instructing mapper about the precision of the timestamp. For that, would need to place two Beans Jackson2ObjectMapperBuilder and ObjectMapper in the Spring's Context.
#Configuration
public class JsonConfig {
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder();
}
#Bean
public ObjectMapper objectMapper() {
return jackson2ObjectMapperBuilder()
.build()
.registerModule(new JavaTimeModule())
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
}
}
And as I've said before, no additional steps and no extra annotations on fields required (unless the property name doesn't match).
Usage example:
Input JSON:
{"timestamp": 1656628762235}
Output:
[2022,6,30,22,39,22,235000000]
If you can't a change the data type
As the last resort if you're forced to use legacy date-time representations, here's a couple of possible solutions:
You can declare all-args constructor in your POJO and annotate each argument with #JsonProperty. The trick is to declare the corresponding arguments of type long and parse milliseconds to java.sql.Timestamp manually. But judging by the number of field you have, this approach doesn't look like the best option.
Another option would be to implement a custom Deserializer and annotate the fields of type Timestamp with #JsonDeserialize annotation.

Changing date format when sending a Response entity

We are currently using SpringBoot and PostgreSQL and we have problem with date format.
When we save or edit(sending POST request from front-end) something we need a YYYY-MM-DD format since any other type of format wont save anything to database so #JSONformat on Entity or some type of annotation like that is not possible. But when we are fetching example all users it would be nicer that we get DD-MM-YYYY in a response from server, and im not sure how to do that.
We can do it from front-end but code isn't nice so back-end solution would be better.
Thanks in advance!
You can create a mapper and map your entity object to a DTO that will be sent to your front-end.
Since you don't specify the class you are using for your dates I will use LocalDate as an example, but the same logic can be applied to your date class.
Let's say your entity class looks like that:
public class SampleEntity {
private LocalDate localDate;
}
Your DTO class will look like that:
public class SampleDto {
// the Jackson annotation to format your date according to your needs.
#DateTimeFormat(pattern = "DD-MM-YYYY")
private LocalDate localDate;
}
Then you need to create the mapper to map from SampleEntity to SampleDto and vice versa.
#Component
public class SampleMapper {
public SampleDto mapTo(final SampleEntity sampleEntity){
// do the mapping
SampleDto sampleDto = new SampleDto();
sampleDto.setLocalDate(sampleEntity.getLocalDate());
return sampleDto;
}
public SampleEntity mapFrom(final SampleDto sampleDto){
// do the mapping
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setLocalDate(sampleDto.getLocalDate());
return sampleEntity;
}
}
You can use all those in your controller like that:
#GetMapping
public ResponseEntity<SampleDto> exampleMethod() {
// service call to fetch your entity
SampleEntity sampleEntity = new SampleEntity(); // lets say this is the fetched entity
sampleEntity.setLocalDate(LocalDate.now());
SampleDto sampleDto = sampleMapper.mapTo(sampleEntity);
return ResponseEntity.ok(sampleDto);
}
With this solution, you can avoid adding Jackson annotations to your entity. Also with this solution, you can control exactly what your front-end can access. More on that here What is the use of DTO instead of Entity?

Spring R2DBC DatabaseClient losing TZ information

I have a db entity with some Instant type fields.
org.springframework.data.r2dbc.core.DatabaseClient (which is now deprecated), had a method .as(..) to automatically map to a java entity and it honoured the timezones too. Not sure how that happened internally.
But using org.springframework.r2dbc.core.DatabaseClient which does not have an automatic mapper, I must use a .map(...) which provides the Rows and I map them like
row.get("blah", Instant.class)
But it just gives time in my local TZ, not UTC.
Anyone knows the root cause? Thanks.
Short Answer
I'm assuming that you are using the PostgresSQL database and appropriate driver. Also, I assume that you are using the TIMESTAMP DB type for your Instant filed. To get the correct timezone from org.springframework.r2dbc.core.DatabaseClient you can use TIMESTAMP WITH TIME ZONE as a data type.
Long Answer
I have created a test repo to test the behavior of R2dbcRepository and R2DBC DatabaseClient to retrieve timestamp and timestampz into Instant. To do so, I have created the following table:
CREATE TABLE test_table (id SERIAL PRIMARY KEY, timestamp_without_tz TIMESTAMP, timestamp_with_tz TIMESTAMP WITH TIME ZONE);
And implemented the following service:
package com.patotski.r2dbctimestamps.service;
import com.patotski.r2dbctimestamps.domain.TestEntity;
import com.patotski.r2dbctimestamps.repo.TestEntityRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.time.Instant;
#Service
#RequiredArgsConstructor
public class TestEntityService {
private final TestEntityRepo testEntityRepo; // uses standard R2dbcRepository implementation
private final DatabaseClient databaseClient;
public Mono<TestEntity> saveUsingRepo(TestEntity entity) {
return testEntityRepo.save(entity);
}
public Mono<TestEntity> getByIdFromRepo(int id) {
return testEntityRepo.findById(id);
}
public Mono<TestEntity> saveUsingDbClient(TestEntity entity) {
return databaseClient.sql("INSERT INTO test_table (timestamp_without_tz, timestamp_with_tz) VALUES(:timestamp_without_tz, :timestamp_with_tz)")
.bind("timestamp_without_tz", entity.getTimestamp_without_tz())
.bind("timestamp_with_tz", entity.getTimestamp_with_tz())
.map(row -> TestEntity.builder()
.id(row.get("id", Integer.class))
.timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
.timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
.build()).first();
}
public Mono<TestEntity> getByIdFromDbClient(int id) {
return databaseClient.sql("SELECT * from test_table where id = :id")
.bind("id", id)
.map(row -> TestEntity.builder()
.id(row.get("id", Integer.class))
.timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
.timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
.build()).first();
}
}
And created tests that just store entity and retrieve asserting Timestamps:
#SpringBootTest
class TestEntityServiceTest {
#Autowired
TestEntityService testEntityService;
#Test
#DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC repo.")
void shouldStoreCorrectTimestampsAndRetriveWithRepo() {
Instant now = Instant.now();
TestEntity entity = TestEntity.builder()
.timestamp_with_tz(now)
.timestamp_without_tz(now)
.build();
TestEntity saved = testEntityService.saveUsingRepo(entity).block();
Assertions.assertThat(testEntityService.getByIdFromRepo(saved.getId()).block()).isNotNull()
.extracting(TestEntity::getId,
TestEntity::getTimestamp_without_tz,
TestEntity::getTimestamp_with_tz)
.containsExactly(saved.getId(), now, now);
}
#Test
#DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC DatabaseClient.")
void shouldStoreCorrectTimestampsAndRetriveWithDbClient() {
Instant now = Instant.now();
TestEntity entity = TestEntity.builder()
.timestamp_with_tz(now)
.timestamp_without_tz(now)
.build();
testEntityService.saveUsingDbClient(entity).block();
Assertions.assertThat(testEntityService.getByIdFromDbClient(1).block()).isNotNull()
.extracting(TestEntity::getId,
TestEntity::getTimestamp_without_tz,
TestEntity::getTimestamp_with_tz)
.containsExactly(1, now, now);
}
}
Results show that these 2 methods are not consistent with each other:
R2dbcRepository test always passes in all TZs
DatabaseClient test passes only when tests are running in UTC TZ and fails when not. The reason is that field defined as TIMESTAMP in DB gets TZ offset.
TIMESTAMPZ field works correctly in both cases always
Repo to reproduce: https://github.com/xp-vit/r2dbc-timestamps
Created and issue for Spring team to at least discuss: https://github.com/spring-projects/spring-data-r2dbc/issues/608

ZonedDateTime returning as Epoch time instead of standard String in Spring App

I am trying fetch some DateTime values stored in a local MySQL database in my Spring App. These dates are parsed into a ZoneDateTime and are then sent to a Client Front End as a json. I have an Object Mapper that specifies this conversion.
#Bean
public ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class,
new ZonedDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")));
return Jackson2ObjectMapperBuilder.json().featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISODate
.modules(javaTimeModule).build();
}
However, on the front-end, the values I receive are in Epoch time instead of the format specified in the ObjectMapper. I have checked the value parsed into ZoneDateTime and it is parsed correctly. My guess is that there is some fault in the process mapping the ZoneDateTime object into the json String value.
What could be the fix of this?
Here is how to do it simply and efficiently:
#JsonFormat(shape= JsonFormat.Shape.STRING, pattern="EEE MMM dd HH:mm:ss Z yyyy")
#JsonProperty("created_at")
ZonedDateTime created_at;
This is a quote from a question: [Jackson deserialize date from Twitter to `ZonedDateTime`
Also, I don't think that you need to add a special serializer for this. It works for me without this definition just fine.
https://docs.spring.io/spring/docs/4.3.0.RC1_to_4.3.0.RC2/Spring%20Framework%204.3.0.RC2/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.html#timeZone-java.lang.String-
Docs says there is a timezone(String) method to override default timezone.
I suppose you could pass the timezone into this method while building the ObjectMapper
#Bean
public Jackson2ObjectMapperBuilderCustomizer init() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.timeZone(TimeZone.getDefault());
}
};
}
You could use above code to override default timezone.
return Jackson2ObjectMapperBuilder.json().featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISODate
.modules(javaTimeModule).timeZone(TimeZone.getDefault()).build();
You could try this.

Set date and time in Spring mvc

I'm using latest spring and hibernate. I need to get the date and time form jsp page and want to insert into mysql database. I'm using TIMESTAMP as dataType of one fields. When I try to save, there is no error, but showing "HTTP Status [400] – [Bad Request]".
Finally I find out, there is a problem in Date and Time format (may be in annotation or MySQL datatype or jsp page). Because I try to update the form without changing valuei of date time (path="startDateTime"). It was successfully updated. When I try to change the value in date time (path="startDateTime"), it shows the "HTTP Status [400] – [Bad Request]".
I need to update Date and Time to database. I tried a lot of ways, but failed.
Model class
public class Survey{
//other declaration
#Column(name="startDateTime",columnDefinition="TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
private Date startDateTime;
}
jsp page
<spring:url value="/survey/save" var="saveURL"></spring:url>
<form:form action="${saveURL}" method="POST" modelAttribute="surveyForm">
//other stuffs
<form:input type="datetime-local" path="startDateTime" />
</form:form>
Controller
public ModelAndView saveSurvey(#ModelAttribute("surveyForm") Survey survey) {
surveyService.saveOrUpdate(survey);
return new ModelAndView("redirect:/survey/list");
}
In order to adopt good practice, i suggest using JAVA 8 LocalDateTime class.
So modify your model class like this
public class Survey{
//other declaration
#Column(name="startDateTime",columnDefinition="TIMESTAMP")
private LocalDateTime startDateTime;
}
When using JAVA 8 LocalDateTime class, you no longer need to add #Temporal(TemporalType.TIMESTAMP)
After that,you need to create a LocalDateTimeConverter class by implement Converter interface like this
import org.springframework.core.convert.converter.Converter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public final class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
private final DateTimeFormatter formatter;
public LocalDateTimeConverter(String dateFormat) {
this.formatter = DateTimeFormatter.ofPattern(dateFormat);
}
#Override
public LocalDateTime convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
return LocalDateTime.parse(source, formatter);
}
}
Then register the LocalDateTimeConverter class in your configuration class like this
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableWebMvc
class WebMvcContext extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS"));
}
}
That's all and i hope it solves your problem.
You can learn more here
Well, i will still like to suggest that instead of leaving the responsibility to your site user to do the date time update why not allow hibernate to do that for you by using the #CreationTimestamp and #UpdateTimestamp annotations of hibernate or preferably by using JPA #PrePersist and #PreUpdate annotations like this
#Column(name="startDateTime")
#PrePersist
private LocalDateTime startDateTime;
#Column(name="updatedDateTime")
#PreUpdate
private LocalDateTime updatedDateTime;
With this approach, you no longer need to add startDateTime field in your form because hibernate will automatically insert and update those columns for you.
Note: Form input type datetime-local is not supported in all browers.
The problem is the parameter send to spring mvc is String, your dateToday type is date. Spring mvc don't know how to convert it.
You need a #InitBinder
Consult below
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Timestamp.class, new CustomDateEditor(dateFormat, false));
}
In your hibernate configuration file .xml, is it set to update? Try checking on it.
<property name="hbm2ddl.auto">update</property>
Use initbinder in your controller class
#InitBinder
public void dataBinding(WebDataBinder binder) {
SimpleDateFormat dateFormat = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
dateFormat.setLenient(false);
binder.registerCustomEditor(LocalDateTime.class, "startDateTime", new CustomDateEditor(dateFormat, true));
}
Hope this solves it

Categories