Using static classes correctly in Java

Using a library that has static Java classes for its functionality seems straightforward and easy unless…

Mohammed Atif
Level Up Coding

--

Let me begin with an incident

We were working on payment integration in our Spring Boot Application and here is one sample code our of it

Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";

Map<String, Object> params = new HashMap<>();
params.put(
"description",
"My First Test Customer (created for API docs)"
);

Customer customer = Customer.create(params);

Looks like a simple code block unless you notice the create that is happening on a static method Customer.create(params)

You might ask, what’s wrong in this?

Firstly, unit testing static classes becomes difficult. Yes you can use PowerMock kind of tools, but it is not recommended. I will elaborate on this further in one of my other articles

Secondly, the update disaster. One fine day Stripe team decided to provide a mandatory update due to a security fix and all the payments that were made using prior versions were failing.

So we started updating the Library and faced these issues

  1. New library was not backward compatible, so we had to rewrite the code
  2. Rewriting the code was another pain as all the static calls were lying all across the application
  3. Not just the inputs, but the way data was handled, returned and processed also changed. That means we had to rework on the business logic and data handling logic at multiple places
  4. Whole process took about 1 dedicated sprint with 3 developers working on the migration and regression testing. (P.S. Since everything changed, Power Mock also failed)

What did we learn?

  1. As the statement in STUPID vs SOLID says, Singleton is a big trouble
  2. It creates tight coupling on the code and making any changes to the code becomes really difficult
  3. In case of library updates, regression tests fail as the method signature itself changes
  4. Many teams or projects hesitate to perform the upgrades due to the time consuming nature of the migration of static referenced code
  5. If we wanted to change our Service Provider from Stripe to something else, then we would have been in a bigger trouble as our whole code was tightly couple to Stripe by using the static classes, Exceptions and the model classes provided by them

What is the solution?

Solution is simple and straightforward,

  1. Write a wrapper around the static classes of third party libraries
  2. Never use the classes provided by the library directly in the Application
  3. Never propagate the Exceptions that are provided by the library
  4. Write test cases around your wrapper classes or interfaces instead of library. i.e. Instead of Mocking a response from Stripe (static class), mock the interface or class you defined as wrapper. This way even if you change your underlying library, your dependant code will not change as you will just re write the implementation with other library.

Let us look into a sample code

Then in your actual code, you use the PaymentRepository instead of using StripePaymentRepositoryImpl and all your code changes are then confined to a single class

It also simplifies the test cases because now you will just have to verify or mock the results of your interface which will not change when you. change the library

Also by decoupling the Exception and DTO (or Model) classes, you totally get rid of any dependency on the library in rest of your code and making any migrations or changes becomes much more easier

In my upcoming articles I will talk in detail about how knowledge of design patterns can simplify our day to day coding without causing any major overheads in the development process

--

--