Enterprise Java

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 ParametersValue (String)SimpleDateFormat patternCustom Data Type Class Name
date_field1948-05-15yyyy-MM-ddRESTDateParam
time_field3:23PMh:mmaRESTTimeParam
timestamp_field1979-10-11T14:45:00yyyy-MM-dd’T’HH:mm:ssRESTTimestampParam
timestamp_with_tzd_field1979-10-11T14:45:00+0800yyyy-MM-dd’T’HH:mm:ssZRESTTimestampWithTZDParam

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.

Post Result
Post Result

That’s all. Thank you for reading and hope it helps.

Max Lam

Born and currently resides in Malaysia, a seasoned Java developer whom had held positions as Senior Developer, Consultant and Technical Architect in various enterprise software development companies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Kedar Joshi
Kedar Joshi
8 years ago

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.

Max Lam
8 years ago

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/

Sang Dang
Sang Dang
8 years ago

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 »

Max Lam
8 years ago

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.

Marcelo
Marcelo
8 years ago

Thanks for posting this. It helped me a lot!

narendra pratap singh
narendra pratap singh
5 years ago

give the error something went wrong 404

narendra pratap singh
narendra pratap singh
5 years ago

plz help me i run this project then go to error side ot go to succes

Back to top button