Useful and Unknown Java Libraries

In this article, you will learn about some not so well known but useful Java libraries. This is the second article in the “Useful and Unknown” series. The previous one described several attractive but little-known features of Java. You can read more about this Here.

Today we will focus on Java libraries. We usually use several external libraries in our projects – even if we don’t include them directly. For example, Spring Boot comes with a specific set of dependencies included by starters. If we include, for example, spring-boot-starter-testthen at the same time we include libraries such as mockito, junit-jupiter or hamcrest. Of course, these are known libraries for the community.

In fact, there are many different Java libraries. Usually I don’t need to use many of them (or even I don’t need them) when working with frameworks like Spring Boot or Quarkus.

However, there are some very interesting libraries that can come in handy everywhere. You may not have heard of any of them. I’m going to introduce 5 of my favorite “useful and unknown” Java libraries. Let’s start!

Source

If you want to try it yourself, you can always take a look at my source code. For this you need to clone mine repository on GitHub.

You can also find an example. Then you just need to follow my instructions.

Instantio

Will go to the first fire Instantio. How do you generate test data in your unit tests? Instancio will help us with this. Its goal is to reduce the time and number of lines of code spent manually setting up data in unit tests. It creates objects and fills them with random data, making our tests more dynamic. With Instancio we can generate random data but at the same time we can set custom data in a specific field.

Before we get started with Instancio, let’s discuss our data model. Here is the first class Person:

public class Person {

   private Long id;
   private String name;
   private int age;
   private Gender gender;
   private Address address;

   // getters and setters ...

}

Our class contains three simple fields ( id, name, age), one enumeration Gender and class instance Address. Gender is just an enum containing values MALE And FEMALE. Here is the class implementation Address:

public class Address {

   private String country;
   private String city;
   private String street;
   private int houseNumber;
   private int flatNumber;

   // getters and setters ...

}

Now let’s create a test to verify that the service successfully added and retrieved objects from storage. Person. We want to generate random data for all fields except the field id, which is set by the service. Here is our test:

@Test
void addAndGet() {
   Person person = Instancio.of(Person.class)
             .ignore(Select.field(Person::getId))
             .create();
   person = personService.addPerson(person);
   Assertions.assertNotNull(person.getId());
   person = personService.findById(person.getId());
   Assertions.assertNotNull(person);
   Assertions.assertNotNull(person.getAddress());
}

The values ​​generated for my test run are visible below. As you can see, the field id equals null. Other fields contain random values ​​generated according to the field type ( String or int).

Person(id=null, name=ATDLCA, age=2619, gender=MALE, 
address=Address(country=FWOFRNT, city=AIRICCHGGG, street=ZZCIJDZ, houseNumber=5530, flatNumber=1671))

Let’s see how we can generate multiple objects using Instancio. Assuming for our test that we need 5 objects in the list, we can do it like this. We will also set a constant value for the fields city inside the object Address. Next, we want to test a method for finding objects by city name.

@Test
void addListAndGet() {
   final int numberOfObjects = 5;
   final String city = "Warsaw";
   List<Person> persons = Instancio.ofList(Person.class)
           .size(numberOfObjects)
           .set(Select.field(Address::getCity), city)
           .create();
   personService.addPersons(persons);
   persons = personService.findByCity(city);
   Assertions.assertEquals(numberOfObjects, persons.size());
}

Let’s look at the last example. As before, we generate a list of objects – this time 100. We can easily set additional criteria for the generated values. For example, I want to set a value for a field age between 18 and 65 years old.

@Test
void addGeneratorAndGet() {
   List<Person> persons = Instancio.ofList(Person.class)
            .size(100)
            .ignore(Select.field(Person::getId))
            .generate(Select.field(Person::getAge), 
                      gen -> gen.ints().range(18, 65))
            .create();
   personService.addPersons(persons);
   persons = personService.findAllGreaterThanAge(40);
   Assertions.assertTrue(persons.size() > 0);
}

This is just a small selection of settings that Instancio offers for generating test data. You can read more about other possibilities in its documentation.

datafaker

The next library we’ll look at today is datafaker. The purpose of this library is very similar to the previous one. We need to generate random data. However, this time we need data that looks like real data. From my point of view, this is useful for demo presentations or examples to be run somewhere.

Datafaker creates fake data for your JVM programs in minutes using our wide range of over 100 data providers. This can be very useful when generating test data to populate a database, generating data for a stress test, or anonymizing data from production services. Let’s include it in our dependencies.

<dependency>
  <groupId>net.datafaker</groupId>
  <artifactId>datafaker</artifactId>
  <version>1.7.0</version>
</dependency>

We will expand our model example a bit. Here is the new class definition. The Contact class contains two fields email And phoneNumber. We will check both of these fields with the module jakarta.validation.

public class Contact {

   @Email
   private String email;
   @Pattern(regexp="\\d{2}-\\d{3}-\\d{2}-\\d{2}")
   private String phoneNumber;

   // getters and setters ...

}

Here is the new version of our class Personwhich contains an object instance Contant:

public class Person {

   private Long id;
   private String name;
   private int age;
   private Gender gender;
   private Address address;
   @Valid
   private Contact contact;

   // getters and setters ...

}

Now let’s generate dummy data for the object Person. We can create localized data by simply setting the Locale object in the Faker constructor (1). For me it’s Poland.

There are many providers for standard values. For settings email need to use a provider Internet (2). There are special providers for generating phone numbers (3)addresses (4) and people’s names (5). See the full list of available providers Here. After creating test data, we can run a test that adds a new validated object Person on the server side.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PersonsControllerTests {

   @Autowired
   private TestRestTemplate restTemplate;

   @Test
   void add() {
      Faker faker = new Faker(Locale.of("pl")); // (1)
      Contact contact = new Contact();
      contact.setEmail(faker.internet().emailAddress()); // (2)
      contact.setPhoneNumber(faker.phoneNumber().cellPhone()); // (3)
      Address address = new Address();
      address.setCity(faker.address().city()); // (4)
      address.setCountry(faker.address().country());
      address.setStreet(faker.address().streetName());
      int number = Integer
         .parseInt(faker.address().streetAddressNumber());
      address.setHouseNumber(number);
      number = Integer.parseInt(faker.address().buildingNumber());
      address.setFlatNumber(number);
      Person person = new Person();
      person.setName(faker.name().fullName()); // (5)
      person.setContact(contact);
      person.setAddress(address);
      person.setGender(Gender.valueOf(
         faker.gender().binaryTypes().toUpperCase()));
      person.setAge(faker.number().numberBetween(18, 65));

      person = restTemplate
         .postForObject("/persons", person, Person.class);
      Assertions.assertNotNull(person);
      Assertions.assertNotNull(person.getId());
   }

}

Here is the data generated during my testing. I think you can find one inconsistency here (field country)

Person(id=null, name=Stefania Borkowski, age=51, gender=FEMALE, address=Address(country=Ekwador, city=Sępopol, street=al. Chudzik, houseNumber=882, flatNumber=318), contact=Contact{email="gilbert.augustyniak@gmail.com", phoneNumber="69-733-43-77"})

Sometimes you want to generate a more predictable random result. In the Faker constructor, you can specify an initial value. By providing a seed value, creation of Faker objects will always occur in a predictable manner, which can be handy for generating results multiple times. Here is the new version of my object declaration Faker:

Faker faker = new Faker(Locale.of("pl"), new Random(0));

JPA Streamer

Our next library is related to JPA queries. If you enjoy using Java streams and are building applications that interact with databases via JPA or Hibernate, the library JPA Streamer might be an interesting choice. It is a library for expressing JPA/Hibernate/Spring queries using standard Java streams. JPA Streamer instantly provides Java developers with a type-safe, expressive, and intuitive means of getting data into database applications. Moreover, you can easily integrate it with Spring Boot and Quarkus. First of all, let’s include JPA Streamer in our dependencies:

<dependency>
  <groupId>com.speedment.jpastreamer</groupId>
  <artifactId>jpastreamer-core</artifactId>
  <version>1.1.2</version>
</dependency>

If you want to integrate it with Spring Boot, you need to add one additional dependency:

<dependency>
  <groupId>com.speedment.jpastreamer.integration.spring</groupId>
  <artifactId>spring-boot-jpastreamer-autoconfigure</artifactId>
  <version>1.1.2</version>
</dependency>

To test the JPA Streamer, we need to create an example entity model.

@Entity
public class Employee {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   private String position;
   private int salary;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;

   // getters and setters ...

}

There are also two other entities: Organization And Department. Here are their definitions:

@Entity
public class Department {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;

   // getters and setters ...
}

@Entity
public class Organization {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "organization")
   private Set<Department> departments;
   @OneToMany(mappedBy = "organization")
   private Set<Employee> employees;

   // getters and setters ...
}

We can now prepare multiple queries using the Java threads pattern. In the following code snippet, we are looking up an object by id and then joining two relationships. The default is LEFT JOIN, but we can customize it when calling the method joining(). In the following code snippet, we connect Department And Organizationwhich are in relation @ManyToOne with the entity Employee. We then filter the result, convert the object to a DTO, and select the first result.

@GetMapping("/{id}")
public EmployeeWithDetailsDTO findById(@PathVariable("id") Integer id) {
   return streamer.stream(of(Employee.class)
           .joining(Employee$.department)
           .joining(Employee$.organization))
        .filter(Employee$.id.equal(id))
        .map(EmployeeWithDetailsDTO::new)
        .findFirst()
        .orElseThrow();
}

Of course, we can call many other Java stream methods. In the following code snippet, we count the number of employees assigned to a particular department.

@GetMapping("/{id}/count-employees")
public long getNumberOfEmployees(@PathVariable("id") Integer id) {
   return streamer.stream(Department.class)
         .filter(Department$.id.equal(id))
         .map(Department::getEmployees)
         .mapToLong(Set::size)
         .sum();
}

If you are looking for a detailed explanation and more examples with JPA Streamer, you can read my articlededicated to this topic.

Blaze Persistence

Blaze Persistence – another library from the field of JPA and Hibernate. It allows you to write complex queries using a sequential constructor API with a rich Criteria API for JPA providers. But that is not all. You can also use the Entity-View module for DTO mapping. Of course, you can easily integrate with Spring Boot or Quarkus. If you want to use all Blaze Persistence modules in your application, you should add a section dependencyManagement in maven pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.blazebit</groupId>
            <artifactId>blaze-persistence-bom</artifactId>
            <version>1.6.8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>    
    </dependencies>
</dependencyManagement>

Personally, I use Blaze Persistence for DTO mapping. Thanks to integration with Spring Boot, we can replace Spring Data Projections with Blaze Persistence Entity-Views. This will be especially useful for more complex maps as Blaze Persistence offers more features and better performance for this. You can find a detailed comparison in the following article. If we want to integrate Entity-Views Blaze Persistence with Spring Data, we must add the following dependencies:

<dependency>
  <groupId>com.blazebit</groupId>
  <artifactId>blaze-persistence-integration-spring-data-2.7</artifactId>
</dependency>
<dependency>
  <groupId>com.blazebit</groupId>
  <artifactId>blaze-persistence-integration-hibernate-5.6</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>com.blazebit</groupId>
  <artifactId>blaze-persistence-entity-view-processor</artifactId>
</dependency>

Then you need to create an interface with getters for the mapped fields. It must be annotated @EntityViewA that belongs to the target entity class. In the following example, we are mapping two entity fields firstName And lastName individual fields within an object PersonDTO. To map the primary key of an object, you need to use the annotation @IdMapping.

@EntityView(Person.class)
public interface PersonView {

   @IdMapping
   Integer getId();
   void setId(Integer id);

   @Mapping("CONCAT(firstName,' ',lastName)")
   String getName();
   void setName(String name);

}

We can still take advantage of the Spring Data repository pattern. The interface of our repository should extend the interface EntityViewRepository.

@Transactional(readOnly = true)
public interface PersonViewRepository 
    extends EntityViewRepository<PersonView, Integer> {

    PersonView findByAgeGreaterThan(int age);

}

We also need to provide some additional configuration and enable Blaze Persistence in the main or configuration class:

@SpringBootApplication
@EnableBlazeRepositories
@EnableEntityViews
public class PersonApplication {

   public static void main(String[] args) {
      SpringApplication.run(PersonApplication.class, args);
   }

}

hoverfly

Finally, the last Java library on my list is hoverfly. More precisely, we will use the documented here Java version of the Hoverfly library. It is a lightweight service virtualization tool that allows you to stub or simulate HTTP(S) services. hoverfly Java is a native language binding that gives you an expressive API to control Hoverfly in Java. It provides you with a Hoverfly class that abstracts away binary and API calls, a DSL for creating simulations, and JUnit integration for use in unit tests.

Okay, there are other similar libraries… but for some reason I really like Hoverfly.

It is a simple, lightweight library that can run tests in various modes such as simulation, snooping, capturing, or comparing. You can use Java DSL to create request/response mappings. Let’s include the latest version of Hoverfly as a Maven dependency:

<dependency>
  <groupId>io.specto</groupId>
  <artifactId>hoverfly-java-junit5</artifactId>
  <version>0.14.3</version>
</dependency>

Suppose we have the following method in our Spring @RestController. Before returning a response to ping, it calls another service at http://callme-service:8080/callme/ping.

@GetMapping("/ping")
public String ping() {
   String response = restTemplate
     .getForObject("http://callme-service:8080/callme/ping", 
                   String.class);
   LOGGER.info("Calling: response={}", response);
   return "I'm caller-service " + version + ". Calling... " + response;
}

Now we will create a test for our controller. To use Hoverfly to intercept outgoing traffic, register a HoverflyExtension (1). We can then use the Hoverfly object to create a request and simulate an HTTP response (2). The simulated response body I'm callme-service v1.

@SpringBootTest(properties = {"VERSION = v2"}, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(HoverflyExtension.class) // (1)
public class CallerCallmeTest {
    
   @Autowired
   TestRestTemplate restTemplate;

   @Test
   void callmeIntegration(Hoverfly hoverfly) {
      hoverfly.simulate(
            dsl(service("http://callme-service:8080")
               .get("/callme/ping")
               .willReturn(success().body("I'm callme-service v1.")))
      ); // (2)
      String response = restTemplate
         .getForObject("/caller/ping", String.class);
      assertEquals("I'm caller-service v2. Calling... I'm callme-service v1.", response);
   }
}

We can easily customize the behavior of Hoverfly with an annotation @HoverflyConfig. By default, Hoverfly works in proxy mode. If we want it to work as a web server, we need to set the property webserver meaning true (1). After that, it will listen for requests for localhost and the port specified by the property proxyPort. In the next step, we will also enable Spring Cloud @LoadBalancedClient to set up a static list of target URLs instead of dynamic discovery (2). Finally, we can create the Hoverfly test. This time we are capturing traffic from a web server listening localhost:8080 (3).

@SpringBootTest(webEnvironment = 
   SpringBootTest.WebEnvironment.RANDOM_PORT)
@HoverflyCore(config = 
   @HoverflyConfig(logLevel = LogLevel.DEBUG, 
                    webServer = true, 
                    proxyPort = 8080)) // (1)
@ExtendWith(HoverflyExtension.class)
@LoadBalancerClient(name = "account-service", 
                    configuration = AccountServiceConf.class) // (2)
public class GatewayTests {

    @Autowired
    TestRestTemplate restTemplate;

    @Test
    public void findAccounts(Hoverfly hoverfly) {
        hoverfly.simulate(dsl(
            service("http://localhost:8080")
                .andDelay(200, TimeUnit.MILLISECONDS).forAll()
                .get(any())
                .willReturn(success("[{\"id\":\"1\",\"number\":\"1234567890\",\"balance\":5000}]", "application/json")))); // (3)

        ResponseEntity<String> response = restTemplate
                .getForEntity("/account/1", String.class);
        Assertions.assertEquals(200, response.getStatusCodeValue());
        Assertions.assertNotNull(response.getBody());
    }
}

Here is a load balancer client configuration created for testing purposes only.

class AccountServiceInstanceListSuppler implements 
    ServiceInstanceListSupplier {

    private final String serviceId;

    AccountServiceInstanceListSuppler(String serviceId) {
        this.serviceId = serviceId;
    }

    @Override
    public String getServiceId() {
        return serviceId;
    }

    @Override
    public Flux<List<ServiceInstance>> get() {
        return Flux.just(Arrays
                .asList(new DefaultServiceInstance(serviceId + "1", 
                        serviceId, 
                        "localhost", 8080, false)));
    }
}

Final Thoughts

As you probably guessed, I have used all these Java libraries in Spring Boot applications. Although Spring Boot comes with a certain set of external libraries, sometimes we may need some extras. The Java libraries I’ve presented are usually created for one specific task, such as generating test data. From my point of view, this is perfectly normal. I hope that you will find at least one item from my list useful in your projects.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *