DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Proper Java Exception Handling
  • Postgres JSON Functions With Hibernate 6
  • Postgres JSON Functions With Hibernate 5
  • Generating MongoDB Annotations for Java POJOs from JSON Schema Using the JSONSchema2Pojo Plugin

Trending

  • Podman Desktop Review
  • A Better Web3 Experience: Account Abstraction From Flow (Part 2)
  • What Is Kubernetes RBAC and Why Do You Need It?
  • Generative AI: A New Tool in the Developer Toolbox
  1. DZone
  2. Coding
  3. Languages
  4. Using Java WebSockets, JSR 356, and JSON mapped to POJO's

Using Java WebSockets, JSR 356, and JSON mapped to POJO's

Gerard Davison user avatar by
Gerard Davison
·
May. 02, 13 · Interview
Like (0)
Save
Tweet
Share
21.43K Views

Join the DZone community and get the full member experience.

Join For Free

I have been playing around with Tyrus, the reference implementation of the JSR 356 WebSocket for Java spec. Because I was looking at test tooling I was interested in running both the client and the server side in Java. So no HTML5 in this blog post I am afraid.

In this example we want to sent JSON back and forth and because I am old fashioned like that I want to be able to bind to a POJO object. I am going to use Jackson for this so my maven file looks like this:

<dependencies>
    <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-client</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-server</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-container-grizzly</artifactId>
        <version>1.0-rc3</version>
    </dependency>
    

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.2.0</version>
    </dependency>

  </dependencies>


So the first things we need to do is to define an implementations of the Encode/Decoder interfaces to do this work for us. This is going to do some simple reflection to workout what the bean class is. Like with JAX-WS it is easier to put them on the same class. Note that we use the streaming version of the interface and are only handling text content. (Ignoring the ability to send binary data for the moment)
    package websocket;  
      
    import com.fasterxml.jackson.databind.ObjectMapper;  
      
    import java.io.IOException;  
    import java.io.Reader;  
    import java.io.Writer;  
      
    import java.lang.reflect.ParameterizedType;  
    import java.lang.reflect.Type;  
      
    import javax.websocket.DecodeException;  
    import javax.websocket.Decoder;  
    import javax.websocket.EncodeException;  
    import javax.websocket.Encoder;  
    import javax.websocket.EndpointConfig;  
      
    public abstract class JSONCoder<T>  
      implements Encoder.TextStream<T>, Decoder.TextStream<T>{  
      
      
        private Class<T> _type;  
          
        // When configured my read in that ObjectMapper is not thread safe  
        //  
        private ThreadLocal<ObjectMapper> _mapper = new ThreadLocal<ObjectMapper>() {  
      
            @Override  
            protected ObjectMapper initialValue() {  
                return new ObjectMapper();  
            }  
        };  
          
      
        @Override  
        public void init(EndpointConfig endpointConfig) {  
          
            ParameterizedType $thisClass = (ParameterizedType) this.getClass().getGenericSuperclass();  
            Type $T = $thisClass.getActualTypeArguments()[0];  
            if ($T instanceof Class) {  
                _type = (Class<T>)$T;  
            }  
            else if ($T instanceof ParameterizedType) {  
                _type = (Class<T>)((ParameterizedType)$T).getRawType();  
            }  
        }  
      
        @Override  
        public void encode(T object, Writer writer) throws EncodeException, IOException {  
            _mapper.get().writeValue(writer, object);  
        }  
      
        @Override  
        public T decode(Reader reader) throws DecodeException, IOException {  
            return _mapper.get().readValue(reader, _type);  
        }  
      
        @Override  
        public void destroy() {  
              
        }  
      
    }  
The bean class is really quite simple with a static subclass of the Coder that we can use later.
    package websocket;  
      
    public class EchoBean {  
          
          
        public static class EchoBeanCode extends  
           JSONCoder<EchoBean> {  
              
        }  
          
          
        private String _message;  
        private String _reply;  
      
      
        public EchoBean() {  
              
        }  
      
        public EchoBean(String _message) {  
            super();  
            this._message = _message;  
        }  
      
      
        public void setMessage(String _message) {  
            this._message = _message;  
        }  
      
        public String getMessage() {  
            return _message;  
        }  
      
      
        public void setReply(String _reply) {  
            this._reply = _reply;  
        }  
      
        public String getReply() {  
            return _reply;  
        }  
      
    }  
So new we need to implement our server endpoint, you can go one of two way either annotating a POJO or extending Endpoint. I am going with the first for the server and the second for the client. Really all this service does is to post the message back to the client. Note the registration of the encode and decoder. The same class in this case.
    package websocket;  
      
    import java.io.IOException;  
      
    import javax.websocket.EncodeException;  
    import javax.websocket.EndpointConfig;  
    import javax.websocket.OnMessage;  
    import javax.websocket.OnOpen;  
    import javax.websocket.Session;  
    import javax.websocket.server.ServerEndpoint;  
    import static java.lang.System.out;  
      
    @ServerEndpoint(value="/echo",  
                    encoders = {EchoBean.EchoBeanCode.class},  
                    decoders = {EchoBean.EchoBeanCode.class})  
    public class EchoBeanService  
    {  
          
        @OnMessage  
        public void echo (EchoBean bean, Session peer) throws IOException, EncodeException {  
            //  
            bean.setReply("Server says " + bean.getMessage());  
            out.println("Sending message to client");  
            peer.getBasicRemote().sendObject(bean);  
        }  
      
        @OnOpen  
        public void onOpen(final Session session, EndpointConfig endpointConfig) {  
            out.println("Server connected "  + session + " " + endpointConfig);  
        }  
    }  
Lets look at a client bean, this time extending the standard Endpoint class and adding a specific listener for a message. In this case when the message is received the connection is simply closed to make our test case simple. In the real world managing this connection would obviously be more complicated.
    package websocket;  
      
    import java.io.IOException;  
      
    import javax.websocket.ClientEndpoint;  
    import javax.websocket.CloseReason;  
    import javax.websocket.EncodeException;  
    import javax.websocket.Endpoint;  
    import javax.websocket.EndpointConfig;  
    import javax.websocket.MessageHandler;  
    import javax.websocket.Session;  
      
    import static java.lang.System.out;  
      
    @ClientEndpoint(encoders = {EchoBean.EchoBeanCode.class},  
                    decoders = {EchoBean.EchoBeanCode.class})  
    public class EchoBeanClient   
      extends Endpoint  
    {  
        public void onOpen(final Session session, EndpointConfig endpointConfig) {  
      
            out.println("Client Connection open "  + session + " " + endpointConfig);  
              
            // Add a listener to capture the returning event  
            //  
              
            session.addMessageHandler(new MessageHandler.Whole<echobean>() {  
      
                @Override  
                public void onMessage(EchoBean bean) {  
                    out.println("Message from server : " + bean.getReply());  
                      
                    out.println("Closing connection");  
                    try {  
                        session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "All fine"));  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
            });  
              
            // Once we are connected we can now safely send out initial message to the server  
            //  
              
            out.println("Sending message to server");  
            try {  
                EchoBean bean = new EchoBean("Hello");  
                session.getBasicRemote().sendObject(bean);  
            } catch (IOException e) {  
                e.printStackTrace();  
            } catch (EncodeException e) {  
                e.printStackTrace();  
            }  
      
        }  
    }  
    </echobean>  
Now running the WebSocket standalone is really quite straightforward with Tyrus, you simple instantiate a Server and start it. Be aware this starts daemon threads so you need to make sure if this is in a main method that you do something to keep the JVM alive.
    import org.glassfish.tyrus.server.Server;  
      
    Server server = new Server("localhost", 8025, "/", EchoBeanService.class);  
    server.start();  
So the client is relatively simple; but as we are doing the declarative method we need to explicitly register the encoders and decoders when registering the client class.
    import javax.websocket.ClientEndpointConfig;  
    import javax.websocket.Decoder;  
    import javax.websocket.Encoder;  
    import javax.websocket.Session;  
      
    import org.glassfish.tyrus.client.ClientManager;  
      
      
    // Right now we have to create a client, which will send a message then close  
    // when it has received a reply  
    //  
      
    ClientManager client = ClientManager.createClient();  
    EchoBeanClient beanClient = new EchoBeanClient();  
      
    Session session = client.connectToServer(  
            beanClient,   
            ClientEndpointConfig.Builder.create()  
             .encoders(Arrays.<Class<? extends Encoder>>asList(EchoBean.EchoBeanCode.class))  
             .decoders(Arrays.<Class<? extends Decoder>>asList(EchoBean.EchoBeanCode.class))  
             .build(),  
            URI.create("ws://localhost:8025/echo"));  
              
              
    // Wait until things are closed down  
              
    while (session.isOpen()) {  
        out.println("Waiting");  
        TimeUnit.MILLISECONDS.sleep(10);  
    }  
Now the output of this looks like the following:
Server connected SessionImpl{uri=/echo, id='e7739cc8-1ce5-4c26-ad5f-88a24c688799', endpoint=EndpointWrapper{endpointClass=null, endpoint=org.glassfish.tyrus.core.AnnotatedEndpoint@1ce5bc9, uri='/echo', contextPath='/'}} javax.websocket.server.DefaultServerEndpointConfig@ec120d
Waiting
Client Connection open SessionImpl{uri=ws://localhost:8025/echo, id='7428be2b-6f8a-4c40-a0c4-b1c8b22e1338', endpoint=EndpointWrapper{endpointClass=null, endpoint=websocket.EchoBeanClient@404c85, uri='ws://localhost:8025/echo', contextPath='ws://localhost:8025/echo'}} javax.websocket.DefaultClientEndpointConfig@15fdf14
Sending message to server
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Sending message to client
Message from server : Server says Hello
Closing connection
Waiting

Interestingly the first time this is run the there is a pause, I suspect this is due to Jackson setting itself up but I haven't had time to profile. I did find that this long delay on occurred on the first post - although obviously this is going to be slower than just passing plain text messages in general. Whether the different is significant to you depends on your application.

It would be interesting to compare the performance of the plain text with a JSON stream API such as that provided by the new JSR and of course the version that binds those values to a JSON POJO. Something for another day perhaps.


JSON Java (programming language) WebSocket

Published at DZone with permission of Gerard Davison, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Proper Java Exception Handling
  • Postgres JSON Functions With Hibernate 6
  • Postgres JSON Functions With Hibernate 5
  • Generating MongoDB Annotations for Java POJOs from JSON Schema Using the JSONSchema2Pojo Plugin

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: