Java REST JAX-RS 2.0 – How To Handle Date, Time and Timestamp Data Types
Be it X-Form-Urlencoded or JSON HTTP post to a REST resource end-point, there is no specific “data type” for date or time related data. Most developers will have these data posted as “String” or just simply convert them to Unix timestamp value (e.g. 1435061152). But, as developers implement more and more end-point methods, codes to parse of date, time and timestamp string representation values to actual java.sql.Date or java.util.Date will be repetitive (and boring). So, the intention of this article is to show how to implement a custom data type for handling date and time related string values in JAX-RS 2.0 REST end-point method parameters.
Compatibility
The codes were tested with Payara 4.1 and Wildfly 8.2. For the other rest of the application servers and servlet containers, JAX-RS 2.0 libraries / Java EE 7 compatibility is required to run this.
Sample Application
To demonstrate this, let’s build a sample application that has a JAX-RS REST resource end-point which takes custom data type object classes through @FormParam parameter values and converting them to java.sql.Date, java.sql.Time, java.sql.Timestamp and java.util.Date for convenience.
Example HTTP POST Request
Let’s say a HTTP POST of the below URL is made (with “SampleApplication” as the application name and therefore, the context):
http://<hostname>:<port>/SampleApplication/rest-api/request-handler/post-request-with-dates-and-time/
As for the HTTP parameters to be posted along with this URL, they are:
Post Parameters | Value (String) | SimpleDateFormat pattern | Custom Data Type Class Name |
---|---|---|---|
date_field | 1948-05-15 | yyyy-MM-dd | RESTDateParam |
time_field | 3:23PM | h:mma | RESTTimeParam |
timestamp_field | 1979-10-11T14:45:00 | yyyy-MM-dd’T’HH:mm:ss | RESTTimestampParam |
timestamp_with_tzd_field | 1979-10-11T14:45:00+0800 | yyyy-MM-dd’T’HH:mm:ssZ | RESTTimestampWithTZDParam |
Implementing The Custom Data Type Classes
Parsing the date string value and converting it to java.sql.Date
First, let’s write a custom data type class that handles the parameter “date_field“, which parses the string representation of date in the format ‘yyyy-MM-dd‘ and turning it to java.sql.Date.
Codes for RESTDateParam.java
package com.developerscrappad; import java.text.ParseException; import java.text.SimpleDateFormat; import javax.ws.rs.WebApplicationException; public class RESTDateParam { // Declare the date format for the parsing to be correct private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" ); private java.sql.Date date; /** * This is the default constructor which must take in one string parameter. * The parameter is no other than the one passed in through the REST * end-point. We'll see it later... */ public RESTDateParam( String dateStr ) throws WebApplicationException { try { date = new java.sql.Date( df.parse( dateStr ).getTime() ); } catch ( final ParseException ex ) { // Wrap up any expection as javax.ws.rs.WebApplicationException throw new WebApplicationException( ex ); } } /** * This is a getter method which returns the parsed date string value as * java.sql.Date * */ public java.sql.Date getDate() { return date; } /** * For convenience of result checking */ @Override public String toString() { if ( date != null ) { return date.toString(); } else { return ""; } } }
Code Explanation
Here, we first define the appropriate date format e.g. “yyyy-MM-dd” for SimpleDateFormat to parse the date string. Once the constructor had been invoked and after the conversion, we can then grab the java.sql.Date object through the getDate() method. Besides java.sql.Date, you may want the resulting object to be either java.util.Date or java.util.Calendar and that’s fine, which largely depends on the application specifics. Here, since we don’t keep extra information of time and time zone, just a plain java.sql.Date is good enough.
Like so for the rest of the custom data type classes below.
Parsing the time string value (with AM/PM indicator) and converting it to java.sql.Time
Codes for RESTTimeParam.java
package com.developerscrappad; import java.text.ParseException; import java.text.SimpleDateFormat; import javax.ws.rs.WebApplicationException; public class RESTTimeParam { private static final SimpleDateFormat df = new SimpleDateFormat( "h:mma" ); private java.sql.Time time; public RESTTimeParam( String timeStr ) throws WebApplicationException { try { time = new java.sql.Time( df.parse( timeStr ).getTime() ); } catch ( final ParseException ex ) { throw new WebApplicationException( ex ); } } public java.sql.Time getTime() { return time; } @Override public String toString() { if ( time != null ) { return time.toString(); } else { return ""; } } }
Parsing the date and time string value and converting it to java.sql.Timestamp
Codes for RESTTimestampParam.java
package com.developerscrappad; import java.text.ParseException; import java.text.SimpleDateFormat; import javax.ws.rs.WebApplicationException; public class RESTTimestampParam { private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" ); private java.sql.Timestamp timestamp; public RESTTimestampParam( String timestampStr ) throws WebApplicationException { try { timestamp = new java.sql.Timestamp( df.parse( timestampStr ).getTime() ); } catch ( final ParseException ex ) { throw new WebApplicationException( ex ); } } public java.sql.Timestamp getTimestamp() { return timestamp; } @Override public String toString() { if ( timestamp != null ) { return timestamp.toString(); } else { return ""; } } }
Parsing the time string value (with time zone data) and converting it to java.util.Date (with time zone information)
Codes for RESTTimestampWithTZDParam.java
package com.developerscrappad; import java.text.ParseException; import java.text.SimpleDateFormat; import javax.ws.rs.WebApplicationException; public class RESTTimestampWithTZDParam { private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" ); private java.util.Date date; public RESTTimestampWithTZDParam( String dateTimeStr ) throws WebApplicationException { try { date = new java.util.Date( df.parse( dateTimeStr ).getTime() ); } catch ( final ParseException ex ) { throw new WebApplicationException( ex ); } } public java.util.Date getDate() { return date; } @Override public String toString() { if ( date != null ) { return date.toString(); } else { return ""; } } }
Implementing the REST Resource End-Point
So after the necessary custom data type classes to handle various formats of date and time were defined. The REST resource end-point method will now be able to use these classes to encapsulate various data format given. All that is to do is to use it directly as the data type of the end-point method arguments. For example:
// ... @POST @Path( "/path-root/path-value" ) public Response methodThatHandlesPostRequest( @FormParam( "date_field" ) RESTDateParam dateField ) { // The rest of the implementation... } // ...
Let’s take a look at a complete JAX-RS 2.0 REST Resource End-Point implementation example.
Codes for RESTResource.java
package com.developerscrappad; import javax.json.Json; import javax.ws.rs.FormParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; @Path( "request-handler" ) public class RESTResource { @POST @Path( "post-request-with-custom-param-data-type" ) @Produces( "application/json" ) public Response postRequestWithCustomParamDataType( @FormParam( "date_field" ) RESTDateParam dateField, // Put the custom data type to good use @FormParam( "time_field" ) RESTTimeParam timeField, @FormParam( "timestamp_field" ) RESTTimestampParam timestampField, @FormParam( "timestamp_with_tzd_field" ) RESTTimestampWithTZDParam tsWithTZDField ) { // Output these data as JSON as server response String jsonResult = Json.createObjectBuilder() .add( "data_submitted", Json.createObjectBuilder() .add( "date_field", dateField.toString() ) .add( "time_field", timeField.toString() ) .add( "timestamp_field", timestampField.toString() ) .add( "timestamp_with_tzd_field", tsWithTZDField.toString() ) ).build().toString(); return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonResult ).build(); } /** * Say NO to result caching */ protected ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) { CacheControl cc = new CacheControl(); cc.setNoCache( true ); cc.setMaxAge( -1 ); cc.setMustRevalidate( true ); return Response.status( status ).cacheControl( cc ); } }
Not forgetting the initiating REST Application class that extends javax.ws.rs.core.Application…
Codes for RESTApplication
package com.developerscrappad; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath( "rest-api" ) public class RESTApplication extends Application { public Set<Class<?>> getClasses() { return new HashSet<Class<?>>( Arrays.asList( RESTResource.class ) ); } }
Testing through a HTML Client with jQuery Ajax POST
To test the custom data type classes, a simple HTML page was written with the use of jQuery, which performs an ajax HTTP POST to the end-point URL. Just package the below HTML file as part of the web app to be deployed together for testing. Please deployed this to the appropriate application server or servlet container.
Codes for post-with-custom-param-data-type.html
<!DOCTYPE html> <html> <head> <title>Date, Time and Timestamp HTTP Post</title> </head> <body> <div>Date Field: <input id="dateField" type="text" value="1948-05-15" /> (format must be 'yyyy-MM-dd')</div> <div>Time Field: <input id="timeField" type="text" value="3:23PM" /> (format must be 'h:mma')</div> <div>Timestamp Field: <input id="timestampField" type="text" value="1979-10-11T14:45:00" style="width: 200px;" /> (format must be 'yyyy-MM-ddTHH:mm:ss')</div> <div>Timestamp With Time Zone Field: <input id="timestampWithTZDField" type="text" value="1979-10-11T14:45:00+0800" style="width: 200px;" /> (format must be 'yyyy-MM-ddTHH:mm:ss+/-HHmm')</div> <div><input type="button" value="Submit" onclick="javascript:performSubmit();" /></div> <br /><br /> <div id="resultJson"></div> <script src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9hamF4Lmdvb2dsZWFwaXMuY29tL2FqYXgvlibs/jquery/1.11.3/jquery.min.js"></script> <script type="text/javascript"> var $ = jQuery.noConflict(); function performSubmit() { $.ajax( { url: "rest-api/request-handler/post-request-with-custom-param-data-type", type: "POST", data: { "date_field": $.trim( $( "#dateField" ).val() ), "time_field": $.trim( $( "#timeField" ).val() ), "timestamp_field": $.trim( $( "#timestampField" ).val() ), "timestamp_with_tzd_field": $.trim( $( "#timestampWithTZDField" ).val( ) ) }, success: function ( resultObj, textStatus, xhr ) { $( "#resultJson" ).html( "<h2>Post Result (JSON)</h2>" + JSON.stringify( resultObj ) ); }, error: function ( xhr, textStatus, errorThrown ) { $( "#resultJson" ).html( "Something went wrong, status " + xhr.status ); } } ); } </script> </body> </html>
The Result
Once the ‘Submit‘ button is clicked, the HTML client should receive the rightful JSON response from the REST Resource End-Point method (path: post-request-with-custom-param-data-type) and be displayed at the bottom of the screen.
That’s all. Thank you for reading and hope it helps.
Reference: | Java REST JAX-RS 2.0 – How To Handle Date, Time and Timestamp Data Types from our JCG partner Max Lam at the A Developer’s Scrappad blog. |
Nice article but I would like to suggest one change. SimpleDateFormat is not thread-safe, so it is not recommended to have its instance at class level i.e. private static. New instance should be created in the constructor instead.
Ah, pardon me…thanks for pointing the fault. I have changed that on my original post at: http://www.developerscrappad.com/2308/java/java-ee/rest-jax-rs/java-rest-jax-rs-2-0-how-to-handle-date-time-and-timestamp-data-types/
I believe that throwing exception when parsing in your constructor is not a good idea. Let the controller decide whether using default value or return exception. I also suggest you to use abstract class at example bellow and implement your own concrete data-type. Here is my example: RestParam DateTimeParam extedns RestParam. Hope this help. /** * * @author sangdn * @param Actualy Param data type */ public abstract class RestParam { protected T _value; public T getValue() { return _value; } protected final String _param; /** * * @param param String param in request. */ public RestParam(String param) { _param… Read more »
Truly appreciate all of the comments. However, back to my original intention of this article, I’ve written this article to be as straight forward as possible (without much abstraction, much design patterns and other bells and whistles to make the code “robust”), so that some poor souls out there could just read, understand, use it and get on with their lives. For any readers to use their design witchcrafts to impress their bosses/clients is solely their freedom of choice.
Thanks for posting this. It helped me a lot!
give the error something went wrong 404
plz help me i run this project then go to error side ot go to succes