Server Centric Java Frameworks: Performance Comparison
Join the DZone community and get the full member experience.
Join For FreeThese days we are used to AJAX-intensive, sophisticated web frameworks. These frameworks provide us desktop style development into the Single Page Interface (SPI) paradigm. As you know there are two main types of frameworks, client-centric and server-centric. Each approach has pros and cons.
Testing the
performance of Java server-centric frameworks
In the server-centric view, state is managed in server. In some way the client is a sophisticated terminal of the server because most of visual decisions are taken on the server and some kind of visual rendering is done on the server (HTML generation as markup or embedded in JavaScript or more higher level code sent to the client). The main advantage is that data and visual rendering are together in the same memory space, avoiding custom client-server bridges for data communication and synchronization, typical of the client-centric approach.
This article only reviews Java server-centric frameworks.
In SPI, the web page is partially changed; that is, some HTML parts can be removed and some new HTML markup can be inserted. This approach obviously saves tons of bandwidth and computer power because the complete page is not rebuilt and not fully sent to the client when some page change happens.
A server-centric framework to be effective must send to the client ONLY the markup going to be changed or equivalent instructions in some form, when some AJAX event hits the server.
This article reviews how much effective most of the SPI Java web frameworks are on partial changes provided by the server. We are not interested in events with no server communication, that is, events with no (possible) server control.
How they are going to be measured
We are going to measure the amount of code that is sent to client regarding to the visual change performed in client.
For instance for a minor visual change (some new data) in a component we expect not much code from server, that is, the new markup needed as plain HTML, or embedded in JavaScript, or some high level instructions containing the new data to be visualized. Otherwise something seems wrong for instance the complete component or page zone is rebuilt, wasting bandwidth and client power (and maybe server power).
Because we will use public demos, we are not going to get a definitive and fine grain benchmark. But you will see very strong differences between frameworks.
The testing technique is very easy and everybody can do it with no special infrastructure, we just need FireFox and FireBug. In this test FireFox 3.6.8 and FireBug 1.5.4 are used.
The FireBug Console when "Show XMLHttpRequests" is enabled logs any AJAX request showing the server response.
The process is simple:
- The Console will be enabled before loading the page with the demo.
- Some clicks will drive some concrete component to the desired state.
- A final click will perform a small change in the component being analyzed.
- Then we will copy the output code of the AJAX request (HTML, XML, JavaScript ...) sent from server.
The more code the less effective, more bandwidth waste and client processing is needed.
We cannot measure the server power used because we need a deep knowledge of how the framework works in server, said this we can easily "suspect" the more code generated in server the more server power is wasted.
Frameworks tested
RichFaces, IceFaces, MyFaces/Trinidad, OpenFaces, PrimeFaces, Vaadin, ZK, ItsNat
ADF Faces is not tested because there is no longer a public live demo. Because ADF Faces is based on Trinidad, Trinidad analysis could be extrapolated to ADF Faces (?).
Update: NO, ADF Faces are very different to Trinidad.
Note before starting
Some frameworks seem to perform very well (regarding to this kind of test), that is, the ratio between visual change and amount of code is acceptable, but in some concrete cases (components) they "miserably" fail. This article tries to measure bad performant components.
RichFaces
Console must be enabled, configured and open as seen before.
- Open this tree demo (Ajax switch type)
- Expand "Baccara" node
- Expand "Grand Collection"
- Collapse "Grand Collection"
As you can see the child nodes below "Grand Collection" has been removed or hidden (FireBug's DOM inspector says they were removed).
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head></head><body><div xmlns:rich="http://richfaces.ajax4jsf.org/rich"><table border="0" cellpadding="0" cellspacing="0" class="rich-tree-node" id="j_id354:j_id355:0:1::j_id358"><tbody><tr id="j_id354:j_id355:0:1::j_id358:mainRow" onclick=" "><td class="rich-tree-node-handleicon rich-tree-h-ic-line-node" id="j_id354:j_id355:0:1::j_id358:handles" style="background-image:expression(this.parentNode.parentNode.parentNode.nextSibling.nextSibling ? 'url(/richfaces-demo/a4j/g/3_3_3.Finalorg.richfaces.renderkit.html.images.TreeLineNodeImage/DATB/eAH7!!!!72fXGBgYACWpBbU_.jsf)' : 'url(/richfaces-demo/a4j/g/3_3_3.Finalorg.richfaces.renderkit.html.images.TreeLineLastImage/DATB/eAH7!!!!72fXGBgYACWpBbU_.jsf)')"><div><a class="rich-tree-node-handle" href="#" id="j_id354:j_id355:0:1::j_id358:handle" onclick="var c = Tree.Item.findComponent(this); if (!c) return; c.fireExpansionEvent();;A4J.AJAX.Submit('j_id354',event,{'similarityGroupingId':'j_id354:j_id355:0:1::j_id358','parameters':{'j_id354:j_id355:0:1::j_id358AjaxExpanded':true,'j_id354:j_id355:0:1::j_id358NodeExpanded':'true'} } ); return false;"><img alt="" class="rich-tree-node-handleicon-collapsed" id="j_id354:j_id355:0:1::j_id358:handle:img:collapsed" src="/richfaces-demo/a4j/g/3_3_3.Finalorg.richfaces.renderkit.html.images.TreePlusImage/DATB/eAH7!!!!72fXGBgYACWpBbU_.jsf" style=";border:0" /><img alt="" class="rich-tree-node-handleicon-expanded" id="j_id354:j_id355:0:1::j_id358:handle:img:expanded" src="/richfaces-demo/a4j/g/3_3_3.Finalorg.richfaces.renderkit.html.images.TreeMinusImage/DATB/eAH7!!!!72fXGBgYACWpBbU_.jsf" style="display: none;;border:0" /></a></div></td><td class="rich-tree-node-icon rich-tree-h-ic-line-clp" id="j_id354:j_id355:0:1::j_id358:icon" rich:draggableoptions="{'parameters':{'dragSourceId':'j_id354:j_id355:0:1::j_id358','j_id354:j_id355:0:1::j_id358':'j_id354:j_id355:0:1::j_id358'} } " rich:dropzoneoptions="{} "><img alt="" class="rich-tree-h-ic-img" src="/richfaces-demo/images/tree/disc.gif" /></td><td class="rich-tree-node-text " id="j_id354:j_id355:0:1::j_id358:text" rich:highlightedclass="rich-tree-node-highlighted" rich:selectedclass="rich-tree-node-selected">Grand Collection</td></tr></tbody></table><div id="j_id354:j_id355:0:1::j_id358:childs" style="display: none;" class="rich-tree-node-children rich-tree-h-ic-line"></div><div id="j_id354:j_id355:script" class="rich-tree-h"><script type="text/javascript">$('j_id354:j_id355').component.refreshAfterAjax(['j_id354:j_id355:0:1::j_id358'] ,'')</script></div></div><div xmlns:rich="http://richfaces.ajax4jsf.org/rich"></div><div xmlns:rich="http://richfaces.ajax4jsf.org/rich"></div><div xmlns:rich="http://richfaces.ajax4jsf.org/rich"></div><div xmlns:rich="http://richfaces.ajax4jsf.org/rich"></div><meta name="Ajax-Update-Ids" content="j_id354:j_id355:0:1::j_id358,j_id354:j_id355:0:1::j_id358:childs,j_id354:j_id355:script" /><span id="ajax-view-state"><input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="j_id3" /></span><meta id="Ajax-Response" name="Ajax-Response" content="true" /><meta name="Ajax-Update-Ids" content="j_id354:j_id355:0:1::j_id358,j_id354:j_id355:0:1::j_id358:childs,j_id354:j_id355:script" /><span id="ajax-view-state"><input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="j_id3" /></span><meta id="Ajax-Response" name="Ajax-Response" content="true" /></body></html>
As you can see too much HTML code has been sent for not much of a visual change.
A more severe performance penalty:
- Open the Extended Data Table Demo
- On "State Name" paste "Alaska" (paste the name from clipboard), one row is shown
- Paste "Alabama" replacing "Alaska" (again paste from clipboard selecting Alaska first), again one different row is shown.
The answer (HTML code) is too big to put here, 3.474 bytes, if you inspect the result you will see a complete rewrite of the table including header.
IceFaces
- Open the Calendar demo
- Click on any different day
Something like this is the last AJAX response:
The answer (XML with metadata) is really big, 6.452 bytes, for a simple day change according to visual changes.
MyFaces/Trinidad
- Open this Tree Table Demo
- Expand node_0_0
- Expand node_0_0_0 (node node_0_0_0_ is shown)
- Collapse node_0_0_0 (hides/removes node_0_0_0_0)
The last AJAX response is too big to put here, 18.765 bytes, because is a complete rewrite of the tree component.
Update: a live demo of ADF Faces components is here and they seem to work fine as expected, that is, the ratio between code sent to the client and visual change is "correct" (in spite of HTML layout is very verbose the code sent to the client is almost the same to be displayed).
OpenFaces
- Open the Tree Table demo
- Expand "Re: Scalling an image"
- Expand the new child "Re: Scalling an image"
The last AJAX response is
<?xml version="1.0" encoding="UTF-8"?><ajaxResponse><head> </head><updatable id="subRows:2" type="portion" value="<tr><td><table cellspacing="0" cellpadding="0" class="o_cellWrapper"><tr><td class="o_treetable_indent"><div class="o_treetable_indent"></div></td><td class="o_treetable_indent"><div class="o_treetable_indent"></div></td><td class="o_treetable_indent"><div class="o_treetable_indent"></div></td><td class="o_treetable_indent"><div class="o_treetable_indent"></div></td><td><span class="treeTableText">Re: Scaling an image</span></td></tr></table></td><td><span class="treeTableText">Christian Smile</span></td><td><span class="treeTableText">Aug 3, 2007</span></td></tr>" data="{"structureMap":{"0":"1"}}" scripts="<script>O$.updateViewId('j_id3:j_id6');</script>" /></ajaxResponse>
This code is very reasonable according to the change (a new child node/table row).
Nevertheless some component miserably fails:
- Open the Data Table demo
- Select "AK" as "State", resulting one row.
- Replace with "AR", resulting again a new row
The last AJAX result is too big, 38.209 bytes, because is a complete rewrite of the table including headers.
PrimeFaces
The AJAX answers of all tested examples were very reasonable. Said this, PrimeFaces lacks of a "filtered table component" or similar, the Achilles's heel of other JSF implementations.
Update: As Cagatay Civici (one of the fathers of PrimeFaces) points out, PrimeFaces has a filltered table, this component works fine regarding to the ratio of visual change/code sent to client (try to do the same tests as prvious frameworks).
Vaadin
This is the first non-JSF framework.
Open the Tree single selection demo
Select "Dell OptiPlex GX240"
Click "Apply" button (no change is needed)
This is the last AJAX answer:
for(;;);[{"changes":[["change",{"format": "uidl","pid": "PID190"},["12",{"id": "PID190","immediate":true,"caption": "Hardware Inventory","selectmode": "single","nullselect":true,"v":{"action":"","selected":["2"],"expand":[],"collapse":[],"newitem":[]}},["node",{"caption": "Desktops","key": "1","expanded":true,"al":["1","2"]},["leaf",{"caption": "Dell OptiPlex GX240","key": "2","selected":true,"al":["1","2"]}],["leaf",{"caption": "Dell OptiPlex GX260","key": "3","al":["1","2"]}],["leaf",{"caption": "Dell OptiPlex GX280","key": "4","al":["1","2"]}]],["node",{"caption": "Monitors","key": "5","expanded":true,"al":["1","2"]},["leaf",{"caption": "Benq T190HD","key": "6","al":["1","2"]}],["leaf",{"caption": "Benq T220HD","key": "7","al":["1","2"]}],["leaf",{"caption": "Benq T240HD","key": "8","al":["1","2"]}]],["node",{"caption": "Laptops","key": "9","expanded":true,"al":["1","2"]},["leaf",{"caption": "IBM ThinkPad T40","key": "10","al":["1","2"]}],["leaf",{"caption": "IBM ThinkPad T43","key": "11","al":["1","2"]}],["leaf",{"caption": "IBM ThinkPad T60","key": "12","al":["1","2"]}]],["actions",{},["action",{"caption": "Add child item","key": "1"}],["action",{"caption": "Delete","key": "2"}]]]]], "meta" : {}, "resources" : {}, "locales":[]}]
It seems not very much, but if you review the code the entire tree is being rebuilt again.
ZK
Another non-JSF framework. In the last versions ZK embrace an hybrid approach, most of the visual logic is in client as JavaScript components, the server sends to the client high level commands to the high level client library (Vaadin is not different). I have not found a component sending too much code from client (according to the visual change) in ZK's demo.
ItsNat
The last framework studied, again a non-JSF framework.
In ItsNat the server keeps the same DOM state as in client and through DOM mutation events any change to the DOM in server automatically generates the JavaScript necessary to update the client accordingly.
- Open the demo
- Click on the handler of "Core" folder, the child nodes (11) are hidden.
Result code of the AJAX event:
itsNatDoc.addNodeCache(["cn_10","cn_14","0,1,1,0,0",["cn_15","cn_16"]]);
itsNatDoc.setAttribute2("cn_14","src","img/tree/tree_node_collapse.gif");
itsNatDoc.setAttribute2(["cn_15","cn_17","1"],"src","img/tree/tree_folder_close.gif");
itsNatDoc.setAttribute2(["cn_16","cn_18","1,0",["cn_19"]],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_20","1"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_21","2"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_22","3"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_23","4"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_24","5"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_25","6"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_26","7"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_27","8"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_28","9"],"style","display:none");
itsNatDoc.setAttribute2(["cn_19","cn_29","10"],"style","display:none");
No surprises.
Another test
Open this Tree demo
Click on "Insert Child". A new child node ("Actors") is inserted and a new log message is added.
AJAX result code:
itsNatDoc.removeAttribute2(["cn_15","cn_39","13"],"style");
itsNatDoc.setInnerHTML2("cn_39","<div><input> click</div><div>javax.swing.event.TreeModelEvent 13802934 path [Grey's Anatomy, Actors] indices [ 4 ] children [ Actors ]</div>");
var child = itsNatDoc.doc.createElement("li");
itsNatDoc.setAttribute(child,"style","padding:1px;");
itsNatDoc.appendChild2(["cn_17","cn_40","0,1,1,1",["cn_41","cn_42"]],child);
itsNatDoc.setInnerHTML(child,"<span><img src=\"img/tree/tree_node_expanded.gif\"><img src=\"img/tree/tree_folder_open.gif\"><span><b>Label</b></span></span>\n <ul style=\"list-style-type: none;\"></ul>\n ");
itsNatDoc.setTextData2(["cn_40","cn_43","4,0,2,0",["cn_44","cn_45"]],null,"Actors");
itsNatDoc.setAttribute2(["cn_45","cn_46","0"],"style","display:none");
itsNatDoc.setAttribute2(["cn_45","cn_47","1"],"src","img/tree/gear.gif");
Again no surprises.
And the Winner is...
There is no winner because only some components have been tested.
Having said this, apparently the only JSF implementation free of serious performance penalties is PrimeFaces.
In non-JSF frameworks using a very high level JS library like in Vaadin or ZK (PrimeFaces?) helps very much to reduce the network bandwidth (in spite of the fact that some components in Vaadin have serious performance problems), this cannot be said for client performance because in ItsNat the exact JS DOM code is sent to the client.
On the other side a high level JS library complicates custom component development (beyond composition) because the server does not help very much but this is another story, and another article.
Opinions expressed by DZone contributors are their own.
Comments