Wednesday, April 2, 2014

Java frameworks, encapsulation and code bloat, i.e., the javabean specification failed us!

I have recently run into the following error in my JAX-RS application running in Jersey 2.6 (on Tomcat 6):

 Failed to generate the schema for the JAX-B elements  
 com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions  
 com.tinypass.rest.v3.framework.dto.RestDTOFromEntity does not have a no-arg default constructor.  
   this problem is related to the following location:  
    at com.tinypass.rest.v3.framework.dto.RestDTOFromEntity  
    at com.tinypass.rest.v3.dto.AppDTO  
    at com.tinypass.rest.v3.dto.AppSensitiveDTO  
   at com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:91)  
   at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:436)  
   at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.(JAXBContextImpl.java:277)  
   at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1100)  
   at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:143)  
   at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:110)  
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
   at java.lang.reflect.Method.invoke(Method.java:597)  
   at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)  
   at javax.xml.bind.ContextFinder.find(ContextFinder.java:376)  
   at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)  
   at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)  
   at org.glassfish.jersey.server.wadl.internal.generators.WadlGeneratorJAXBGrammarGenerator.buildModelAndSchemas(WadlGeneratorJAXBGrammarGenerator.java:368)  
   at org.glassfish.jersey.server.wadl.internal.generators.WadlGeneratorJAXBGrammarGenerator.createExternalGrammar(WadlGeneratorJAXBGrammarGenerator.java:317)  
   at org.glassfish.jersey.server.wadl.internal.WadlBuilder.generate(WadlBuilder.java:121)  
   at org.glassfish.jersey.server.wadl.internal.WadlApplicationContextImpl.getApplication(WadlApplicationContextImpl.java:143)  
   at org.glassfish.jersey.server.wadl.internal.WadlApplicationContextImpl.getApplication(WadlApplicationContextImpl.java:162)  
   at org.glassfish.jersey.server.wadl.processor.WadlModelProcessor$OptionsHandler.apply(WadlModelProcessor.java:138)  
   at org.glassfish.jersey.server.wadl.processor.WadlModelProcessor$OptionsHandler.apply(WadlModelProcessor.java:120)  
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
   at java.lang.reflect.Method.invoke(Method.java:597)  
   at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)  
   at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:151)  
   at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:171)  
   at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:152)  
   at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:104)  
   at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:406)  
   at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:350)  
   at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:106)  
   at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:259)  
   at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)  
   at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)  
   at org.glassfish.jersey.internal.Errors.process(Errors.java:315)  
   at org.glassfish.jersey.internal.Errors.process(Errors.java:297)  
   at org.glassfish.jersey.internal.Errors.process(Errors.java:267)  
   at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:319)  
   at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:236)  
   at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1028)  
   at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)  
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)  
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)  
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:219)  
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)  
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)  
   at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)  
   at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)  
   at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)  
   at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)  
   at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)  
   at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)  
   at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)  
   at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)  
   at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)  
   at java.lang.Thread.run(Thread.java:662)  


It looks like this is happening when the user tries to retrieve the application.wadl file. This error occurs because the class RestDTOFromEntity does not have a default constructor. For some reason JAXB requires this no-arg constructor to generate the WADL file. This can be fixed by adding a private no-arg constructor to RestDTOFromEntity.

The absence of a default constructor for that class is motivated by design choices made to properly encapsulate the contents of this class. It's a safe way to force programmers to provide the parameters required to the constructor (which will appropriately throw in case of nulls etc.). A non default constructor would be required if the class was immutable (which is not in this case). This and other encapsulation choices are very common in "academic" object oriented design.

In this specific example, the solution does not break the encapsulation of the class, however it does increase the code bloat with a private no-arg constructor that sits there for no apparent reason (and I believe there might be instances in which framework support requires breaking encapsulation or other OOP principles).

I feel like most Java frameworks built on the javabean specification end up running into this kind of problems. Here are some examples:
  • It's common practice in Spring to have default constructors and setters: this is terrible OOP design that leaves the object to all kinds of potential bugs; this can be fixed by using constructor injection but nobody does that because it's a feature added later and because everyone is so used to the javabean paradigm.
  • Again, in Spring, it's common practice to instantiate beans with "singleton" scope: this "singleton" is usually an instance of a class; if by accident your application starts with two application contexts, you end up with two singletons! This problem can be solved by using an actual singleton and a factory method, e.g., `getInstance()`, to register the singleton with the Spring context. The method could also take care of DI, but again, this is an even more obscure feature of Spring that nobody uses.
  • Objects serialized/deserialized using Jackson typically require a default constructor unless a constructor is defined as a JsonCreator. Once again, the javabean paradigm is so widely spread that many java programmers look at code like that and think I'm insane. Besides, the JsonCreator constructor usually looks pretty terrible and requires to define the properties name in two different places (code bloat + duplication)!
In my experience with (legacy) Java web apps, it looks like these problems are compounded by generally poor design practices, once again fostered by the javabean model: poor encapsulation and lack of immutability. This, together with layer violation in monolithic java apps (like a Spring app would be, ironically!), like using ORM objects in frontend code, leads to unmanageable and fragile code.

Some could argue that being diligent and methodical can prevent programmers from falling into bad practices. However, in my experience this also means swimming against the current because following the right path sometimes leads to horrible code bloat ("what's that giant constructor with all the properties? use setters!") in the best case scenarios and straight up bugs (as the one above) in the worst case scenarios. Taking the easy way out is then justified by the mere existence of the javabean paradigm: every class becomes mutable, with every property (be it a simple type or an object) effectively escaping encapsulation and thread safety (which is "fixed" by using heavy synchronized blocks). So in one single move, the javabean specification flushes down the toilet all the "academic" 101 design practices and makes our life so much harder in the long run.

Javabeans you have failed me!

2 comments:

  1. Nice blog Giovanni! You've discussed some cool and interesting things here!!

    ReplyDelete
  2. Hey man, thanks! Pretty old stuff at this point, I barely have time...

    ReplyDelete