Useful & Unknown Java Libraries
This article will teach you about some not famous but useful Java libraries. This is the second article in the “useful & unknown” series. The previous one described several attractive, but not well-known Java features. You can read more about it here.
Today we will focus on Java libraries. Usually, we use several external libraries in our projects – even if we do not include them directly. For example, Spring Boot comes with a defined set of dependencies included by starters. Assuming we include e.g. spring-boot-starter-test
we include libraries like mockito
, junit-jupiter
or hamcrest
at the same time. Of course, these are well-known libraries for the community.
In fact, there are a lot of different Java libraries. Usually, I don’t need to use many of them (or even I need none of them) when working with the frameworks like Spring Boot or Quarkus. However, there are some very interesting libraries that may be useful everywhere. I’m writing about them because you might not hear about any of them. I’m going to introduce 5 of my favorite “useful & unknown” Java libraries. Let’s begin!
Source Code
If you would like to try it by yourself, you may always take a look at my source code. To do that you need to clone my GitHub repository. Yo can also find the example Then you should just follow my instructions.
Instancio
On the first fire will go Instancio. How do you generate test data in your unit tests? Instancio will help us with that. It aims to reduce the time and lines of code spent on manual data setup in unit tests. It instantiates and populates objects with random data, making our tests more dynamic. We can generate random data with Instancio but at the same, we can set custom data in a particular field.
Before we start with Instancio, let’s discuss our data model. Here’s 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
), a single enum Gender
, and the instance of the Address
class. Gender is just a simple enum containing MALE
and FEMALE
values. Here’s the implementation of the Address
class:
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 check whether the Person
service will successfully add and obtain objects from the store. We want to generate random data for all the fields except the id
field, which is set by the service. Here’s 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 see, the id
field equals null
. Other fields contain random values generated per 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 several objects with Instancio. Assuming we need 5 objects in the list for our test, we can do that in the following way. We will also set a constant value for the city
fields inside the Address
object. Then we would like to test the method for searching objects by the 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 take a look at the last example. The same as before, we are generating a list of objects – this time 100. We can easily specify the additional criteria for generated values. For example, I would like to set a value for the age
field between 18 and 65.
@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);
}
That’s just a small set of customizations, that Instancio offers, for test data generation. You can read more about other options in their docs.
Datafaker
The next library we will discuss today is Datafaker. The purpose of this library is quite similar to the previous one. We need to generate random data. However, this time we need data that looks like real data. From my perspective, it is useful for demo presentations or examples running somewhere.
Datafaker creates fake data for your JVM programs within minutes, using our wide range of more than 100 data providers. This can be very helpful when generating test data to fill 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 sample model a little. Here’s a new class definition. The Contact class contains two fields email
and phoneNumber
. We will validate both these fields using the jakarta.validation
module.
public class Contact {
@Email
private String email;
@Pattern(regexp="\\d{2}-\\d{3}-\\d{2}-\\d{2}")
private String phoneNumber;
// getters and setters ...
}
Here’s a new version of our Person
class that contains the Contant
object instance:
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 fake data for the Person
object. We can create localized data just by setting the Locale object in the Faker constructor (1). For me, it is Poland 🙂 There are a lot of providers for the standard values. To set email
we need to use the Internet
provider (2). There is a dedicated provider for generating phone numbers (3), addresses (4), and person names (5). You can see a full list of available providers here. After creating test data, we can run the test that adds a new Person
verified 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’s the data generated during my test. I think you can find one inconsistency here (the country
field) 😉
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 need to generate a more predictable random result. It’s possible to provide a seed value in the Faker constructor. When providing a seed, the instantiation of Fake objects will always happen in a predictable way, which can be handy for generating results multiple times. Here’s a new version of my Faker
object declaration:
Faker faker = new Faker(Locale.of("pl"), new Random(0));
JPA Streamer
Our next library is related to JPA queries. If you like to use Java streams and you are building apps that interact with databases through JPA or Hibernate, the JPA Streamer library may be an interesting choice. It is a library for expressing JPA/Hibernate/Spring queries using standard Java streams. JPA Streamer instantly gives Java developers type-safe, expressive and intuitive means of obtaining data in database applications. Moreover, you can easily integrate it with Spring Boot and Quarkus. Firstly, 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>
In order to test JPA Streamer, we need to create an example entities 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 ...
}
Now, we can prepare some queries using the Java streams pattern. In the following fragment of code, we are searching an entity by id and then joining two relations. By default, it is LEFT JOIN, but we can customize it when calling the joining()
method. In the following fragment of code, we join Department
and Organization
, which are in @ManyToOne
a relationship with the Employee entity. Then we filter the result, convert the object to the DTO and pick 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 fragment of code, we count the number of employees assigned to the 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 my article dedicated to that topic.
Blaze Persistence
Blaze Persistence is another library from the JPA and Hibernate area. It allows you to write complex queries with a consistent builder API with rich Criteria API for JPA providers. That’s not all. You can also use the Entity-View module dedicated to DTO mapping. Of course, you can easily integrate with Spring Boot or Quarkus. If you want to use all Blaze Persistence modules in your app it is worth adding the dependencyManagement
section in your 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’m using Blaze Persistence for DTO mapping. Thanks to the integration with Spring Boot we can replace Spring Data Projections with Blaze Persistence Entity-Views. It will be especially useful for more advanced mappings since Blaze Persistence offers more features and better performance for that. You can read a detailed comparison in the following article. If we want to integrate Blaze Persistence Entity-Views with Spring Data we should 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, we need to create an interface with getters for mapped fields. It should be annotated with the @EntityView
that refers to the target entity class. In the following example, we are mapping two entity fields firstName
and lastName
to the single fields inside the PersonDTO
object. In order to map the entity’s primary key we should use the @IdMapping
annotation.
@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. Our repository interface needs to extend the EntityViewRepository
interface.
@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 the configuration class:
@SpringBootApplication
@EnableBlazeRepositories
@EnableEntityViews
public class PersonApplication {
public static void main(String[] args) {
SpringApplication.run(PersonApplication.class, args);
}
}
Hoverfly
Finally, the last of the Java libraries on my list – Hoverfly. To be more precise, we will use the Java version of the Hoverfly library documented here. 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 for managing Hoverfly in Java. It gives you a Hoverfly class which abstracts away the binary and API calls, a DSL for creating simulations, and a JUnit integration for using it within unit tests.
Ok, there are some other, similar libraries… but somehow I really like Hoverfly 🙂 It is a simple, lightweight library that may perform tests in different modes like simulation, spying, capture, or diffing. You can use Java DSL to build request matchers to response mappings. Let’s include the latest version of Hoverfly in the Maven dependencies:
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>0.14.3</version>
</dependency>
Let’s assume we have the following method in our Spring @RestController
. Before returning a ping response for itself, it calls another service under the address 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 the test for our controller. In order to use Hoverfly to intercept outgoing traffic we register HoverflyExtension (1). Then we may the Hoverfly object to create a request mather and simulate an HTTP response (2). The simulated response body is 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 Hovefly behavior with the @HoverflyConfig
annotation. By default, Hoverfly works in proxy mode. Assuming we want it to act as a web server we need to set the property webserver
to true
(1). After that, it will listen for requests on localhost
and the port indicated by the proxyPort
property. In the next step, we will also enable Spring Cloud @LoadBalancedClient
to configure a static list of target URLs instead of dynamic discovery (2). Finally, we can create a Hoverfly test. This time we are intercepting traffic from the web server listening on the 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’s the load balancer client configuration created just for the test purpose.
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 figured out, I used all that Java libraries with Spring Boot apps. Although Spring Boot comes with a defined set of external libraries, sometimes we may need some add-ons. The Java libraries I presented are usually created to solve a single, particular problem like e.g. test data generation. It’s totally fine from my perspective. I hope you will find at least one position from my list useful in your projects.
6 COMMENTS