I don't know what this says about my memory, but I was doing a little JAX-RS programming today and when I googled to remind myself of the particular feature I was using, I ran into my own blog post about that feature. It took me a moment to recall that I had written that earlier blog post. I wonder if I'll forget writing this one as well?

Anyhow, both that post and the other one I found, written by JAX-RS Spec Lead Pavel Bucek <https://blogs.oracle.com/pavelbucek/jax-rs-21-server-sent-events> are now stale with respect to the final version of JAX-RS 2.1. I looked into the GlassFish Samples for a similar and simple example, but found none, so I took the opportunity to add two samples based on updated content from Pavel's and my blog posts. Please clone it to see the full source code. This blog post documents these two super simple samples.

The Simplest JAX-RS SSE Example

  1. @Path("/")
  2. public class SSESimpleResource {
  3.  
  4.     @Resource
  5.     private ManagedExecutorService executor;
  6.  
  7.     @GET
  8.     @Path("eventStream")
  9.     @Produces(MediaType.SERVER_SENT_EVENTS)
  10.     public void eventStream(
  11.             @Context SseEventSink eventSink,
  12.             @Context Sse sse) {
  13.         executor.execute(() -> {
  14.             try (SseEventSink sink = eventSink) {
  15.                 eventSink.send(sse.newEventBuilder().data(String.class, "event1").build());
  16.                 eventSink.send(sse.newEventBuilder().data(String.class, "event2").build());
  17.                 eventSink.send(sse.newEventBuilder().data(String.class, "event3").build());
  18.             }
  19.             catch (Throwable e) {
  20.                 e.printStackTrace(System.out);
  21.             }
  22.         });
  23.  
  24.     }
  25. }

Line 4 is a magic injection of the ManagedExecutorService. This class is only available in the full profile of Java EE, not the web profile. This sort of service is essential for doing SSE because it's the easiest way to hand off the socket for the incoming SSE subscription request to a service that can handle the scale of such a resource intensive thing.

Line 9 and neighboring annotations make it so the HTTP GET Request to "/eventStream" will return content-type text/event-stream. Lines 11 and 12 are more magic to get handles to the JAX-RS 2.1 APIs for SSE. SseEventSink lets you send stuff down to the browser. Sse is a factory for other classes in the SSE API. Line 14 is a try-with-resources that lets the sink be autoclosable.

Let's take a look at the corresponding HTML.

  1. <script type="text/javascript">
  2.     if(typeof(EventSource) !== "undefined") {
  3.         var source = new EventSource("app/eventStream");
  4.         source.onmessage = function(event) {
  5.             document.getElementById("result").innerHTML += event.data + "<br>";
  6.         };
  7.     } else {
  8.         document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
  9.     }
  10. </script>
  11.  
  12. <h1>SSE sample</h1>
  13.  
  14. <div id="result"></div>
  15.  

The JavaScript on lines 1 - 10 connects to the server when the page loads and installs an SSE event handler that appends the data of each SSE to the div on line 14.

Broadcasting to Multiple Clients

  1. @Path("/")
  2. public class SSEBroadcastResource {
  3.  
  4.     @Context Sse sse;
  5.     private static SseBroadcaster sseBroadcaster;
  6.  
  7.     @PostConstruct
  8.     public void postConstruct() {
  9.         getBroadcaster(sse);
  10.     }
  11.  
  12.     private static SseBroadcaster getBroadcaster(Sse sse) {
  13.         if (null == sseBroadcaster) {
  14.             sseBroadcaster = sse.newBroadcaster();
  15.         }
  16.         return sseBroadcaster;
  17.     }
  18.  
  19.     @GET
  20.     @Path("subscribe")
  21.     @Produces(MediaType.SERVER_SENT_EVENTS)
  22.     public void subscribe(@Context SseEventSink eventSink) {
  23.         eventSink.send(sse.newEvent("welcome!"));
  24.         getBroadcaster(sse).register(eventSink);
  25.     }
  26.  
  27.     @POST
  28.     @Path("broadcast")
  29.     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  30.     public void broadcast(@FormParam("data") String event) {
  31.         getBroadcaster(sse).broadcast(sse.newEventBuilder().data(String.class, event).build());
  32.     }
  33. }

Another example in Pavel's Blog that didn't quite work as written is the SseBroadcaster example. Above is one that works. Note the use of the static getBroadcaster() method and forcing it to be pre-invoked before the resource is put into service through the use of @PostConstruct on lines 7 - 10.

As with the first example, the GET request sets up the SSE, sending an initial event. The new hting is handing off the servicing of the SSE to the handy SseBroadcaster on line 24.

This resource also listens for POST requests, on lines 27 - 32. It takes the form data and simply broadcasts it out to the SSE. The HTML is next.

  1. <script type="text/javascript">
  2.     var i = 1;
  3.     if(typeof(EventSource) !== "undefined") {
  4.         var source = new EventSource("app/subscribe");
  5.         source.onmessage = function(event) {
  6.             document.getElementById("result").innerHTML += event.data + "<br>";
  7.             document.getElementById("input").value = i++ + " " +
  8.                     navigator.userAgent;
  9.         };
  10.     } else {
  11.         document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
  12.     }
  13.  
  14.     function submit() {
  15.         var params = "data=" + document.getElementById("input").value;
  16.         var url = document.getElementById("form").action;
  17.         var xhr = new XMLHttpRequest();
  18.         xhr.open("POST", url);
  19.         xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  20.         xhr.send(params);
  21.     }
  22.  
  23. <form id="form" method="POST" action="app/broadcast"  >
  24.     <input id="input" type="hidden" name="data">
  25. </form>
  26.  
  27. <button type="button" onclick="submit()">POST</button>
  28.  
  29. <h1>SSE sample</h1>
  30.  
  31. <div id="result"></div>
  32.  

Lines 1 - 12 are mostly the same as in the first example. Lines 14 - 20 are new. The submit() function uses Ajax to send a POST to the server (lines 23 - 28 in the Java listing). The value of the data form data comes from the hidden field, and is initialized on line 7. I just use navigator.userAgent for novelty.

Each click of the POST button causes a POST to be sent to the server, which in turn causes an SSE to be sent back to the client. For extra fun, you could open up two tabs on the same page and see that pressing POST on one causes the data to be updated on both pages.

Thanks to Pavel Bucek and Santigo Pericas-Geertsen for stewarding the JAX-RS community to deliver a very useful piece of Java EE 8.