Bashing ORM tools, which in the JVM bubble is most likely Hibernate, is probably the most common hobby among developers. I agree ORM is often an obsolete piece in our technology stacks, but I claim that in most cases, it ought to be the relational database itself that should be burnt with fire.
Don’t get me wrong, as much as I like Hibernate for some solutions, I also have a love-hate relationship with databases. Databases introduce extra complexity into our lives, forcing us to delve into details unrelated to our domain problems and compelling us to use patterns we’d skip otherwise.
The fact that databases can be used for pretty much anything and can scale well when used correctly has somehow made us developers forget about two amazing innovations for handling data: object-oriented programming and RAM.
By combining object serialization with a straightforward storage option like a filesystem, you’ll have the easiest persistence solution in your hands, one that scales well enough for most use cases. If you only have one application accessing the data and your dataset fits into memory, the decision should be a no-brainer (unless you are more skilled with the databases than with the programming language of your choice).
The good and bad of Java’s Serialization API
Since its early versions, the JDK has included a powerful object serialization API. To use it, you simply need to mark your data objects with the Serializable interface, and you are ready to write and read data using ObjectOutputStream
and ObjectInputStreams
to whichever byte storage you prefer. The serialization API is also transparently used by many enterprise Java features, such as RMI and JMS.
While there is a powerful framework built into the JDK, it may not necessarily be the serialization solution you should choose if you decide to replace your database:
- Partly due to its “completeness,” the JDK’s serialization framework has been the source of many security vulnerabilities. For example, developers can customize the serialization/deserialization process, which has led to several vulnerabilities in more complex Java libraries.
- The file format is neither human-readable nor natively supported by other programming languages. Simply serializing the object graph into XML using JAXB or into JSON with Jackson can be the most obvious choice for many solutions. However, with non-trivial data structures, these options often force you to use similar complex techniques as you would with relational databases to store object references (without the support of foreign key constraints and join queries).
- The JDK serialization framework is not particularly fast. If performance is a concern, you should definitely look into a custom binary format or libraries like Kryo or Eclipse Serializer.
Meet Eclipse Serializer
Eclipse Serializer is both a new and old serialization framework, with its 1.0.0 version landing in Maven Central this week. Its origin can be traced back to a more complete persistence solution called MicroStream, which has now been split into two Eclipse-governed projects: Eclipse Store and Eclipse Serializer.
During the summer, I made some tests using Eclipse Serializer while planning a modernization of one of my hobby projects. This particular project happened to be using JDK’s default serialization. I’m quite convinced of the benefits of this solution: it’s superior to the built-in serialization in every way for my needs, and it doesn’t lock my architecture in any way.
Eclipse Serializer is essentially a better-performing replacement for the built-in JDK serialization framework. It offers better performance, ease of use, and enhanced safety. Based on my own tests, it is clearly faster than the built-in JDK serialization, though not quite as fast as Kryo. One of its significant advantages over Kryo is its hassle-free usage. You won’t need zero parameter constructors or custom serialization logic, making it a convenient choice.
In my hobby app, I serialize rather complex domain objects that include geographical data using the Java Topology Suite. Serializing these objects with Eclipse Serializer works with the same elegance as with the JDK's built-in serialization. In contrast, when using Kryo, you may find yourself diverting your focus from the domain problems, albeit in a different way compared to dealing with relational databases and object-relational mapping.
Implementing persistence using serialization in a CRM example app
Since my hobby app has not yet been migrated (and other major updates like Vaadin 7 to Vaadin 24 are still ongoing), I’ve decided to set up a slightly smaller example to show how to set up this type of application. I took our example app that one typically builds in the full-stack Vaadin Tutorial and converted it from JPA-RDBMS.
I’ll let you investigate the changes on your own, but here’s a summary of my findings and disclaimers. Most, if not all, of these points would also apply if you built your system with JDK serialization or Kryo instead:
- Startup time is much shorter compared to using a relational database & JPA
- The data model looks cleaner as it is not filled with a ton of ORM-related annotations or identifiers that you may not actually need.
- In this example, data is persisted in a file in the working directory, which is convenient for development. However, for productions, you’ll want to make adjustments.
- This is just one trivial way to take advantage of the serialization approach. Instead of saving the state on the server’s disk, you could let users download/upload it and store it in the browser's local storage or in any other storage systems, including a relational database if necessary for other parts of your app.
- There is no need for clunky optimistic locking in the app’s UI or backend, nor transactions. While similar functionality can be built manually, it’s worth noting that not all systems using RDBMS require these features either.
- In a typical Java app backed by a database, your DTOs or entities are naturally “detached objects,” serving as snapshots of the data persisted in the relational database. This example uses live objects and makes clones of the data before editing to keep “undo functionality” in place. Depending on your requirements, consider implementing a “detached objects pattern” at your service layers.
- Developing other apps that use the same data may become more challenging, especially if those apps use different programming languages.
- The shortcomings of raw object serialization may be downplayed by more complex solutions like Eclipse Store.