Most Common Spring Framework Mistakes

Mintesnot

Last Update vor 4 Jahren

Spring is arguably one of the most popular Java frameworks and arguably the best one. While its basic concepts are fairly easy to grasp, becoming a strong Spring developer requires some time and effort.  In this article, we cover the most common pitfalls of using the Spring framework so new and experienced developers alike have a roadmap of what to avoid.


Mistake #1 - Going too low

We’re hitting it off with this common mistake because the “not invented here” syndrome is quite common in the software development world. Symptoms including regularly rewriting pieces of commonly used code and a lot of developers seems to suffer from it.

While understanding the internals of a particular library and its implementation is for the most part good and necessary (and can be a great learning process as well), it’s detrimental to your development as a software engineer to be constantly tackling the same low-level implementation details. There is a reason why abstractions and frameworks such as Spring exist, which is precisely to separate you from repetitive manual work and allow you to concentrate on higher level details– your domain objects and business logic.

So embrace the abstractions - the next time you are faced with a particular problem, do a quick search first and determine whether a library solving that problem is already integrated into Spring; nowadays, chances are you’ll find a suitable existing solution. As an example of a useful library, I’ll be using Project Lombok annotations in examples for the remainder of this article. Lombok is used as a boilerplate code generator and the lazy developer within you hopefully shouldn’t have a problem acquainting yourself with the library. As an example, check out what a “standard Java bean” looks like with Lombok:

Mistake #2 - 'Leaking' Internals

Exposing your internal structure is never a good idea because it creates inflexibility in service design and consequently promotes bad coding practices. ‘Leaking’ internals are manifested by making database structure accessible from certain API endpoints. As an example, let’s say the following POJO (“Plain Old Java Object”) represents a table in your database:

Let’s say an endpoint exists which needs to access the CraftEntity data. Tempting as it may be to return CraftEntity instances, a more flexible solution would be creating a new class to represent the Entity data on the API endpoint:

That way, making changes to your database back-end will not require any additional changes in the service layer. Consider what would happen in the case of adding a ‘password’ field to CraftEntity for storing your users’ password hashes in the database - without a connector such as CraftData, forgetting to change the service front-end would accidentally expose some very undesirable secret information!

Mistake #3: Lacking Separation of Concerns

As your application grows, code organization increasingly starts becoming an ever more important matter. Ironically, most of the good software engineering principles start to break down at scale – especially in cases where not much thought has been given to the application architecture design. One of the most common mistakes developers then tend to succumb to is mixing code concerns, and it’s extremely easy to do!

What usually breaks separation of concerns is just ‘dumping’ new functionality into existing classes. This is, of course, a great short-term solution (for starters, it requires less typing) but it inevitably becomes a problem further down the road, be it during testing, maintenance, or somewhere in between. Consider the following controller, which returns CraftData from its repository:

At first, it might not seem there’s anything particularly wrong with this piece of code; it provides a list of CraftData which is being retrieved from CraftEntity instances. Taking a closer look, however, we can see that there are actually a few things that CraftController is performing here; namely, it is mapping requests to a particular endpoint, retrieving data from a repository, and converting entities received from CraftRepository into a different format. A ‘cleaner’ solution would be separating those concerns into their own classes. It might look something like this:

An additional advantage to this hierarchy is that it allow us to determine where functionality resides just by inspecting the class name. Furthermore, during testing we can easily substitute any of the classes with a mock implementation if the need arises.

Mistake #4 - Inconsistency and Poor Error Handling

The topic of consistency is not necessarily exclusive to Spring (or Java, for that matter), but still is an important facet to consider when working on Spring projects. While coding style can be up for debate (and is usually a matter of agreement within a team or within an entire company), having a common standard turns out to be a great productivity aid. This is especially true with multi-person teams; consistency allows hand-off to occur without many resources being spent on hand-holding or providing lengthy explanations regarding the responsibilities of different classes.

Consider a Spring project with its various configuration files, services and controllers. Being semantically consistent in naming them creates an easily searchable structure where any new developer can manage his way around the code; appending Config suffixes to your configuration classes, Service suffixes to your services and Controller suffixes to your controllers, for example.

Closely related to the topic of consistency, error handling on the server-side deserves a specific emphasis. If you ever had to handle exception responses from a poorly written API, you probably know why– it can be a pain to properly parse exceptions, and even more painful to determine the reason for why those exceptions occurred in the first place.

As an API developer, you’d ideally want to cover all user-facing endpoints and translate them into a common error format. This usually means having a generic error code and description rather than the cop-out solution of a) returning a “500 Internal Server Error” message, or b) just dumping the stack trace to the user (which should actually be avoided at all costs since it exposes your internals in addition to being difficult to handle client-side).

An example of a common error response format might be:

Something similar to this is commonly encountered in most popular APIs, and tends to work well since it can be easily and systematically documented. Translating exceptions into this format can be done by providing the @ExceptionHandler annotation to a method.

Mistake #5 - Improperly Dealing with Multithreading

Regardless of whether it is encountered in desktop or web apps, Spring or no Spring, multithreading can be a tough nut to crack. Problems caused by parallel execution of programs are nerve-wrackingly elusive and often times extremely difficult to debug - in fact, due to the nature of the problem, once you realise you’re dealing with a parallel-execution issue you’re probably going to have to forego the debugger entirely and inspect your code “by hand” until you find the root error cause. Unfortunately, a cookie-cutter solution does not exists for solving such issues; depending on your specific case, you’re going to have to assess the situation and then attack the problem from the angle you deem is best.

Ideally you would, of course, want to avoid multithreading bugs altogether. Again, a one-size-fits-all approach does not exist for doing so, but here are some practical considerations for debugging and preventing multithreading errors:

Avoid Global State

First, always remember the “global state” issue. If you’re creating a multithreaded application, absolutely anything that is globally modifiable should be closely monitored and, if possible, removed altogether. If there is a reason why the global variable must remain modifiable, carefully employ synchronization and track your application’s performance to confirm that it’s not sluggish due to the newly introduced waiting periods.

Avoid Mutability

This one comes straight from functional programming and, adapted to OOP, states that class mutability and changing state should be avoided. This, in short, means foregoing setter methods and having private final fields on all your model classes. The only time their values are mutated is during construction. This way you can be certain that no contention problems arise and that accessing object properties will provide the correct values at all times.

Log Crucial Data

Assess where your application might cause trouble and preemptively log all crucial data. If an error occurs, you will be grateful to have information stating which requests were received and have better insight into why your application misbehaved. It’s again necessary to note that logging introduces additional file I/O and should therefore not be abused as it can severely impact your application’s performance.

Reuse Existing Implementations

Whenever you are in need of spawning your own threads (e.g. for making async requests to different services), reuse existing safe implementations rather than create your own solutions. This will, for the most part, mean utilizing ExecutorServices and Java 8’s neat functional-style CompletableFutures for thread creation. Spring also allows asynchronous request processing via the DeferredResult class.

Mistake #6 - (Still) Using An XML-Based Configuration

While XML was a necessity for previous versions of Spring, nowadays most of the configuration can be done exclusively via Java code / annotations; XML configurations just pose as additional and unnecessary boilerplate code.

This article (as well as its accompanying GitHub repository) uses annotations for configuring Spring and Spring knows which beans it should wire because the root package has been annotated with a @SpringBootApplication composite annotation, like so:

The composite annotation (you can learn more about it in the Spring documentation simply gives Spring a hint on which packages should be scanned to retrieve beans. In our concrete case, this means the following under the top (co.craft) package will be used for wiring:

  • @Component (CraftConverter, MyAnnotationValidator)
  • @RestController (CraftController)
  • @Repository (CraftRepository)
  • @Service (CraftService) classes

If we had any additional @Configuration annotated classes they would also be checked for Java-based configuration.

Becoming a Spring Master

Spring is a powerful framework that’s easy to get started with but requires some dedication and time to achieve full mastery. Taking the time to familiarize yourself with the framework will definitely improve your productivity in the long run and ultimately help you write cleaner code and become a better developer.

If you’re looking for further resources, Spring In Action is a good hands-on book covering many core Spring topics and enroll on our Java Full Stack Course https://craftknowledge.com/

References

Was this article helpful?

1 out of 1 liked this article