I am building a web application based on hotel room booking and testing my REST API through postman and this @Future annotation from import jakarta.validation.constraints.Future; is giving me 500 response, while I tried to hit the link mentioned in the screenshot, I tried again by removing this @Future
annotation and it is working perfectly, so please tell me if there is any alternative or solution for this issue.
I have used this @Future
over one attribute(checkOutDate) of entity, below are the codes related to Booking: -
Booking.java
package com.Yash.Astoria.entities;
import jakarta.validation.constraints.Future;
import jakarta.persistence.*;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDate;
@Data
@Entity
@Table(name = "bookings")
public class Booking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull(message = "CheckIn Date is required")
private LocalDate checkInDate;
@Future(message = "check out date must be in the future")
private LocalDate checkOutDate;
@Min(value = 1, message = "Atleast 1 adult should be selected")
private int numOfAdults;
@Min(value = 0, message = "Number of Childrens should not be less then 0")
private int numOfChildren;
private int totalNumOfGuests;
private String bookingConfirmationCode;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id")
private Room room;
public void getTotalNumberOfGuests(){
this.totalNumOfGuests = this.numOfAdults + this.numOfChildren;
}
public void setNumOfAdults(int numOfAdults) {
this.numOfAdults = numOfAdults;
getTotalNumberOfGuests();
}
public void setNumOfChildren(int numOfChildren) {
this.numOfChildren = numOfChildren;
getTotalNumberOfGuests();
}
@Override
public String toString() {
return "Booking{" +
"id=" + id +
", checkInDate=" + checkInDate +
", checkOutDate=" + checkOutDate +
", numOfAdults=" + numOfAdults +
", numOfChildren=" + numOfChildren +
", totalNumOfGuests=" + totalNumOfGuests +
", bookingConfirmationCode='" + bookingConfirmationCode + '\'' +
'}';
}
}
BookingController.java
package com.Yash.Astoria.controllers;
import com.Yash.Astoria.dto.Response;
import com.Yash.Astoria.entities.Booking;
import com.Yash.Astoria.services.Interface.IBookingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/bookings")
public class BookingController {
@Autowired
private IBookingService bookingService;
@PostMapping("/book-room/{roomId}/{userId}")
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
public ResponseEntity<Response> saveBookings(@PathVariable Long roomId,
@PathVariable Long userId,
@RequestBody Booking bookingRequest){
Response response = bookingService.saveBooking(roomId, userId, bookingRequest);
return ResponseEntity.status(response.getStatusCode()).body(response);
}
@GetMapping("/all")
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<Response> getAllBookings(){
Response response = bookingService.getAllBookings();
return ResponseEntity.status(response.getStatusCode()).body(response);
}
@GetMapping("/get-by-confirmation-code/{confirmationCode}")
public ResponseEntity<Response> getBookingByConfirmationCode(@PathVariable String confirmationCode){
Response response = bookingService.findBookingByConfirmationCode(confirmationCode);
return ResponseEntity.status(response.getStatusCode()).body(response);
}
@DeleteMapping
@PreAuthorize("hasAuthority('ADMIN') or hasAuthority('USER')")
public ResponseEntity<Response> cancelBooking(@PathVariable Long bookingId){
Response response = bookingService.cancelBooking(bookingId);
return ResponseEntity.status(response.getStatusCode()).body(response);
}
}
IBookingService.java
package com.Yash.Astoria.services.Interface;
import com.Yash.Astoria.dto.Response;
import com.Yash.Astoria.entities.Booking;
public interface IBookingService {
Response saveBooking(Long roomId, Long userId, Booking bookingRequest);
Response findBookingByConfirmationCode(String confirmationCode);
Response getAllBookings();
Response cancelBooking(Long bookingId);
}
BookingService.java
package com.Yash.Astoria.services.impl;
import com.Yash.Astoria.dto.BookingDTO;
import com.Yash.Astoria.dto.Response;
import com.Yash.Astoria.entities.Booking;
import com.Yash.Astoria.entities.Room;
import com.Yash.Astoria.entities.User;
import com.Yash.Astoria.exception.OurException;
import com.Yash.Astoria.repository.BookingRepository;
import com.Yash.Astoria.repository.RoomRepository;
import com.Yash.Astoria.repository.UserRepository;
import com.Yash.Astoria.services.Interface.IBookingService;
import com.Yash.Astoria.services.Interface.IRoomService;
import com.Yash.Astoria.utils.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookingService implements IBookingService {
@Autowired
private BookingRepository bookingRepository;
@Autowired
private IRoomService roomService;
@Autowired
private RoomRepository roomRepository;
@Autowired
private UserRepository userRepository;
@Override
public Response saveBooking(Long roomId, Long userId, Booking bookingRequest) {
Response response = new Response();
try {
if (bookingRequest.getCheckOutDate().isBefore(bookingRequest.getCheckInDate())) {
throw new IllegalArgumentException("Check out date must come after Check in date");
}
Room room = roomRepository.findById(roomId).orElseThrow(() -> new OurException("Room Not Found"));
User user = userRepository.findById(userId).orElseThrow(() -> new OurException("User Not Found"));
List<Booking> existingBookings = room.getBookings();
if (!roomIsAvailable(bookingRequest, existingBookings)) {
throw new OurException("Room not Available for selected date range");
}
bookingRequest.setRoom(room);
bookingRequest.setUser(user);
String bookingConfirmationCode = Utils.generateRandomConfirmationCode(10);
bookingRequest.setBookingConfirmationCode(bookingConfirmationCode);
bookingRepository.save(bookingRequest);
response.setStatusCode(200);
response.setMessage("successful");
response.setBookingConfirmationCode(bookingConfirmationCode);
} catch (OurException e) {
response.setStatusCode(404);
response.setMessage(e.getMessage());
} catch (Exception e){
response.setStatusCode(500);
response.setMessage("Error while Saving a Booking"+e.getMessage());
}
return response;
}
@Override
public Response findBookingByConfirmationCode(String confirmationCode) {
Response response = new Response();
try {
Booking booking = bookingRepository.findBookingByBookingConfirmationCode(confirmationCode).orElseThrow(()-> new OurException("Booking Not Found"));
BookingDTO bookingDTO = Utils.mapBookingEntityToBookingDTOPlusBookedRooms(booking, true);
response.setStatusCode(200);
response.setMessage("successful");
response.setBooking(bookingDTO);
} catch (OurException e) {
response.setStatusCode(404);
response.setMessage(e.getMessage());
} catch (Exception e){
response.setStatusCode(500);
response.setMessage("Error Finding a Booking"+e.getMessage());
}
return response;
}
@Override
public Response getAllBookings() {
Response response = new Response();
try {
List<Booking> bookingList = bookingRepository.findAll(Sort.by(Sort.Direction.DESC, "id"));
List<BookingDTO> bookingDTOList = Utils.mapBookingListEntityToBookingListDTO(bookingList);
response.setStatusCode(200);
response.setMessage("successful");
response.setBookingList(bookingDTOList);
} catch (OurException e) {
response.setStatusCode(404);
response.setMessage(e.getMessage());
} catch (Exception e){
response.setStatusCode(500);
response.setMessage("Error while retrieving all the Bookings"+e.getMessage());
}
return response;
}
@Override
public Response cancelBooking(Long bookingId) {
Response response = new Response();
try {
bookingRepository.findById(bookingId).orElseThrow(()-> new OurException("Booking Does Not Exist"));
bookingRepository.deleteById(bookingId);
response.setStatusCode(200);
response.setMessage("successful");
} catch (OurException e) {
response.setStatusCode(404);
response.setMessage(e.getMessage());
} catch (Exception e){
response.setStatusCode(500);
response.setMessage("Error Cancelling a Booking"+e.getMessage());
}
return response;
}
// Method to check if a room is available for a given booking request
private boolean roomIsAvailable(Booking bookingRequest, List<Booking> existingBookings) {
// Stream through the list of existing bookings
return existingBookings.stream()
// Check if none of the existing bookings match the given conditions
.noneMatch(existingBooking ->
// Condition 1: Check if the check-in date of the booking request is the same as any existing booking's check-in date
bookingRequest.getCheckInDate().equals(existingBooking.getCheckInDate())
// Condition 2: Check if the check-out date of the booking request is before any existing booking's check-out date
|| bookingRequest.getCheckOutDate().isBefore(existingBooking.getCheckOutDate())
// Condition 3: Check if the check-in date of the booking request is after the check-in date and before the check-out date of any existing booking
|| (bookingRequest.getCheckInDate().isAfter(existingBooking.getCheckInDate())
&& bookingRequest.getCheckInDate().isBefore(existingBooking.getCheckOutDate()))
// Condition 4: Check if the booking request overlaps with any existing booking
|| (bookingRequest.getCheckInDate().isBefore(existingBooking.getCheckOutDate())
&& bookingRequest.getCheckOutDate().isAfter(existingBooking.getCheckInDate()))
// Condition 5: Check if the check-in date of the booking request is equal to the check-out date and the check-out date is equal to the check-in date of any existing booking
|| (bookingRequest.getCheckInDate().equals(existingBooking.getCheckOutDate())
&& bookingRequest.getCheckOutDate().equals(existingBooking.getCheckInDate()))
// Condition 6: Check if the check-in date of the booking request is equal to the check-out date and the check-out date is equal to the check-in date of the booking request
|| (bookingRequest.getCheckInDate().equals(existingBooking.getCheckOutDate())
&& bookingRequest.getCheckOutDate().equals(bookingRequest.getCheckInDate()))
);
}
}
BookingDTO.java
package com.Yash.Astoria.dto;
import com.Yash.Astoria.entities.Room;
import com.Yash.Astoria.entities.User;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDate;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BookingDTO {
private Long id;
private LocalDate checkInDate;
private LocalDate checkOutDate;
private int numOfAdults;
private int numOfChildren;
private int totalNumOfGuests;
private String bookingConfirmationCode;
private UserDTO user;
private RoomDTO room;
}
StackTrace in Console:
2024-08-19T22:35:34.930+05:30 INFO 19264 --- [Astoria] [nio-8081-exec-5] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
jakarta.validation.ConstraintViolationException: Validation failed for classes [com.Yash.Astoria.entities.Booking] during persist time for groups [jakarta.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='check out date must be in the future', propertyPath=checkOutDate, rootBeanClass=class com.Yash.Astoria.entities.Booking, messageTemplate='check out date must be in the future'}
]
at org.hibernate.boot.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:151)
at org.hibernate.boot.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:81)
at org.hibernate.action.internal.EntityIdentityInsertAction.preInsert(EntityIdentityInsertAction.java:201)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:79)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:670)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:291)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:272)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:322)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:391)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:305)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:224)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:137)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:175)
at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:93)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:77)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:54)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:757)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:741)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364)
at jdk.proxy4/jdk.proxy4.$Proxy132.persist(Unknown Source)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:319)
at jdk.proxy4/jdk.proxy4.$Proxy132.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:629)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy4/jdk.proxy4.$Proxy143.save(Unknown Source)
at com.Yash.Astoria.services.impl.BookingService.saveBooking(BookingService.java:60)
at com.Yash.Astoria.controllers.BookingController.saveBookings(BookingController.java:23)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.proceed(AuthorizationManagerBeforeMethodInterceptor.java:269)
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization(AuthorizationManagerBeforeMethodInterceptor.java:264)
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:197)
at org.springframework.security.config.annotation.method.configuration.PrePostMethodSecurityConfiguration$DeferringMethodInterceptor.invoke(PrePostMethodSecurityConfiguration.java:200)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:720)
at com.Yash.Astoria.controllers.BookingController$$SpringCGLIB$$0.saveBookings(<generated>)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)
at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
[postman screenshot 500 internal server error] (https://i.sstatic.net/8M5SUQsT.png)
Expectation: validating the condition that checkOutDate should be greater than checkInDate
Also, I have tried
@FutureOrPresent
annotation, and it gives the same error
The @Valid
annotation helped me to resolve this issue. I added this annotation to the method parameter as shown below:
public ResponseEntity<Response> saveBookings(@PathVariable("roomId") Long roomId,
@PathVariable("userId") Long userId,
@Valid
@RequestBody Booking bookingRequest) {
Also, I ensured that the checkOutDate
was sufficiently in the future relative to the system’s current date and followed the same timezone. This resolved the validation error, and the booking was successfully saved.
When used in a controller method parameter,
@Valid
ensures that the incoming request body is validated before the method is executed.