Quantcast
Channel: Baeldung
Viewing all 4476 articles
Browse latest View live

Detecting Compromised Passwords Using Spring Security

$
0
0

1. Overview

When building web applications that deal with sensitive data, it’s important to ensure the security of user passwords. One important aspect of password security is checking whether a password is compromised, often due to its presence in a data breach.

Spring Security 6.3 introduces a new feature that allows us to easily check if a password has been compromised.

In this tutorial, we’ll explore the new CompromisedPasswordChecker API in Spring Security and how it can be integrated in our Spring Boot application.

2. Understanding Compromised Passwords

A compromised password is a password that’s exposed in a data breach, making it vulnerable to unauthorized access. Attackers often use these compromised passwords in credential stuffing and password stuffing attacks, using the leaked username-password pairs across multiple sites or common passwords against multiple accounts.

To mitigate this risk, it’s crucial to check if a user’s password is compromised before creating an account against it.

It’s also important to note that a previously valid password can become compromised over time, so it’s always recommended to check for compromised passwords not only during account creation but also during the login process or any process that allows the user to change their passwords. We can prompt a user to reset their password, if a login attempt fails due to detection of a compromised password.

3. The CompromisedPasswordChecker API

Spring Security provides a simple CompromisedPasswordChecker interface for checking if a password has been compromised:

public interface CompromisedPasswordChecker {
    CompromisedPasswordDecision check(String password);
}

This interface exposes a single check() method that takes a password as an input and returns an instance of CompromisedPasswordDecision, indicating whether the password is compromised.

The check() method expects a plaintext password, so we’ll have invoke it before we encrypt our password using a PasswordEncoder.

3.1. Configuring the CompromisedPasswordChecker Bean

To enable compromised password checking in our application, we need to declare of bean of type CompromisedPasswordChecker:

@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
    return new HaveIBeenPwnedRestApiPasswordChecker();
}

The HaveIBeenPwnedRestApiPasswordChecker is the default implementation of CompromisedPasswordChecker provided by Spring Security.

This default implementation, integrates with the popular Have I Been Pwned API, which maintains an extensive database of compromised passwords from data breaches.

When the check() method of this default implementation is invoked, it securely hashes the provided password and sends the first 5 characters of the hash to the Have I Been Pwned API. The API responds with a list of hash suffixes that match this prefix. The method then compares the full hash of the password against this list and determines if it’s compromised. The entire check is performed without ever sending the plaintext password over the network.

3.2. Customizing the CompromisedPasswordChecker Bean

If our application uses a proxy server for egress HTTP requests, we can configure the HaveIBeenPwnedRestApiPasswordChecker with a custom RestClient:
@Bean
public CompromisedPasswordChecker customCompromisedPasswordChecker() {
    RestClient customRestClient = RestClient.builder()
      .baseUrl("https://api.proxy.com/password-check")
      .defaultHeader("X-API-KEY", "api-key")
      .build();
    HaveIBeenPwnedRestApiPasswordChecker compromisedPasswordChecker = new HaveIBeenPwnedRestApiPasswordChecker();
    compromisedPasswordChecker.setRestClient(customRestClient);
    return compromisedPasswordChecker;
}

Now, when we call the check() method of our CompromisedPasswordChecker bean in our application, It’ll send the API request to the base URL we’ve defined along with the custom HTTP header.

4. Handling Compromised Passwords

Now that we’ve configured our CompromisedPasswordChecker bean, let’s look at how we can use it in our service layer to validate passwords. Let’s take a common use case of a new user registration:

@Autowired
private CompromisedPasswordChecker compromisedPasswordChecker;
String password = userCreationRequest.getPassword();
CompromisedPasswordDecision decision = compromisedPasswordChecker.check(password);
if (decision.isCompromised()) {
    throw new CompromisedPasswordException("The provided password is compromised and cannot be used.");
}

Here, we simply invoke the check() method with the plaintext password provided by the client and examine the returned CompromisedPasswordDecision. If the isCompromised() method returns true, we throw a CompromisedPasswordException to abort the registration process.

5. Handling the CompromisedPasswordException

When our service layer throws a CompromisedPasswordException, we’d want to handle it gracefully and provide feedback to the client.

One way to do this is to define a global exception handler in a @RestControllerAdvice class:

@ExceptionHandler(CompromisedPasswordException.class)
public ProblemDetail handle(CompromisedPasswordException exception) {
    return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, exception.getMessage());
}

When this handler method catches a CompromisedPasswordException, it returns an instance of ProblemDetail class, which constructs an error response that’s compliant with the RFC 9457 specification:

{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "The provided password is compromised and cannot be used.",
    "instance": "/api/v1/users"
}

6. Custom CompromisedPasswordChecker Implementation

While the HaveIBeenPwnedRestApiPasswordChecker implementation is a great solution, there might be scenarios where we want to integrate with a different provider, or even implement our own compromised password checking logic.

We can do so by implementing the CompromisedPasswordChecker interface:

public class PasswordCheckerSimulator implements CompromisedPasswordChecker {
    public static final String FAILURE_KEYWORD = "compromised";
    @Override
    public CompromisedPasswordDecision check(String password) {
        boolean isPasswordCompromised = false;
        if (password.contains(FAILURE_KEYWORD)) {
            isPasswordCompromised = true;
        }
        return new CompromisedPasswordDecision(isPasswordCompromised);
    }
}

Our sample implementation considers a password compromised if it contains the word “compromised”. While not very useful in a real-world scenario, it demonstrates how straightforward it’s to plug in our own custom logic.

In our test cases, it’s generally a good practice to use such simulated implementation instead of making an HTTP call to an external API. To use our custom implementation in our tests, we can define it as a bean in a @TestConfiguration class:

@TestConfiguration
public class TestSecurityConfiguration {
    @Bean
    public CompromisedPasswordChecker compromisedPasswordChecker() {
        return new PasswordCheckerSimulator();
    }
}

In our test classes, where we want to use this custom implementation, we’ll annotate it with @Import(TestSecurityConfiguration.class).

Also, to avoid BeanDefinitionOverrideException when running our tests, we’ll annotate our main CompromisedPasswordChecker bean with @ConditionalOnMissingBean annotation.

Finally, to verify the behaviour of our custom implementation, we’ll write a test case:

@Test
void whenPasswordCompromised_thenExceptionThrown() {
    String emailId = RandomString.make() + "@baeldung.it";
    String password = PasswordCheckerSimulator.FAILURE_KEYWORD + RandomString.make();
    String requestBody = String.format("""
            {
                "emailId"  : "%s",
                "password" : "%s"
            }
            """, emailId, password);
    String apiPath = "/users";
    mockMvc.perform(post(apiPath).contentType(MediaType.APPLICATION_JSON).content(requestBody))
      .andExpect(status().isBadRequest())
      .andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.value()))
      .andExpect(jsonPath("$.detail").value("The provided password is compromised and cannot be used."));
}

7. Creating a Custom @NotCompromised Annotation

As discussed earlier, we should check for compromised passwords not only during user registration, but in all APIs that allow a user to change their passwords or authenticate using their password, such as the login API.

While we can perform this check in the service layer for each of these processes, using a custom validation annotation provides a more declarative and reusable approach.

First, let’s define a custom @NotCompromised annotation:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CompromisedPasswordValidator.class)
public @interface NotCompromised {
    String message() default "The provided password is compromised and cannot be used.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Next, let’s implement the ConstraintValidator interface:

public class CompromisedPasswordValidator implements ConstraintValidator<NotCompromised, String> {
    @Autowired
    private CompromisedPasswordChecker compromisedPasswordChecker;
    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        CompromisedPasswordDecision decision = compromisedPasswordChecker.check(password);
        return !decision.isCompromised();
    }
}

We autowire an instance of the CompromisedPasswordChecker class and use it to check if the client’s password is compromised.

We can now use our custom @NotCompromised annotation on the password fields of our request bodies and validate their values:

@NotCompromised
private String password;
@Autowired
private Validator validator;
UserCreationRequestDto request = new UserCreationRequestDto();
request.setEmailId(RandomString.make() + "@baeldung.it");
request.setPassword(PasswordCheckerSimulator.FAILURE_KEYWORD + RandomString.make());
Set<ConstraintViolation<UserCreationRequestDto>> violations = validator.validate(request);
assertThat(violations).isNotEmpty();
assertThat(violations)
  .extracting(ConstraintViolation::getMessage)
  .contains("The provided password is compromised and cannot be used.");

8. Conclusion

In this article, we explored how we can use Spring Security’s CompromisedPasswordChecker API to enhance our application’s security by detecting and preventing the use of compromised passwords.

We discussed how to configure the default HaveIBeenPwnedRestApiPasswordChecker implementation. We also talked about customizing it for our specific environment, and even implement our own custom compromised password checking logic.

In conclusion, checking for compromised passwords adds an extra layer of protection for our users’ accounts against potential security attacks.

As always, all the code examples used in this article are available over on GitHub.

       

Convert int to Unsigned byte in Java

$
0
0

1. Introduction

In Java, the byte type is a signed 8-bit integer. This means it can hold values from -128 to 127. However, there are situations where we might need to work with bytes as if they were unsigned, representing values from 0 to 255. This can be especially important when dealing with binary data, networking, and file I/O where unsigned bytes are common.

In this tutorial, we’ll discover two approaches to converting an int to an unsigned byte in Java.

2. Using Type Casting and Bit Masking

The most common approach is to use type casting combined with bit masking. Let’s explore the implementation:

int value = 200;
@Test
public void givenInt_whenUsingTypeCastingAndBitMasking_thenConvertToUnsignedByte() {
    byte unsignedByte = (byte) (value & 0xFF);
    assertEquals(value, Byte.toUnsignedInt(unsignedByte));
}

In this test, we begin by initializing an integer value of 200. Then, we employ the expression (value & 0xFF) to convert this integer into an unsigned byte representation. This operation involves a bitwise AND operation between the integer value and the hexadecimal value 0xFF, which corresponds to 255 in decimal or 11111111 in binary.

By executing this bitwise AND operation, we ensure that only the least significant 8 bits of the integer value are retained, effectively discarding any higher-order bits. Consequently, the resulting value of (value & 0xFF) represents an unsigned byte within the range of 0 to 255. Furthermore, this resultant unsigned byte value is then cast to a byte datatype using (byte), facilitating compatibility with Java’s byte type.

Subsequently, after obtaining this byte representation, we utilize the Byte.toUnsignedInt() method to correctly interpret it as an unsigned value.

3. Using ByteBuffer

Another approach involves using the ByteBuffer class to convert an int to a byte array and then extract the byte:

@Test
public void givenIntInRange_whenUsingByteBuffer_thenConvertToUnsignedByte() {
    int value = 200;
    ByteBuffer buffer = ByteBuffer.allocate(4).putInt(value);
    byte unsignedByte = buffer.array()[3];
    assertEquals(value, Byte.toUnsignedInt(unsignedByte));
}

Here, we allocate a ByteBuffer of 4 bytes to store the integer value. Then, we utilize the putInt(value) method to store the integer in the buffer. Since the buffer stores the value in big-endian order by default, the least significant byte (which we need) is the fourth byte in the buffer (index 3).

4. Conclusion

In conclusion, while Java lacks an unsigned byte type, various techniques such as type casting combined with bit masking or using ByteBuffer provide effective means for converting an int to an unsigned byte, which is crucial for scenarios requiring the representation of values from 0 to 255.

As always, the complete code samples for this article can be found over on GitHub.

       

Properties in BeanFactoryPostProcessor

$
0
0
Contact Us Featured

1. Overview

In Spring, properties can be injected directly into our beans using the @Value annotation, accessed through the Environment abstraction, or be bound to structured objects through @ConfigurationProperties. If we try to inject Properties in BeanFactoryPostProcessor using these conventional ways, it won’t work! This is because these annotations are processed by BeanPostProcessor, which means they cannot be used within BeanPostProcessor or BeanFactoryPostProcessor types.

In this tutorial, we’ll learn to use the Environment class to inject properties in BeanFactoryPostProcessor. To show how this works with Spring Boot, we’ll use Binder in place of @ConfigurationProperties. Lastly, we’ll demonstrate both options for creating BeanFactoryPostProcessor using the @Bean annotation and the @Component annotation.

2. Properties in BeanFactoryPostProcessor

To inject properties in BeanFactoryPostProcessor, we need to retrieve the Environment object. Then, using this, we can:

  • Use the getProperty() method for each property we wish to fetch
  • Use Binder and Environment to retrieve the whole ConfigurationFile

The getProperty() method is convenient to get a small number of properties. If we wish to fetch significantly more, it would be simpler to use a ConfigurationFile, assuming that all properties exist in the same file. Note that @ConfigurationFile is a feature of Spring Boot and therefore not available in simple Spring applications.

3. Properties in BeanFactoryPostProcessor Using @Bean Annotation

Let’s now see how we can use the Environment abstraction to get properties when we create a BeanFactoryPostProcessor using the @Bean annotation.

First, we’ll create a configuration file where we’ll define a bean of type BeanFactoryPostProcessor, injecting the Environment as a parameter. Then, we can either use the getProperty() method or a Binder, as shown in the next sections.

3.1. getProperty() Method of Environment

The use of getProperty() is most useful when only a few properties are needed:

@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(Environment environment) {
    return beanFactory -> {
        String articleName = environment.getProperty("article.name", String.class);
        LOGGER.debug("Article name, using environment::getProperty: " + articleName);
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        registry.registerBeanDefinition("articleNameFromBeanAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
          .addConstructorArgValue(articleName)
          .getBeanDefinition());
    };
}

In the example above, we use environment.getProperty() to get the article.name property and then create a new dummy bean with the name articleNameFromBeanAnnotation to hold its value.

3.2. Binder

We can also combine Binder and Environment to load the whole configuration file. This way, we can leverage the @ConfigurationFile annotation of Spring Boot applications:

@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(Environment environment) {
    return beanFactory -> {
        BindResult<ApplicationProperties> result = Binder.get(environment)
          .bind("application", ApplicationProperties.class);
        ApplicationProperties properties = result.get();
        LOGGER.debug("Application name, using binder to access ApplicationProperties: " + properties.getName());
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        registry.registerBeanDefinition("applicationNameFromBeanAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
          .addConstructorArgValue(properties.getName())
          .getBeanDefinition());
    };
}

In this example, we use Binder‘s methods get() and bind() to load the configuration file for the current environment. Then, we use the configuration file getters to retrieve the application name. We finally store its value in a dummy bean with the name applicationNameFromBeanAnnotation.

4. Properties in BeanFactoryPostProcessor Using @Component Annotation

Another way to create a BeanFactoryPostProcessor is to use the @Component annotation. In this case, similar to using @Bean annotation, we need the Environment abstraction. The difference is that we can’t inject the Environment because BeanFactoryPostProcessor only comes with a no-arguments constructor. The solution is to use the EnvironmentAware interface to inject the environment:

@Component
public class PropertiesWithBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
    private Environment environment;
    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String articleName = environment.getProperty("article.name", String.class);
        LOGGER.debug("Article name, using environment::getProperty: " + articleName);
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        registry.registerBeanDefinition("articleNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
          .addConstructorArgValue(articleName)
          .getBeanDefinition());
    }
    @Override
    public void setEnvironment(@NonNull Environment environment) {
        this.environment = environment;
    }
}

This provides the method setEnvironment(), which is another way to inject beans in Spring applications. Then, we store the injected value of the Environment in a field. In postProcessBeanFactory(), we can invoke either the getProperty() method or a Binder using the field. In the code above, we used the former.

4.1. getProperty() Method of Environment

We can use the getProperty() method to retrieve a small number of properties:

@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String articleName = environment.getProperty("article.name", String.class);
    LOGGER.debug("Article name, using environment::getProperty: " + articleName);
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
    registry.registerBeanDefinition("articleNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
      .addConstructorArgValue(articleName)
      .getBeanDefinition());
}

In this example, we use environment.getProperty() to load the article name from properties and store it in the articleNameFromComponentAnnotation bean.

4.2. Binder

In Spring Boot applications, we can use Binder with Environment to retrieve a configuration file that holds a group of properties:

@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
    BindResult<ApplicationProperties> result = Binder.get(environment)
      .bind("application", ApplicationProperties.class);
    ApplicationProperties properties = result.get();
    LOGGER.debug("Application name, using binder to access ApplicationProperties: " + properties.getName());
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
    registry.registerBeanDefinition("applicationNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
      .addConstructorArgValue(properties.getName())
      .getBeanDefinition());
}

Using Binder.get().bind(), we load ApplicationProperties and then use its getters to store the application name in the applicationNameFromComponentAnnotation bean.

5. Conclusion

In this article, we discussed the issues with injecting properties directly in the BeanFactoryPostProcessor. We demonstrated how we can leverage concepts like the Environment abstraction, Binder, BeanNameAware, and @ConfigurationFile to overcome those difficulties. Last, we discussed the trade-offs of each option.

As always, all the source code is available over on GitHub.

       

Find the Index of the Largest Value in an Array

$
0
0
Contact Us Featured

1. Overview

When working with arrays in Java, one of the everyday tasks we might encounter is finding the index of the largest value in an array.

In this quick tutorial, we’ll walk through several simple and efficient ways to accomplish this task.

2. Introduction to the Problem

Finding the index of the largest element in an array can be useful in various scenarios, such as finding the highest score in a game, the maximum temperature recorded, or any other situation where identifying the peak value is essential.

Let’s say we have an int array:

static final int[] ARRAY = { 1, 2, 7, 10, 8, 6 };

It’s not hard to identify that 10 is the largest element in the above array. So, the corresponding index is 3.

Next, we’ll take ARRAY as an example and explore different ways to find the expected index: 3.

For simplicity, we assume the input array contains a single, unique, largest element. We’ll also skip input validation, such as a null check, and employ unit test assertions to verify each approach’s result.

3. Converting the Array to a List

The first idea is to find the largest element in the array and then get its index. Finding the largest value isn’t a challenge for us. But Java doesn’t offer the indexOf(element) method for arrays to get the index of an element.

We know List has the indexOf() method. So, let’s first convert the int array to a List and then find the index of the largest value:

int indexOfTheMax(int[] array) {
    List<Integer> list = Arrays.stream(array)
      .boxed()
      .toList();
    int max = Collections.max(list);
    return list.indexOf(max);
}

In the code above, we converted the primitive int array to an Integer List using the Stream API. Then, we again employed the Stream API to find the list’s largest value.

It’s worth noting that the max() method returns an Optional object. Therefore, we use the orElse() method to get its value. If the input array is empty, the method returns -1.

If we pass ARRAY or an empty array as inputs to the indexOfTheMax() method, it returns the expected value:

int result = indexOfTheMax(ARRAY);
assertEquals(3, result);
 
result = indexOfTheMax(new int[] {});
assertEquals(-1, result);

This approach solves the problem. However, it walks through the array multiple times.

4. Looping Through the Array

We can traverse the array to find the index of the largest element.

First, let’s look at the implementation:

int indexOfTheMaxByLoop(int[] array) {
    if (array.length == 0) {
        return -1;
    }
    int idx = 0;
    for (int i = 1; i < array.length; i++) {
        idx = array[i] > array[idx] ? i : idx;
    }
    return idx;
}

In indexOfTheMaxByLoop(), we declared and initialized the idx variable to store the result after checking and handling the empty array case. Then, we loop through the array. Whenever we find an element array[i] larger than array[idx], we update idx = i.

Finally, the method returns idx, which holds the index of the largest value.

Next, let’s test the indexOfTheMaxByLoop() method:

int result = indexOfTheMaxByLoop(ARRAY);
assertEquals(3, result);
 
result = indexOfTheMaxByLoop(new int[] {});
assertEquals(-1, result);

As we can see, this approach reports the correct result.

5. Using the Stream API

In the indexOfTheMaxByLoop() method, we solve the problem using a classic for-loop to walk through the input array. Alternatively, we can implement the same logic using the Stream API.

Next, let’s see how to achieve it:

int indexOfTheMaxByStream(int[] array) {
    return IntStream.range(0, array.length)
      .boxed()
      .max(Comparator.comparingInt(i -> array[i]))
      .orElse(-1);
}

As the code shows, we construct an IntStream using the range() method. It’s important to note that this IntStream contains the array’s indexes instead of the array elements.

Then, we pass a Comparator to the max() method, which compares the element value (array[i]). As the result is an Optional we used orElse(-1) to get the index. This isn’t new to us.

Finally, let’s create a test to verify if indexOfTheMaxByStream() does the job:

int result = indexOfTheMaxByStream(ARRAY);
assertEquals(3, result);
 
result = indexOfTheMaxByStream(new int[] {});
assertEquals(-1, result);

As the test shows, indexOfTheMaxByStream() gives the correct result for empty and non-empty array inputs.

6. Conclusion

Finding the index of the largest value in an array is a straightforward task in Java. In this article, we’ve explored different ways to solve this problem.

As always, the complete source code for the examples is available over on GitHub.

       

Split a String Based on the Last Occurrence of a Character

$
0
0
start here featured

1. Overview

String manipulation is a crucial skill in Java programming. One common task is splitting a String based on the last occurrence of a specific character.

In this quick tutorial, we’ll explore different ways to achieve this.

2. Introduction to the Problem

Our goal is to split a String into two parts by the last occurrence of the specified character. An example can explain it quickly.

Let’s say we’re given a String input:

static final String INPUT1 = "a b c@e f g@x y z";

Now, we’d like to split the String by the last ‘@‘ character and obtain a String array with two elements:

static final String[] EXPECTED1 = new String[] { "a b c@e f g", "x y z" };

The example shows that the input has two ‘@‘ characters, but we must split it only by the last ‘@.

Sometimes, an input contains only one ‘@‘ and the character is at the beginning, for instance:

static final String INPUT2 = "@a b c";

In this scenario, we expect to have a String array with two elements. One is an empty String, and the other is the substring after ‘@‘:

static final String[] EXPECTED2 = new String[] { "", "a b c" };

Similarly, the unique ‘@‘ character can be located at the very end of the input:

static final String INPUT3 = "a b c@";
static final String[] EXPECTED3 = new String[] { "a b c", "" };

Finally, there is still one special case: the input doesn’t contain a ‘@‘ character. In this case, the result String array should only contain one element: the input itself.

static final String INPUT4 = "a b c";
static final String[] EXPECTED4 = new String[] { "a b c" };

In this tutorial, we’ll address two methods to solve the problem. For simplicity, we’ll skip null checks in the implementations.

Next, let’s dive into the implementation.

3. Using lastIndexOf()

A straightforward way to solve the problem is first to find the location of the last ‘@‘ in the input String and then obtain the two substrings before and after the last ‘@‘ character.

Java’s lastIndexOf() and substring() methods can help us achieve our goal:

String[] splitByLastOccurrence(String input, char character) {
    int idx = input.lastIndexOf(character);
    return new String[] { input.substring(0, idx), input.substring(idx + 1) };
}

This implementation works for most inputs. However, it fails if the input doesn’t contain character, such as our INPUT4. This is because idx will be -1, and substring(0, -1) throws an exception.

So, we must handle this special case to make the method work for all input Strings:

String[] splitByLastOccurrence(String input, char character) {
    int idx = input.lastIndexOf(character);
    if (idx < 0) {
        return new String[] { input };
    }
    return new String[] { input.substring(0, idx), input.substring(idx + 1) };
}

Next, let’s create a test to verify whether splitByLastOccurrence() works as expected:

String[] result1 = splitByLastOccurrence(INPUT1, '@');
assertArrayEquals(EXPECTED1, result1);
 
String[] result2 = splitByLastOccurrence(INPUT2, '@');
assertArrayEquals(EXPECTED2, result2);
 
String[] result3 = splitByLastOccurrence(INPUT3, '@');
assertArrayEquals(EXPECTED3, result3);
 
String[] result4 = splitByLastOccurrence(INPUT4, '@');
assertArrayEquals(EXPECTED4, result4);

The test shows this solution works for all scenarios.

4. Using split()

The String.split() method is a convenient tool for solving String splitting problems. So next, let’s create the regex pattern to match the last ‘@‘ character, then we can solve our problem using split().

A positive lookahead can help us match the last ‘@‘ character: “@(?=[^@]*$)“. This pattern effectively matches the last ‘@‘ in a String by ensuring no other ‘@‘ characters after it.

Next, let’s see if we can get the expected results using split() with this regex pattern:

String regex = "@(?=[^@]*$)";
 
String[] result1 = INPUT1.split(regex);
assertArrayEquals(EXPECTED1, result1);
 
String[] result2 = INPUT2.split(regex);
assertArrayEquals(EXPECTED2, result2);
 
String[] result3 = INPUT3.split(regex);
assertArrayEquals(new String[] { "a b c d" }, result3);
 
String[] result4 = INPUT4.split(regex);
assertArrayEquals(EXPECTED4, result4);

As we can see, the split() approach works for INPUT1, INPUT2, and INPUT4. However, after we split() INPUT3 (“a b c@”), the result array only contains one element. This is because if we don’t pass the limit parameter to split(), split() takes zero as limit. Thus, split() discards the trailing empty Strings.

We can pass limit=2 to split() to fix this problem:

String regex = "@(?=[^@]*$)";
 
String[] result1 = INPUT1.split(regex, 2);
assertArrayEquals(EXPECTED1, result1);
 
String[] result2 = INPUT2.split(regex, 2);
assertArrayEquals(EXPECTED2, result2);
 
String[] result3 = INPUT3.split(regex, 2);
assertArrayEquals(EXPECTED3, result3);
 
String[] result4 = INPUT4.split(regex, 2);
assertArrayEquals(EXPECTED4, result4);

As the test shows, it works for all cases when we pass 2 as limit to split().

5. Conclusion

In this article, we’ve explored two approaches to splitting a String by the last occurrence of a character through examples. By applying these methods, we can efficiently handle various scenarios, ensuring robust code for String manipulation tasks.

As always, the complete source code for the examples is available over on GitHub.

       

Find the Closest Number to Zero in a Java Array

$
0
0
Contact Us Featured

1. Overview

In this tutorial, we’ll delve into the problem of finding the closest number to zero within a Java array. For example, given the array, [1, -3, 2, -2, 4], the number closest to zero is 1. We’ll explore various techniques to efficiently find this number and enhance our problem-solving repertoire.

2. Approaches to Find the Closest Number to Zero

We’ll discuss several approaches, each with advantages and trade-offs. First, we’ll look at a brute force method, followed by an optimized approach using sorting and binary search, and finally, an alternative technique utilizing a priority queue.

2.1. The Brute Force Approach

This method involves a straightforward iteration through the array, calculating the absolute difference between each element and zero, and keeping track of the minimum difference encountered so far. If two numbers have the same absolute difference from zero, the method prioritizes the positive number to ensure a consistent and predictable result.

Let’s begin with an implementation:

public static int findClosestToZero(int[] arr) throws IllegalAccessException {
    if (arr == null || arr.length == 0) {
        throw new IllegalAccessException("Array must not be null or Empty");
    }
    int closest = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if ((Math.abs(arr[i]) < Math.abs(closest)) ||
          ((Math.abs(arr[i]) == Math.abs(closest)) && (arr[i] > closest))) {
            closest = arr[i];
        }
    }
    return closest;
}

This approach provides a basic yet effective way to find the element closest to zero in an array of integers. Let’s illustrate this with a test:

@Test
void whenFindingClosestToZeroWithBruteForce_thenResultShouldBeCorrect()
  throws IllegalAccessException {
    int[] arr = {1, 60, -10, 70, -80, 85};
    assertEquals(1, BruteForce.findClosestToZero(arr));
}

This approach begins by sorting the array, simplifying the problem by arranging elements in ascending order of their absolute values. After sorting, a binary search algorithm is applied to efficiently locate the element closest to zero.

Let’s look at the implementation:

public static int findClosestToZero(int[] arr) {
    if (arr == null || arr.length == 0) {
        throw new IllegalArgumentException("Array must not be null or Empty");
    }
    Arrays.sort(arr);
    int closestNumber = arr[0];
    int left = 0;
    int right = arr.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (Math.abs(arr[mid]) < Math.abs(closestNumber)) {
            closestNumber = arr[mid];
        }
        if (arr[mid] < 0) {
            left = mid + 1;
        } else if (arr[mid] > 0) {
            right = mid - 1;
        } else {
            return arr[mid];
        }
    }
    return closestNumber;
}

This implementation sorts the input array and then applies a binary search to check the middle of the current search range, narrowing it down to find the number with the smallest absolute value. Let’s validate this with a test:

@Test
void whenFindingClosestToZeroWithBruteForce_thenResultShouldBeCorrect() throws IllegalAccessException {
    int[] arr = {1, 60, -10, 70, -80, 85};
    assertEquals(1, SortingAndBinarySearch.findClosestToZero(arr));
}

For the array arr, the algorithm first sorts it in ascending order, thus [-80, -10, 1, 60, 70, 85]. The method initializes closestNumber to the first element, -80, and iterates using binary search. It updates closestNumber to 1 after finding that 1 is closer to zero than other elements from the first iteration. The first iteration also produces this sub array from the original array, [-80, -10, 1]. The binary search checks middle elements and adjusts the search range until it exits, confirming 1 as the closest to zero.

2.3. Approach Using Priority Queue

An alternative technique involves utilizing a priority queue to efficiently find the closest number to zero without sorting the entire array. It finds the closest number to zero by adding each number to the queue and keeping only the smallest number based on its absolute value.

Let’s implement this approach in Java:

public static int findClosestToZeroWithPriorityQueue(int[] arr, int k) {
    if (arr == null || arr.length == 0 || k <= 0) {
        throw new IllegalArgumentException("Invalid input");
    }
    PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> Math.abs(b) - Math.abs(a));
    for (int num : arr) {
        pq.offer(num);
        if (pq.size() > k) {
            pq.poll();
        }
    }
    return pq.peek();
}

The comparator Math.abs(b) – Math.abs(a) ensures that the priority queue orders elements based on their absolute values, with the farthest from zero having the lowest priority. As we iterate through the array, each element is added to the priority queue. If the size of the queue exceeds k, the element that is farthest from zero is removed, ensuring the queue maintains only the kth closest numbers to zero. Finally, pq.peek() returns the element that is closest to zero.

Let’s test it:

@Test
void whenFindingClosestToZeroWithBruteForce_thenResultShouldBeCorrect()
  throws IllegalAccessException {
    int[] arr = {1, 60, -10, 70, -80, 85};
    assertEquals(1, PriorityQueueToZero.findClosestToZeroWithPriorityQueue(arr, 1));
}

For this test, the priority queue is initialized to hold only one element, representing the closest number to zero. The algorithm iterates through the array [1, 60, -10, 70, -80, 85], processing each element sequentially. Initially, 1 is added to the queue as it’s empty. As 60 is farther from zero compared to 1, it’s not added. When -10 is encountered, it’s not added because 1 is closer to zero in terms of absolute value. Subsequent elements 70, -80, and 85 are all farther from zero compared to -10, so none of them are added to the queue. After processing all elements, the queue retains 1 as the closest element to zero.

3. Comparison of Approaches

While all three approaches aim to find the closest number to zero within a Java array, they exhibit different characteristics.

  • Brute force approach: although simple, it becomes increasingly inefficient for larger arrays due to its linear time complexity.
  • Optimized approach with sorting and binary search: it offers better performance, especially for larger arrays, as its time complexity is O(n log n), which is significantly better than linear time. However, sorting the array might not be feasible in certain scenarios, making the brute force approach more suitable.
  • Priority queue approach: it strikes a balance between simplicity and performance, making it suitable for scenarios where finding a subset of closest numbers is sufficient and sorting the array isn’t feasible.

4. Conclusion

In this article, we’ve explored approaches to tackle the problem of finding the closest number to zero in a Java array.

The brute force Approach is straightforward and works well for small arrays, but becomes inefficient with larger datasets.

On the other hand, the sorting and binary search Approach improves performance with a time complexity of O(n log n), but it may not be suitable for memory-constrained scenarios.

Finally, the priority queue approach balances simplicity and efficiency, making it ideal for dynamic data or memory-limited situations. Each method has its strengths, emphasizing the importance of selecting the right algorithm based on the problem’s specific requirements.

The code used in this tutorial is available over on GitHub.

       

Fixing the “Could Not Determine Recommended JdbcType for Class” Error in JPA

$
0
0

1. Introduction

In this tutorial, we’ll explore the causes of the “could not determine recommended JdbcType for class” error in Hibernate and discuss how to resolve it.

2. Common Causes

When we encounter this error message, it’s usually because JPA (Java Persistence API) is throwing an exception. This happens when JPA can’t determine the recommended JDBC type for a class. Typically, this occurs during the startup of our Hibernate application, when Hibernate is trying to create the database schema or validate the mappings.

3. Complex Data Types

One common cause of the “could not determine recommended JdbcType for class” error in JPA is when we’re using complex data types or other non-primitive types in our entity classes. A typical example of this is using a java.util.Map to store dynamic key-value pairs.

Let’s consider the following entity class:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Map<String, String> studentDetails;
    // getters and setters
}

In the above example, the studentDetails field is a Map<String, String>. JPA may throw an error because it can’t automatically determine the recommended JDBC type for this complex data type. To demonstrate this, let’s try to set up an EntityManagerFactory with this entity class:

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-complextype"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'java.util.Map<java.lang.String, java.lang.String>'");

To resolve this issue, we can use the @JdbcTypeCode annotation to specify the JDBC type that Hibernate should use for the studentDetails field. In this case, we can use the JSON data type to store the map as a JSON object.

First, we need to add the Jackson dependency to our pom.xml file:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Jackson is a popular library for JSON processing in Java, and it provides the necessary tools for mapping JSON data types. Next, we use the @JdbcTypeCode annotation to tell Hibernate to use the JSON data type for the studentDetails field. Here’s the updated entity class:

@Entity
public class StudentWithJson {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @JdbcTypeCode(SqlTypes.JSON)
    private Map<String, String> studentDetails;
    // getters and setters
}

By adding the @JdbcTypeCode annotation, we explicitly instruct Hibernate on handling the complex data type. The SqlTypes.JSON constant is used to indicate the JDBC type as JSON. Hibernate now stores the studentDetails field as a JSON object in the database, allowing it to properly map the Java Map to a JSON column in the database:

EntityManagerFactory factory = createEntityManagerFactory("jpa-undeterminejdbctype-complextypewithJSON");
EntityManager entityManager = factory.createEntityManager();
entityManager.getTransaction().begin();
StudentWithJson student = new StudentWithJson();
Map<String, String> details = new HashMap<>();
details.put("course", "Computer Science");
details.put("year", "2024");
student.setStudentDetails(details);
entityManager.persist(student);
entityManager.getTransaction().commit();
StudentWithJson retrievedStudent = entityManager.find(StudentWithJson.class, student.getId());
assertEquals(student.getStudentDetails(), retrievedStudent.getStudentDetails());

4. JPA Relationship Mapping

JPA relies on annotations to understand the relationships between entities and their field types. Omitting these annotations can lead to Hibernate being unable to determine the recommended JDBC type for the fields involved, causing errors.

Let’s explore two common relationship mappings: @OneToOne and @OneToMany, and see how missing annotations can cause issues.

4.1. One-to-One Mapping

We’ll demonstrate a scenario where an Employee entity has a one-to-one relationship with an Address entity. If we forget to annotate this relationship, Hibernate may throw a PersistenceException because it can’t determine the appropriate JDBC type for the Address field:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Address address;
    // setter and getter
}

In this case, when we try to initialize the EntityManagerFactory, we’ll encounter the following exception:

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-relationship"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'com.baeldung.jpa.undeterminejdbctype.Address'");

The error occurs because Hibernate doesn’t know how to map the address field to the database. To fix this, we need to properly annotate the one-to-one relationship. Let’s add the necessary annotations to the Employee entity:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;
    // setters and getters
}

By adding the @OneToOne annotations, we’re instructing Hibernate on handling the relationship between Employee and Address. Now, Hibernate knows that the address field in the Employee entity is mapped to the Address entity, and it uses the address_id column in the Employee table to establish this relationship.

4.2. One-to-Many Mapping

Similar to one-to-one mapping, when dealing with one-to-many relationships in JPA, proper annotations are essential to avoid exceptions. Let’s explore this with a one-to-many mapping example between the Department and Employee entities.

In this scenario, a Department can have multiple Employees. If we forget to annotate this relationship correctly, Hibernate may throw a PersistenceException because it can’t determine the appropriate JDBC type for the employees field.

Here is the initial setup without proper annotations:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private List<Employee> employees = new ArrayList<>();
    // setters and getters
}

This setup results in an exception when initializing the EntityManagerFactory:

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-relationship"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'java.util.List<com.baeldung.jpa.undeterminejdbctype.Employee>'");

The error occurs because Hibernate doesn’t know how to map the employees field to the database. To fix this, we need to annotate the one-to-many relationship in the Department entity properly.

Let’s add the necessary annotations to the Department entity:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private List<Employee> employees = new ArrayList<>();
    // setters and getters
}

We often add the @OneToMany relationship in the Department class but forget to annotate the Employee class with the @ManyToOne relationship, leading to such errors. Here’s how to properly annotate both sides of the relationship:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;
    // setters and getters
}

This setup ensures that Hibernate can correctly map the employees field in the Department entity and the department field in the Employee entity, avoiding the PersistenceException and enabling proper database interactions.

5. Conclusion

In this article, we’ve explored the causes of the “could not determine recommended JdbcType for class” error in Hibernate and discussed how to resolve it. We’ve seen how complex data types and relationship mappings can lead to this error and looked at solutions to prevent it.

As always, the source code for the examples is available over on GitHub.

       

How to Pass JVM Arguments via Maven

$
0
0

1. Overview

In this tutorial, we’ll run Java code through Maven with JVM arguments. We’ll explore how to apply those arguments to the build globally and then focus on applying them to specific Maven plugins.

2. Setting up Global JVM Arguments

First, we’ll see two different techniques for applying JVM arguments to the main Maven process.

2.1. Using Command-Line

To run a Java Maven project with JVM arguments, we’ll set the MAVEN_OPTS environment variable. This variable contains the parameters to use on JVM start-up and allows us to supply additional options:

$ export MAVEN_OPTS="-Xms256m -Xmx512m"

In this example, we define the minimal and maximal heap sizes through MAVEN_OPTS. Then, we can run our build via:

$ mvn clean install

The arguments we set up apply to the main process of the build.

2.2. Using the jvm.config File

An alternative to set up global JVM arguments automatically is to define a jvm.config file. We must locate this file in a .mvn folder at the root of our project. The content of the file is the JVM parameters to apply. For example, to mimic the command line we used previously, our configuration file would read:

-Xms256m -Xmx512m

3. Setting JVM Arguments to a Specific Plugin

Maven plugins may fork new JVM processes to execute their task. Thus, setting up global arguments doesn’t work with them. Every plugin defines its way of setting up the arguments, but most configurations look alike. In particular, we’ll showcase examples of three widely used plugins: the spring-boot Maven plugin, the surefire plugin, and the failsafe plugin.

3.1. Setup Example

Our idea is to set up a basic Spring project, but we want the code to be runnable only when we set some JVM argument.

Let’s start by writing our service class:

@Service
class MyService {
    int getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        ArrayList<String> arr = new ArrayList<>();
        Field sizeField = ArrayList.class.getDeclaredField("size");
        sizeField.setAccessible(true);
        return (int) sizeField.get(arr);
    }
}

The introduction of the module system in Java 9 brought more restrictions to reflective access. Thus, this piece of code compiles in Java 8 but requires adding the –add-open JVM option to open the java-util package of the java-base module for reflection in Java 9 and later.

Now, we can add a straightforward controller class on top of it:

@RestController
class MyController {
    private final MyService myService;
    public MyController(MyService myService) {
        this.myService = myService;
    }
    @GetMapping("/length")
    Integer getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        return myService.getLength();
    }
}

To finish our setup, let’s add the main application class:

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

3.2. Spring-Boot Maven Plugin

Let’s add the base configuration of the latest version of Maven’s spring-boot plugin:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>3.3.0</version>
</plugin>

We can now run the project via the following command:

$ mvn spring-boot:run

As we can see, the application starts successfully. However, when we try to make a GET request on http://localhost:8080/length, we get an error associated with the following log:

java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.util.ArrayList.size accessible: module java.base does not "opens java.util" to unnamed module

As we said during setup, we must add some reflection-related JVM arguments to avoid this error. In particular, for the spring-boot Maven plugin, we need to use the jvmArguments configuration tag:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <jvmArguments>--add-opens java.base/java.util=ALL-UNNAMED</jvmArguments>
    </configuration>
</plugin>

We can now re-run the project: the GET request on http://localhost:8080/length now returns 0, the expected answer.

3.3. Surefire Plugin

First, let’s add a unit test for our service:

class MyServiceUnitTest {
    MyService myService = new MyService();
    
    @Test
    void whenGetLength_thenZeroIsReturned() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        assertEquals(0, myService.getLength());
    }
}

The Surefire plugin is commonly used to run unit tests through Maven. Let’s naively add the base configuration of the latest version of the plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <includes>
            <include>**/*UnitTest.java</include>
        </includes>
    </configuration>
</plugin>

The shortest way to run our unit tests via Maven is to run the following command:

$ mvn test

We can see that our test is failing with the same error we got previously! Nevertheless, with the surefire plugin, the correction lies in setting the argLine configuration tag:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <includes>
            <include>**/*UnitTest.java</include>
        </includes>
        <argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
    </configuration>
</plugin>

Our unit tests can now run successfully!

3.4. Failsafe Plugin

We’ll now write an integration test for our controller:

@SpringBootTest(classes = MyApplication.class)
@AutoConfigureMockMvc
class MyControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    void whenGetLength_thenZeroIsReturned() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/length"))
          .andExpect(MockMvcResultMatchers.status().isOk())
          .andExpect(MockMvcResultMatchers.content().string("0"));
    }
}

Generally, we use the failsafe plugin to run integration tests through Maven. Once again, let’s set it up with the base configuration of its latest version:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <includes>
            <include>**/*IntegrationTest.java</include>
        </includes>
    </configuration>
</plugin>

Let’s now run our integration tests:

$ mvn verify

Without any surprise, our integration test fails for the same error that we already experienced. The correction for the failsafe plugin is identical to the one for the surefire plugin. We must use the argLine configuration tag to set the JVM argument:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <includes>
            <include>**/*IntegrationTest.java</include>
        </includes>
        <argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
    </configuration>
</plugin>

The integration test will now run with success.

4. Conclusion

In this article, we learned how to use JVM arguments when running Java code in Maven. We’ve explored two techniques to set global arguments to the build. Then, we saw how to pass JVM arguments to some of the most used plugins. Our examples were not exhaustive, but, in general, other plugins work very similarly. To recap, we can always refer to a plugin documentation to know exactly how to configure it.

As always, the code is available over on GitHub.

       

Java Enums, JPA and PostgreSQL Enums

$
0
0

1. Introduction

In this tutorial, we’ll explore the concepts of Java enums, JPA, and PostgreSQL enums, and learn how to use them together to create a seamless mapping between Java enums and PostgreSQL enums.

2. Java enum

Java enums are a special type of class that represents a fixed number of constants. Enums are used to define a set of named values that have underlying types, such as strings or integers. Enums are useful when we need to define a set of named values that have a specific meaning in our application.

Here is an example of a Java enum:

public enum OrderStatus {
    PENDING, IN_PROGRESS, COMPLETED, CANCELED
}

In this example, the OrderStatus enum defines four constants. These constants can be used in our application to represent the status of an order.

3. Using @Enumerated Annotation

When using Java enums with JPA, we need to annotate the enum field with @Enumerated to specify how the enum value should be stored in the database.

First, we define an entity class named CustomerOrder annotated with @Entity to mark it for JPA persistence:

@Entity
public class CustomerOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated() 
    private OrderStatus status;
    // ... other fields and methods
}

By default, JPA stores the enum value as an integer, representing the ordinal position of the enum constant. For example, if we have an enum OrderStatus with values PENDING, IN_PROGRESS, COMPLETED, and CANCELED, the default behavior would store them as integers 0, 1, 2 and 3, respectively. The resulting database table would have a status column of type smallint, with values 0 to 3:

create table customer_order (
    id bigserial not null,
    status smallint check (status between 0 and 3),
    primary key (id)
);

However, this default behavior can lead to problems if we change the order of the enum constants in our Java code. For example, if we swap the order of IN_PROGRESS and PENDING, the database values would still be 0, 1, 2 and 3, but they would no longer match the updated enum order. This can lead to inconsistencies and errors in our application.

To avoid this problem, we can use EnumType.STRING to store the enum value as a string in the database. This approach ensures that the enum value is stored in a human-readable format, and we can change the order of the enum constants without affecting the database values:

@Enumerated(EnumType.STRING)
private OrderStatus status;

This instructs JPA to store the string representation of the OrderStatus enum value (e.g., “PENDING“) in the database instead of the ordinal position (integer index). The resulting database table would have a status column of type varchar that can hold the specific string values defined in our enum:

create table customer_order (
    id bigint not null,
    status varchar(16) check (status in ('PENDING','IN_PROGRESS', 'COMPLETED', 'CANCELLED')),
    primary key (id)
);

4. The Challenge of Mapping Java Enums to PostgreSQL Enums

Mapping Java enums to PostgreSQL enums can be challenging due to differences in their handling and capabilities. Even with the EnumType.STRING, JPA still doesn’t know how to map the Java enum to the PostgreSQL enum. To demonstrate the problem, let’s create a PostgreSQL enum type:

CREATE TYPE order_status AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELED');

Next, we create a table that uses the PostgreSQL enum type:

CREATE TABLE customer_order (
    id BIGINT NOT NULL,
    status order_status,
    PRIMARY KEY (id)
);

We’ve updated the status column to use the PostgreSQL enum type order_status. Now, let’s try to insert some data into the table:

CustomerOrder order = new CustomerOrder();
order.setStatus(OrderStatus.PENDING);
session.save(order);

However, when we try to insert the data, we’ll get an exception:

org.hibernate.exception.SQLGrammarException: could not execute statement 
  [ERROR: column "status" is of type order_status but expression is of type character varying

The SQLGrammarException occurs because JPA doesn’t know how to map the Java enum OrderStatus to the PostgreSQL enum order_status.

5. Using @Type Annotation

In Hibernate 5, we can leverage the Hypersistence Utils library to address this challenge. This library provides additional types, including support for PostgreSQL enums.

First, we need to add the Hypersistence Utils dependency to our pom.xml:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-55</artifactId>
    <version>3.7.0</version>
</dependency>

The Hypersistence Utils library includes a PostgreSQLEnumType class that handles the conversion between Java enums and PostgreSQL enum types. We’ll use this class as our custom type handler.

Next, we can annotate the enum field using the @Type annotation and define the custom type handler in our entity class:

@Entity
@TypeDef(
    name = "pgsql_enum",
    typeClass = PostgreSQLEnumType.class
)
public class CustomerOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id;
    @Enumerated(EnumType.STRING)
    @Column(columnDefinition = "order_status")
    @Type(type = "pgsql_enum")
    private OrderStatus status;
    // ... other fields and methods
}

By following these steps, we can effectively map PostgreSQL enum types to Java enums in Hibernate 5.

6. Using PostgreSQLEnumJdbcType

In Hibernate 6, we can use the @JdbcType annotation directly on our enum field to specify a custom JDBC type handler. This annotation allows us to define a custom JdbcType class to handle the mapping between our Java enum type and the corresponding JDBC type.

Here’s how we can use @JdbcType in our CustomerOrder entity:

@Enumerated(EnumType.STRING)
@JdbcType(type = PostgreSQLEnumJdbcType.class)
private OrderStatus status;

We’re specifying the PostgreSQLEnumJdbcType class as the custom type handler. This class is part of Hibernate 6 and is responsible for handling the conversion between the Java enum string and the PostgreSQL enum type.

When we persist an OrderStatus object (e.g., order.setStatus(OrderStatus.PENDING)), Hibernate first converts the enum value to its string representation (“PENDING“). Then, the PostgreSQLEnumJdbcType class takes the string value (“PENDING“) and converts it into a format suitable for the PostgreSQL enum type. The converted value is then passed to the database for storage in the order_status column.

7. Inserting Enum Values Using Native Query

When using a native query to insert data into a PostgreSQL table, the type of the data being inserted must match the column type. For an enum type column, PostgreSQL expects the value to be of the enum type, not just a plain string. 

Let’s try using a native query without casting:

String sql = "INSERT INTO customer_order (status) VALUES (:status)";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED); // Use the string representation of the enum

We get the following error:

org.postgresql.util.PSQLException: ERROR: column "status" is of type order_status but expression is of type character varying

This error occurs because PostgreSQL expects the value to be of type order_status but receives a character varying instead. To resolve this, we explicitly cast the value to the enum type in the native query:

String sql = "INSERT INTO customer_order (status) VALUES (CAST(:status AS order_status))";
Query query = session.createNativeQuery(sql);
query.setParameter("status", OrderStatus.COMPLETED);

The CAST(:status AS order_status) part in the SQL statement ensures that the string value “COMPLETED” is explicitly cast to the order_status enum type in PostgreSQL.

8. Conclusion

In this article, we’ve explored how to map between Java enums and PostgreSQL enums using JPA. By using PostgreSQLEnumJdbcType we ensure seamless integration between Java enum and PostgreSQL enum.

As always, the source code for the examples is available over on GitHub. The source code for the Hibernate 5 examples is available over on GitHub.

       

Java Weekly, Issue 547

$
0
0

1. Spring and Java

>> Debugger.godMode() – Hacking a JVM application with the debugger [foojay.io]

Using a debugger to find out how an application works under the hood.

>> Hibernate WITH RECURSIVE query [vladmihalcea.com]

Let’s see how Hibernate’s WITH RECURSIVE query works and how we can use it to fetch hierarchical data structures.

>> Should your tests manage transactions? [thorben-janssen.com]

And a piece on how tests should be running under the same conditions as our production code, including transaction handling bits and bobs.

Also worth reading:

Webinars and presentations:

Time to upgrade:

2. Technical & Musings

>> JetBrains Aqua IDE for Test Automation Now Generally Available [infoq.com]

Meet Aqua: the first IDE for test automation is now generally available, supporting the likes of Selenium and Cypress. Interesting.

Also worth reading:

3. Pick of the Week

>> The real 10x developer makes their whole team better [stackoverflow.blog]

       

Finding the Max Value in Spring Data JPA

$
0
0

1. Introduction

When using Spring Data JPA, finding specific values in our database is a common task. One such task is finding the maximum value in a particular column.

In this tutorial, we’ll explore several ways to achieve this using Spring Data JPA. We’ll check how to use repository methods, JPQL and native queries, and the Criteria API to find the maximum value in a database column.

2. Entity Example

Before we move on, we have to add a required spring-boot-starter-data-jpa dependency to our project:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

After that, let’s define a simple entity to work with:

@Entity
public class Employee {
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private Long salary;
    // constructors, getters, setters, equals, hashcode
}

In the following examples, we’ll find the maximum value of the salary column across all employees using different approaches.

3. Using Derived Queries in a Repository

Spring Data JPA provides a powerful mechanism to define custom queries using repository methods. One of these mechanisms is derived queries, which allow us to implement SQL queries by declaring the method name.

Let’s create a repository for the Employee class:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
    Optional<Employee> findTopByOrderBySalaryDesc();
}

We just implemented a method that uses the query derivation mechanism to generate the appropriate SQL. According to the method name, we’re sorting all employees by their salary in descending order and then returning the first one, which is the employee with the highest salary.

Notably, this approach always returns an entity with all eager properties set. However, if we just want to retrieve a single salary value, we can slightly modify our code by implementing a projection feature.

Let’s create an additional interface and modify the repository:

public interface EmployeeSalary {
    Long getSalary();
}
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
    Optional<EmployeeSalary> findTopSalaryByOrderBySalaryDesc(); 
}

This solution is useful when we need to return only specific parts of the entity.

4. Using JPQL

Another straightforward approach is to use the @Query annotation. This lets us define a custom JPQL (Java Persistence Query Language) query directly in the repository interface.

Let’s implement our JQPL query to retrieve the maximum salary value:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
    @Query("SELECT MAX(e.salary) FROM Employee e")
    Optional<Long> findTopSalaryJQPL();
}

As before, the method returns the highest salary value among all employees. Moreover, we can easily retrieve a single column of an entity without any additional projections.

5. Using a Native Query

We’ve just introduced the @Query annotation in our repository. This approach also allows us to write raw SQL directly using native queries.

To achieve the same result, we can implement:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
    @Query(value = "SELECT MAX(salary) FROM Employee", nativeQuery = true)
    Optional<Long> findTopSalaryNative();
}

The solution is similar to JPQL. Using native queries can be useful to leverage specific SQL features or optimizations.

6. Implementing a Default Repository Method

We can also use custom Java code to find the maximum value. Let’s implement another solution without adding additional queries:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
    default Optional<Long> findTopSalaryCustomMethod() {
        return findAll().stream()
          .map(Employee::getSalary)
          .max(Comparator.naturalOrder());
    }
}

We extended our repository by adding a new default method with our custom logic. We retrieve all Employee entities using a built-in findAll() method, then stream them and find the maximum salary. Unlike previous approaches, all filtering logic occurs at the application layer, not the database.

7. Using Pagination

Spring Data JPA provides support for pagination and sorting features. We can still use them to find the maximum salary of our employees.

We can reach our goal even without implementing any dedicated query or extending the repository:

public Optional<Long> findTopSalary() {
    return findAll(PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "salary")))
      .stream()
      .map(Employee::getSalary)
      .findFirst();
}

As we know, a PagingAndSortingRepository interface provides additional support for Pageable and Sort types. Therefore, our built-in findAll() method in the JpaRepository can also accept these parameters. We just implemented a different approach without adding additional methods in the repository.

8. Using Criteria API

Spring Data JPA also provides the Criteria API – a more programmatic approach to construct queries. It’s a more dynamic and type-safe way to build complex queries without using raw SQL.

First, let’s inject the EntityManager bean into our service and then create a method to find the maximum salary using the Criteria API:

@Service
public class EmployeeMaxValueService {
    @Autowired
    private EntityManager entityManager;
    
    public Optional<Long> findMaxSalaryCriteriaAPI() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> query = cb.createQuery(Long.class);
        Root<Employee> root = query.from(Employee.class);
        query.select(cb.max(root.get("salary")));
        TypedQuery<Long> typedQuery = entityManager.createQuery(query);
        return Optional.ofNullable(typedQuery.getSingleResult());
    }
}

In this method, we first obtain a CriteriaBuilder instance from the injected EntityManager bean. We then create a CriteriaBuilder to specify the result type, and a Root to define the FROM clause. Finally, we select the maximum value of the salary field and execute the query.

Once again, we’ve just retrieved the maximum salary for all employees. However, this approach is more complex than the previous one, so it may be a bit overwhelming if we need to implement a simple query. This solution may be useful if we have more complex structures that cannot be handled by simply expanding the repository.

9. Conclusion

In this article, we’ve explored various methods to find the maximum value of a column using Spring Data JPA.

We started with derived queries, which provide a simple and intuitive way to define queries just by method naming conventions. Then, we looked into using JPQL and native queries with the @Query annotation, offering more flexibility and direct control over the SQL being executed.

We also implemented a custom default method in the repository to leverage Java’s Stream API for processing data at the application level. Additionally, we checked how to use pagination and sorting to find the result using only built-in API.

Finally, we utilized the Criteria API for a more programmatic and type-safe approach to building complex queries. By understanding these different approaches, we can choose the most suitable one for a specific use case, balancing simplicity, control, and performance.

The complete source code used for this tutorial is available over on GitHub.

       

How to Delay a Stubbed Method Response With Mockito

$
0
0
Contact Us Featured

1. Introduction

Mockito allows developers to stub methods to return specific values or perform certain actions. In some test scenarios, we may need to introduce a delay in the response of a stubbed method to simulate real-world conditions, such as network latency or slow database queries.

In this tutorial, we’ll explore different ways to introduce such delays using Mockito.

2. Understanding the Need for Delay in Stubbed Methods

Introducing a delay in stubbed methods can be beneficial in several situations:

  • Simulating real-world conditions: Testing how the application handles delays and timeouts
  • Performance testing: Ensuring the application can handle slow responses gracefully
  • Debugging: Reproducing and diagnosing issues related to timing and synchronization

Adding delays in our tests can help us to create more robust and resilient applications that are better prepared for real-world scenarios.

3. Maven Dependencies

Let’s add the Maven dependencies for mockito-core and awaitility to our project:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.2.1</version>
    <scope>test</scope>
</dependency>

4. Setup

Let’s create a PaymentService to simulate processing payments:

public class PaymentService {
    public String processPayment() {
        // simulate processing payment and return completion status
        return "SUCCESS";
    }
}

We’ll also introduce a delay to simulate processing the payment in various ways.

5. Introduce Delay Using Thread.sleep()

The simplest way to introduce a delay is by using Thread.sleep() within the stubbed method. This method pauses the execution of the current thread for a specified number of milliseconds:

@Test
public void whenProcessingPayment_thenDelayResponseUsingThreadSleep() {
    when(paymentService.processPayment()).thenAnswer(invocation -> {
        Thread.sleep(1000); // Delay of one second
        return "SUCCESS";
    });
    long startTime = System.currentTimeMillis();
    String result = paymentService.processPayment();
    long endTime = System.currentTimeMillis();
    assertEquals("SUCCESS", result);
    assertTrue((endTime - startTime) >= 1000); // Verify the delay
}

In this example, the processPayment() method of the PaymentService mock will wait for two seconds before returning “SUCCESS”. This method is straightforward but blocks the thread, which might not be suitable for testing asynchronous operations or for use in a production environment.

6. Introduce Delay Using Mockito’s Answer

Mockito’s Answer interface provides a more flexible way to stub methods. We can use it to introduce delays along with other custom behaviors:

@Test
public void whenProcessingPayment_thenDelayResponseUsingAnswersWithDelay() throws Exception {
    when(paymentService.processPayment()).thenAnswer(AdditionalAnswers.answersWithDelay(1000, invocation -> "SUCCESS"));
    long startTime = System.currentTimeMillis();
    String result = paymentService.processPayment();
    long endTime = System.currentTimeMillis();
    assertEquals("SUCCESS", result);
    assertTrue((endTime - startTime) >= 1000); // Verify the delay
}

In this example, the answersWithDelay() method from Mockito’s AdditionalAnswers class is used to introduce a delay of 2 seconds before returning “SUCCESS”. This approach abstracts the delay logic, making the code cleaner and easier to maintain.

7. Introduce Delay Using Awaitility

Awaitility is a DSL for synchronizing asynchronous operations in tests. It can be used to wait for conditions to be met, but it can also introduce delays. This makes it particularly useful for testing asynchronous code:

@Test
public void whenProcessingPayment_thenDelayResponseUsingAwaitility() {
    when(paymentService.processPayment()).thenAnswer(invocation -> {
        Awaitility.await().pollDelay(1, TimeUnit.SECONDS).until(()->true);
        return "SUCCESS";
    });
    long startTime = System.currentTimeMillis();
    String result = paymentService.processPayment();
    long endTime = System.currentTimeMillis();
    assertEquals("SUCCESS", result);
    assertTrue((endTime - startTime) >= 1000); // Verify the delay
}

In this example, Awaitility.await().pollDelay(1, TimeUnit.SECONDS).until(() -> true) introduces a delay of at least one second before returning “SUCCESS”. Awaitility’s fluent API makes it easy to read and understand, and it provides powerful features for waiting on asynchronous conditions.

8. Ensuring Test Stability

To ensure test suite stability when introducing delays, consider the following best practices:

  • Set appropriate timeouts: Ensure timeouts are generous enough to accommodate delays but not too long to impact test execution time. This prevents tests from hanging indefinitely if something goes wrong.
  • Mock external dependencies: When possible, mock external dependencies to control and simulate delays reliably.
  • Isolate delayed tests: Isolate tests with delays to prevent them from affecting the overall test suite execution time. This can be done by grouping such tests into a separate category or running them in a different environment.

9. Conclusion

Delaying the response of stubbed methods in Mockito is useful for simulating real-world conditions, testing performance, and debugging.

In this article, we introduced these delays effectively by using Thread.sleep(), Awaitility, and Mockito’s Answer. Additionally, ensuring test stability is crucial for maintaining reliable, robust tests, and incorporating these techniques can help create more resilient applications prepared for real-world scenarios.

As always the code is available over on GitHub.

       

HTTP Request and Response Logging Using Logbook in Spring

$
0
0

1. Overview

HTTP API requests are part of most applications now. Logbook is an extensible Java library to enable complete request and response logging for different client and server-side technologies. It allows developers to log any HTTP traffic an application receives or sends. This can be used for log analysis, auditing, or investigating traffic issues.

In this article, let’s go through the integration of the Logbook library with a Spring Boot application.

2. Dependencies

To use the Logbook library with Spring Boot, we add the following dependency to the project:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>3.9.0</version>
</dependency>

We can find the latest version of the Logbook library in Maven Central.

3. Configuration

Logbook works with logback logging in the Spring Boot application. We need to add configuration to the logback-spring.xml and the application.properties files.

Once we add the Logbook library to pom.xml, the Logbook library is autoconfigured with Spring Boot. Let’s add a log level to the application.properties file:

logging.level.org.zalando.logbook.Logbook=TRACE

Log level TRACE enables the logging of HTTP requests and responses.

Also, we add the Logbook configuration in the logback-spring.xml file:

<logger name="org.zalando.logbook" level="INFO" additivity="false">
    <appender-ref ref="RollingFile"/>
</logger>

Once this is added, we can run the application with an HTTP request. After each HTTP request call, the Logbook library logs the request and the response to the log file path specified in the logback-spring.xml configuration:

11:08:14.737 [http-nio-8083-exec-10] TRACE org.zalando.logbook.Logbook - Incoming Request: eac2321df47c4414
Remote: 0:0:0:0:0:0:0:1
GET http://localhost:8083/api/hello?name=James HTTP/1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-encoding: gzip, deflate, br, zstd
...
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
11:08:14.741 [http-nio-8083-exec-10] TRACE org.zalando.logbook.Logbook - Outgoing Response: eac2321df47c4414
Duration: 4 ms
HTTP/1.1 200 OK
...
Date: Tue, 18 Jun 2024 05:38:14 GMT
Keep-Alive: timeout=60
Hello, James!

We’ve seen how the Logbook library is integrated with Spring Boot using minimal configuration. However, this is basic request and response logging. Let’s look into further configurations in the next subsections.

4. Filtering and Formatting

We can declare the Logbook library configuration bean:

@Bean
public Logbook logbook() {
    Logbook logbook = Logbook.builder()
      .condition(Conditions.exclude(Conditions.requestTo("/api/welcome"), 
        Conditions.contentType("application/octet-stream"), 
        Conditions.header("X-Secret", "true")))
      .sink(new DefaultSink(new DefaultHttpLogFormatter(), new DefaultHttpLogWriter()))
      .build();
    return logbook;
}

In the above code example, we declare the Logbook bean so that Spring Boot picks it up for loading configuration.

Now, we’ve specified conditions while building the Logbook bean. The request mapping specified in the exclude() method is excluded from logging. In this case, the Logbook library won’t log requests or responses for API mapped to path “/api/welcome“. Likewise, we’ve put filters on requests having content type using the contentType() method, and header using the header() method.

Similarly, HTTP APIs that should be included should be specified in the include() methodIf we don’t use the include() method, Logbook logs all requests except the ones mentioned in the exclude() method.

We should set the filter property in the application.properties file for this filter configuration to work. The property logbook.filter.enabled should be set to true:

logbook.filter.enabled=true

The Logbook library logs requests and responses with an sl4j logger that uses the org.zalando.logbook.Logbook category and the log level trace by default:

sink(new DefaultSink(
  new DefaultHttpLogFormatter(),
  new DefaultHttpLogWriter()
))

This default configuration enables logging in the application:

GET http://localhost:8083/api/hello?name=John HTTP/1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
....
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 47
Content-Type: text/html;charset=UTF-8
Date: Fri, 07 Jun 2024 11:12:27 GMT
Keep-Alive: timeout=60
Hello, John

It’s possible to log these responses into System.out or System.err, which prints on the console:

Logbook logbook = Logbook.builder()
  .sink(new DefaultSink(
    new DefaultHttpLogFormatter(),
    new StreamHttpLogWriter(System.out)
  ))
  .build();

We should avoid using console printing in a production environment, but it can be used in a development environment if necessary.

5. Sinks

Implementing the Sink interface directly allows for more sophisticated use cases, e.g., writing requests/responses to a structured persistent storage like a database.

5.1. Common Sinks

Logbook provides some implementations of Sink with commonly used log formats, i.e., CommonsLogFormatSink, and ExtendedLogFormatSink.

ChunkingSink splits long messages into smaller chunks and writes them individually while delegating them to another sink:

Logbook logbook = Logbook.builder()
  .sink(new ChunkingSink(sink, 1000))
  .build();

5.2. Logstash Sink

Logbook provides a logstash encoder in an additional library. Let’s see an example of LogstashLogbackSink. For this, we add logstash and logstash-encoder dependencies in the pom.xml:

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-logstash</artifactId>
    <version>3.9.0</version>
 </dependency>

Then, we change the encoder under the appender in logback-spring.xml. The log stash encoder enables LogstashLogbackSink:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    ... 
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

Now we declare LogstashLogbackSink and add it to the Logbook object builder:

HttpLogFormatter formatter = new JsonHttpLogFormatter(); 
LogstashLogbackSink logstashsink = new LogstashLogbackSink(formatter);
Logbook logbook = Logbook.builder()
  .sink(logstashsink)
  .build();

Here we’ve used JsonHttpLogFormatter with LogstashLogbackSink, This customization prints logs in the JSON format:

{
    "@timestamp": "2024-06-07T16:46:24.5673233+05:30",
    "@version": "1",
    "message": "200 OK GET http://localhost:8083/api/hello?name=john",
    "logger_name": "org.zalando.logbook.Logbook",
    "thread_name": "http-nio-8083-exec-6",
    "level": "TRACE",
    "http":  {
        ...
        "Content-Length": [
            "12"
        ],
        ...
        "body": "Hello, john!"
    }
}

The JSON log is printed in single lines; we’ve formatted it here for better readability.

We can change the log level while declaring the LogstashLogbackSink object:

LogstashLogbackSink logstashsink = new LogstashLogbackSink(formatter, Level.INFO);

We can also use SplunkHttpLogFormatter with the Logbook sinks. It prints the log in key-value format:

origin=remote ... method=GET uri=http://localhost:8083/api/hello?name=John host=localhost path=/api/hello ...

5.3. Composite Sink

Combining multiple sinks we can form a CompositeSink:

CompositeSink compsink = new CompositeSink(Arrays.asList(logstashsink, new CommonsLogFormatSink(new DefaultHttpLogWriter())));
Logbook logbook = Logbook.builder()
  .sink(compsink)
  .build();

This configuration logs request details using all the sinks specified in the composite sink. Here, the Logbook logs the request by combining two sinks:

... "message":"GET http://localhost:8083/api/hello?name=John",... uri":"http://localhost:8083/api/hello?name=John",...
... "message":"200 OK GET http://localhost:8083/api/hello?name=John",.."headers":{"Connection":["keep-alive"],...

6. Conclusion

In this article, we’ve learned how to integrate the Logbook library with Spring Boot using minimal configuration. Also, about filtering the request paths using exclude() and include() methods. We’ve also learned about customizing log format as needed using Sink implementations like LogstashLogbackSink with formatters like JsonHttpLogFormatter.

As always the source code examples are available over on GitHub.

       

Vertical Slice Architecture

$
0
0

1. Overview

In this tutorial, we’ll learn about Vertical Slice Architecture and how it attempts to solve the issues associated with layered designs. We’ll discuss structuring the code by business capabilities, which leads to a more expressive codebase organized into loosely coupled and cohesive modules. After that, we’ll explore this approach from a Domain Driven Design (DDD) perspective and discuss its flexibility.

2. Layered Architecture

Before exploring Vertical Slice Architecture, let’s first review the key features of its main counterpart, Layered Architecture. Layered designs are popular and widely used, with variations like Hexagonal, Onion, Ports and Adapters, and Clean Architecture.

A Layered Architecture uses a series of stacked or concentric layers that protect the domain logic against external components and factors. A key characteristic of these architectures is that all the dependencies point inwards, towards the domain:

2.1. Grouping Components by Technical Concerns

The layered approach solely focuses on grouping components by technical concerns, instead of their business capabilities.

For this article’s code examples, let’s assume we’re building the backend application of a blogging website. The application will support the following use cases:

  • Authors can publish and edit articles
  • Authors can see a dashboard with their article stats
  • Readers can read, like, and add comments to articles
  • Readers receive notifications with article recommendations

For example, our package names reflect technical layers without conveying the true purpose of the project:

2.2. High Coupling

Moreover, re-using the same domain services for unrelated business use cases can lead to tight coupling. For instance, the ArticleService currently depends on:

  • ArticleRepository – to query the database
  • UserService – to fetch data about the article authors
  • RecommendationService – to update reader recommendations when a new article is published
  • CommentService – to manage article comments

Consequently, we risk interfering with unrelated flows when we add or modify a use case. Furthermore, this highly coupled approach often leads to cluttered tests full of mocks.

2.3. Low Cohesion

Finally, this code structure tends to have low cohesion within components. Since the code for a business use case is scattered in various packages across the project, any minor change will require us to modify files from each layer.

Let’s add a slug field to the Article entity. If we want to allow the clients to use the new field to query the database, we’ll have to change many files throughout the layers:

Even a simple modification causes changes in almost every package of our application. The fact that the classes that change together don’t live together indicates a low cohesion.

3. The Vertical Slice Architecture

Vertical Slice Architecture was developed to tackle some issues associated with Layered Architecture by organizing the code by business capabilities. Following this approach, our components mirror business use cases and span over multiple layers.

As a result, instead of grouping all the controllers in a common package, they will be moved to packages associated with their respective slices:

Additionally, we can group related use cases into cohesive slices that align with the business domains. Let’s reorganize our example with the author, reader, and recommendation domains:

 

Dividing the project into vertical slices allows us to use the default, package-private access modifiers for most of the classes. This ensures that unexpected dependencies don’t cross the domain boundary.

Lastly, it enables someone unfamiliar with the codebase to understand what the application does by looking at the file structure. Robert C. Martin, the author of Clean Code, refers to this as “Screaming Architecture. He argues that a software project’s design should communicate its purpose clearly, much like architectural blueprints of a building reveal its function.

4. Coupling and Cohesion

As previously discussed, opting for the Vertical Slice Architecture over the Onion Architecture offers improved management of coupling and cohesion.

4.1. Looser Coupling Through Application Events

Instead of eliminating the coupling between slices, let’s focus on defining the right interfaces for communication across boundaries. Using application events is a powerful technique that allows us to maintain loose coupling while facilitating cross-boundary interactions.

In the Layered Architecture approach unrelated services depend on each other to complete a business feature. Specifically, ArticleService depends on RecommendationService to notify it about new articles. Instead, the recommendation flow can be executed asynchronously, and react to the main flow by listening to an application event.

Since we use the Spring Framework for our code example, let’s publish a Spring Event when a new article is created:

@Component
class CreateArticleUseCase {
    
    private final ApplicationEventPublisher eventPublisher;
    
    // constructor
    void createArticle(CreateArticleRequest article) {
        saveToDatabase(article);
        
        var event = new ArticleCreatedEvent(article.slug(), article.name(), article.category());
        eventPublisher.publishEvent(event);
    }
    private void saveToDatabase(CreateArticleRequest aticle) { /* ... */ }
        // ...
    }
}

Now, the SendArticleRecommendationUseCase can use an @EventListener to react to ArticleCreatedEvents and execute its logic:

@Component
class SendArticleRecommendationUseCase {
    @EventListener
    void onArticleRecommendation(ArticleCreatedEvent article) {
        findTopicFollowers(article.name(), article.category());
          .forEach(follower -> sendArticleViaEmail(article.slug(), article.name(), follower));
    }
    private void sendArticleViaEmail(String slug, String name, TopicFollower follower) {
        // ...
    }
    private List<TopicFollower> findTopicFollowers(String articleName, String topic) {
        // ...
    }
    record TopicFollower(Long userId, String email, String name) {}
}

As we can see, the modules operate independently and don’t directly depend on each other. Additionally, any components interested in newly created articles only need to listen for the ArticleCreatedEvent.

4.2. Higher Cohesion

Finding the right boundaries results in cohesive slices and use cases. A use case class should typically have a single public method and a single reason to change, adhering to the Single Responsibility Principle.

Let’s add a slug field to the Article class in our Vertical Slice Architecture and create an endpoint to find articles by slug. This time, the changes are scoped to a single package. We’ll create a SearchArticleUseCase that uses JdbcClient to query the database and return a projection of an Article. As a result, we’ll only modify two files in one package:

We created one use case and modified the ReaderController to expose the new endpoint. The fact that both files are located in the same package indicates higher cohesion within the project.

5. Design Flexibility

Vertical Slice Architecture allows us to customize approaches for each component, and determine the most effective way to organize the code for each use case. In other words, we can utilize various tools, patterns, or paradigms without enforcing a specific coding style or dependencies across the entire application.

Moreover, this flexibility facilitates approaches such as Domain-Driven Design (DDD) and CQRS. While not mandatory, they are well-suited for a vertically sliced application.

5.1. Modeling the Domain With DDD

Domain-Driven Design is an approach that emphasizes modeling software based on the core business domain and its logic. In DDD, the code must use terms and language familiar to business people and clients, to align technical and business perspectives.

In Vertical Slice Architecture, we might encounter the problem of code duplication between use cases. For slices that expand, we can decide to extract general business rules and use DDD to create a domain model, specific to them:

Moreover, DDD uses Bounded Contexts to define specific boundaries, ensuring clear distinctions between different parts of the system.

Let’s revisit a project that follows the layered approach. We’ll notice that we interact with the system’s users through objects like UserService, UserRepository, and the User entity. In contrast, in a vertically-sliced project, the concept of a user varies across different bounded contexts. Each slice has its own representation of a user, referring to them as a “reader”, an “author”, or a “topic follower”, reflecting the specific role they play within that context.

5.2. Bypassing the Domain for Simple Use Cases

Another drawback of strictly following a layered architecture is that it can result in methods that simply pass calls to the next layer, without adding value. This is also known as the “middle man” antipattern, and it leads to tightly coupled layers.

For example, when finding an article by slug, the controller calls the service, which then calls the repository. Even though the service doesn’t add any value in this case, the strict rules of layered architecture prevent us from bypassing the domain and directly accessing the persistence layer.

In contrast, a vertically sliced application offers the flexibility to choose which layers are needed for each specific use case. This allows us to bypass the domain layer for simple use cases, and directly query the database for projections:

Let’s simplify the use case of viewing an article by slug, using the Vertical Slice Architecture to bypass the domain layer:

@Component
class ViewArticleUseCase {
    private static final String FIND_BY_SLUG_SQL = """
        SELECT id, name, slug, content, authorid
        FROM articles
        WHERE slug = ?
        """;
    private final JdbcClient jdbcClient;
    // constructor
    public Optional<ViewArticleProjection> view(String slug) {
        return jdbcClient.sql(FIND_BY_SLUG_SQL)
          .param(slug)
          .query(this::mapArticleProjection)
          .optional();
    }
    record ViewArticleProjection(String name, String slug, String content, Long authorId) {
    }
    private ViewArticleProjection mapArticleProjection(ResultSet rs, int rowNum) throws SQLException {
        // ...
    }
}

As we can see, ViewArticleUseCase directly queries the database using a JdbcClient. Moreover, it defines its own projection of an article, instead of reusing a common DTO, which would’ve coupled this use case to other components. As a result, unrelated use cases aren’t forced into the same structure and we eliminate unnecessary dependencies.

6. Conclusion

In this article, we’ve learned about Vertical Slice Architecture and compared it to its Layered Architecture counterpart. We learned how to create cohesive components and avoid coupling between unrelated business use cases.

We discussed bounded contexts and how they help us define different projections specific to a part of the system. Finally, we discovered that this approach offers increased flexibility when designing each vertical slice.

As always, the complete source code can be found over on GitHub.

       

Store File or byte[] as SQL Blob in Java (Store and Load)

$
0
0
Contact Us Featured

1. Overview

Files such as images, audio, video, documents, etc. can be stored in a database as Binary Large Object (BLOB). A BLOB is a SQL data type that can store large binary data as a single entity.

In this tutorial, we’ll learn how to store and retrieve BLOB data using Java Database Connectivity (JDBC) with an H2 in-memory database.

2. Example Setup

To begin, let’s add the h2 dependency to the pom.xml:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
</dependency>

Next, let’s create a schema named warehouses:

String sql = """
    CREATE TABLE IF NOT EXISTS warehouses (
    id INTEGER PRIMARY KEY,
    name text NOT NULL,
    capacity REAL,
    picture BLOB
);""";

Here, we create a table named warehouses with four columns. The fourth column is of type BLOB, which is suitable for saving binary objects like pictures, PDF files, etc.

Also, let’s set up the JDBC connection:

static Connection connect() throws SQLException {
    Connection connection = DriverManager.getConnection("jdbc:h2:./test", "sa", "");
    return connection;
}

In this method, we create a Connection object to establish a connection with the database.

Finally, let’s initiate a create operation to create a table in the database:

try (Connection connection = connect(); Statement stmt = connection.createStatement()) {
    stmt.execute(sql);
}

In the code above, we execute our SQL query to create a table.

 3. Saving  a File as BLOB

We can store file content in the database by first converting it into a byte array and then inserting it or streaming the file content in chunks.

3.1. Converting File to a Byte Array

If we intend to store a small file, converting it to a byte array may be an efficient choice. However, this approach may not be suitable for huge files.

First, let’s write a method to convert a file to a byte array:

static byte[] convertFileToByteArray(String filePath) throws IOException {
    File file = new File(filePath);
    try (FileInputStream fileInputStream = new FileInputStream(file); 
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
        byte[] buffer = new byte[1024];
        for (int len; (len = fileInputStream.read(buffer)) != -1; ) {
            byteArrayOutputStream.write(buffer, 0, len);
        }
        return byteArrayOutputStream.toByteArray();
    }
}

The method above accepts the file path as an argument which is passed to the File object. Next, we create FileInputStream and ByteArrayOutputStream objects to read the file and store bytes read from the file respectively.

Also, we read the file in chunks of 1024 bytes at a time. If the fileInputStream returns -1, the end of the file is reached.

Next, let’s insert a new record into the warehouses table:

boolean insertFile(int id, String name, int capacity, String picture) throws SQLException, IOException {
    String insertSql = """
        INSERT INTO warehouses(id,name,capacity,picture) VALUES(?,?,?,?)
        """;
    try (Connection conn = connect()) {
        if (conn != null) {
            PreparedStatement stmt = conn.prepareStatement(insertSql);
            stmt.setInt(1, id);
            stmt.setString(2, name);
            stmt.setDouble(3, capacity);
            stmt.setBytes(4, convertFileToByteArray(picture));
            stmt.executeUpdate();
            return true;
        }
    }
    return false;
}

Here, we establish a connection to the database and create a PreparedStatement object to set values for each column. The file is converted to a byte array and set as a BLOB data using the setBytes() method.

Let’s see a unit test to demonstrate inserting a new record using the insertFile() method:

@ParameterizedTest
@CsvSource({ "1, 'Liu', 3000", "2, 'Walmart', 5000" })
void givenBlobFile_whenInsertingTheBlobFileAsByteArray_thenSuccessful(
    int id, 
    String name, 
    int capacity
) throws SQLException, IOException {
    boolean result = jdbcConnection.insertFile(id, name, capacity, TEST_FILE_PATH);
    assertTrue(result);
}

In the code above, we add two new records to the warehouses table. Finally, we assert that the operation returns true.

3.2. Saving the File as a Stream

When dealing with large files, converting the entire file to a byte array before saving it to the database may not be efficient. In such cases, we can save the file in chunks using a streaming approach.

Here’s an example that saves a file into the database in chunks:

boolean insertFileAsStream(
    int id, 
    String name, 
    int capacity, 
    String filePath
) throws SQLException, IOException {
    String insertSql = """
        INSERT INTO warehouses(id,name,capacity,picture) VALUES(?,?,?,?)
        """;
    try (Connection conn = connect()) {
        if (conn != null) {
            PreparedStatement stmt = conn.prepareStatement(insertSql);
            stmt.setInt(1, id);
            stmt.setString(2, name);
            stmt.setDouble(3, capacity);
            File file = new File(filePath);
            try (FileInputStream fis = new FileInputStream(file)) {
                stmt.setBinaryStream(4, fis, file.length());
                stmt.executeUpdate();
                return true;
            }
        }
    }
    return false;
}

In the code above, instead of converting the picture file to a byte array, we pass it to the FileInputStream object to stream the file contents directly into the database without loading the entire file into memory. This is more efficient for large files because it reduces OutOfMemoryErrors.

4. Retrieving a BLOB from a Database

Next, let’s look at how we can read binary data from the database as an input stream and write it directly to a file output stream.

Here’s a method that retrieves a record with BLOB data from the database:

static boolean writeBlobToFile(
    String query, 
    int paramIndex, 
    int id, 
    String filePath
) throws IOException, SQLException {
    try (
        Connection connection = connect(); 
        PreparedStatement statement = connection.prepareStatement(query)
    ) {
        statement.setInt(paramIndex, id);
        try (
            ResultSet resultSet = statement.executeQuery(); 
            FileOutputStream fileOutputStream = new FileOutputStream(new File(filePath))
        ) {
            while (resultSet.next()) {
                InputStream input = resultSet.getBinaryStream("picture");
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = input.read(buffer)) > 0) {
                    fileOutputStream.write(buffer, 0, bytesRead);
                }
            }
            return true;
        }
    }
}

In the code above, we retrieve a BLOB from the database and write it to a file. Also, we create a PreparedStatement object to execute a query, retrieve the BLOB as a binary stream from the ResultSet, and read each byte in chunks of 1024 bytes.

Then, we ensure the read operation returns the number of bytes read, which may be less than the buffer size.

5. Conclusion

In this article, we learned how to write binary files into the database as a byte array. We also saw how to write a large file using a stream. Furthermore, we learned how to retrieve a blob file from the database and write it to a file.

As always, the complete source code for the examples is available over on GitHub.

       

Introduction to TigerBeetle Transactions Database

$
0
0
start here featured

1. Introduction

In this tutorial, we’ll explore the TigerBeetle database engine and learn how we can use it to build a fault-tolerant and high-performance application.

2. Financial Transactions in a Nutshell

Every time we use a debit or credit to buy something online or in a store, there will be a transaction where some currency amount will be transferred from your account to the merchant’s.

Behind the scenes, fees must be deducted from the value transferred and then split among all parties involved (acquirer, card processing company, banks, etc). All those entities must also keep detailed transaction logs, also known as ledgers, after the books where accountants used to keep them.

Nowadays, most financial transaction systems rely on database engines, such as Oracle, SQL Server, and DB2, to store transactions. A typical system will have an accounts table that holds balances and a transactions table that logs every debt or credit made to the accounts.

While this works well, the general purpose of these databases leads to several inefficiencies and, consequently, requires much more resources to deploy and operate at large scales.

3. TigerBeetle’s Approach

A newcomer in the crowded database market, TigerBeetle is a specialized database engine that focuses primarily on financial transactions. By doing so, it can get rid of most of the complexity associated with a general-purpose database engine and, in exchange, claims to be able to deliver up to 1000x throughput improvement.

These are the main simplifications that make this improvement possible:

  • Fixed schema
  • Fixed-point arithmetic
  • Batched transactions
  • No general query capabilities

Perhaps the most surprising of these is the fixed schema. TigerBeetle has just two entities: Accounts and Transfers.

An Account stores the balance of some asset (current, stocks, bitcoins, etc.), which can be anything that we can acquire or transfer to/from another Account belonging to the same ledger. An Account also has a few fields that are intended to hold external identifiers, allowing us to link it to traditional system-of-records databases.

To add some credit to an Account we create a Transfer instance that contains the amount, the source Account from which we’ll deduce this amount, and the destination Account.

Those are some important features of Accounts and Transfers:

  •  Once created, an Account cannot be deleted
  •  The Account‘s initial balance is always zero
  •  Transfers are immutable. Once committed they cannot be modified or deleted
  •  Transfers require both Accounts to be on the same ledger
  • At all times, the sum of debits and credits over all Accounts is zero

4. Deploying TigerBeetle

TigerBeetle is distributed as a statically linked executable, available at the official site.

Before using TigerBeetle, we need to create a data file where it will store its data. This is done using the format command:

$ tigerbeetle format --cluster=0 --replica=0 --replica-count=1 0_0.tigerbeetle

We can now start a standalone instance using the start command:

$ tigerbeetle start --addresses=3000 0_0.tigerbeetle

5. Using TigerBeetle from Java Applications

This is the TigerBeetle’s official client Maven dependency:

<dependency>
    <groupId>com.tigerbeetle</groupId>
    <artifactId>tigerbeetle-java</artifactId>
    <version>0.15.3</version>
</dependency>

The latest version of this library is available from Maven Central.

Important notice: This dependency contains platform-specific native code. Make sure to run your client only on supported architectures

5.1. Connecting to TigerBeetle

The entry point to access TigerBeetle’s functionalities is the Client class. Client instances are thread-safe, so we just need to create a single instance of it in our applications. For Spring-based applications, the simplest approach is to define a @Bean in a @Configuration class, so we can inject it when needed:

@Configuration
public class TigerBeetleConfig {
    @Value("${tigerbeetle.clusterID:0}")
    private BigInteger clusterID;
    @Value("${tb_address:3000}")
    private String[] replicaAddress;
    @Bean
    Client tigerBeetleClient() {
        return new Client(UInt128.asBytes(clusterID), replicaAddress);
    }
}

5.2. Creating Accounts

TigerBeetle’s API doesn’t come with any domain object, so let’s create a simple Account record to store the data we need to create one:

@Builder
public record Account(
    UUID id,
    BigInteger accountHolderId,
    int code,
    int ledger,
    int userData32,
    long userData64,
    BigInteger creditsPosted,
    BigInteger creditsPending,
    BigInteger debtsPosted,
    BigInteger debtsPending,
    int flags,
    long timestamp) {
}

Here, we’ve used a UUID account identifier as a convenience. Internally, TigerBeetle uses 128-bit integers as account identifiers, which the Java API maps to 16-byte arrays. Our domain class has an accountHolderId, which maps to the userData128 field.

The API uses 128-bit integers in many places, but since Java has no equivalent native datatype, it provides the UInt128 utility class that helps to convert from arrays to other formats. Besides UUIDs, we can also use BigIntegers or a pair of regular long integers.

The userData128, userData32, and userData64 fields’ main use is to store secondary identifiers associated with this account. For example, we can use them to store the identifier of this account in an external database.

Now, let’s create an AccountRepository and implement a createAccount() method:

@RequiredArgsConstructor
public class AccountRepository {
    private final Client client;
    public Account createAccount(BigInteger accountHolderId, int code, int ledger,
      int userData32, long userData64, int flags ) {
        AccountBatch batch = new AccountBatch(1);
        byte[] id = UInt128.id(); 
        batch.add();
        batch.setId(id);
        batch.setUserData128(UInt128.asBytes(accountHolderId));
        batch.setLedger(ledger);
        batch.setCode(code);
        batch.setFlags(AccountFlags.HISTORY | flags);
        CreateAccountResultBatch result = client.createAccounts(batch);
        if(result.getLength() > 0) {
            result.next();
            throw new AccountException(result.getResult());
        }
        return findAccountById(UInt128.asUUID(id)).orElseThrow();
    }
   // ... other repository methods omitted
}

The implementation starts by creating an AccountBatch object to hold the batch data. In this example, the batch consists of a single account creation command, but we could easily extend this model to accept multiple requests.

Notice the AccountFlags.HISTORY flag. When set, we’ll be able to query historical balances, as we’ll see later. Also important is the use of UInt128.id() to generate the Account identifier. Values returned from this method are unique and time-based, meaning that if we compare them, we can determine which one was created first.

Once we’ve populated the batch request, we send it to TigerBeetle using the createAccounts() method. This method returns a CreateAccountResultBatch, which will be empty in case of a successful request. Otherwise, there will be an entry for each failed creation request containing the reason for the failure.

As a convenience to the caller, the method returns the Account domain object populated from data recovered from the database, which includes the actual creation timestamp as set by TigerBeetle.

5.3. Looking Up Accounts

To implement findAccountById() we follow a similar pattern as in the previous case. Firstly, we create a batch to hold the identifiers we want to find. To make things simpler, we’ll limit ourselves to just a single account per call.

Next, we submit this batch to TigerBeetle and process the results:

public Optional<Account> findAccountById(UUID id) throws ConcurrencyExceededException {
    IdBatch idBatch = new IdBatch(UInt128.asBytes(id));
    var batch = client.lookupAccounts(idBatch);
    if (!batch.next()) {
        return Optional.empty();
    }
    return Optional.of(mapFromCurrentAccountBatch(batch));
 }

Notice the use of next() to determine whether the given identifier exists or not. This works because of the single account limitation mentioned above.

A variant of this method that supports multiple identifiers is available online. There, we populate the resulting Map using the returned values, leaving null entries for any identifier not found.

5.4. Creating Simple Transfers

Let’s start with the simplest case: a Transfer between two accounts belonging to the same ledger. Besides the source and destination accounts and ledger, we’ll also allow users of our repository to add some metadata: code, userData128, userData64, and userData32. Although optional, these metadata fields are useful to link this Transfer to external systems.

public UUID createSimpleTransfer(UUID sourceAccount, UUID targetAccount, BigInteger amount,
  int ledger, int code, UUID userData128, long userData64, int userData32)  {
    var id = UInt128.id();
    var batch = new TransferBatch(1);
    batch.add();
    batch.setId(id);
    batch.setAmount(amount);
    batch.setCode(code);
    batch.setCreditAccountId(UInt128.asBytes(targetAccount));
    batch.setDebitAccountId(UInt128.asBytes(sourceAccount));
    batch.setUserData32(userData32);
    batch.setUserData64(userData64);
    batch.setUserData128(UInt128.asBytes(userData128));
    batch.setLedger(ledger);
    var batchResults = client.createTransfers(batch);
    if (batchResults.getLength() > 0) {
        batchResults.next();
        throw new TransferException(batchResults.getResult());
    }
    return UInt128.asUUID(id);
}

If the operation succeeds, the amount will be added to the source Account‘s debitsPosted field and the destination Account‘s creditsPosted.

5.5. Balance Queries

When an Account is created with the HISTORY flag set, we can query its balances as they change as a result of a transfer. The API expects an AccountFilter filled with the Account identifier and a time range. The filter also supports parameters to limit the amount and order of returned entries.

This is how we’ve used the getAccountBalances() method to implement the listAccountBalances() repository’s method:

List<Balance> listAccountBalances(UUID accountId, Instant start, Instant end, int limit, boolean lastFirst) {
    var filter = new AccountFilter();
    filter.setAccountId(UInt128.asBytes(accountId));
    filter.setCredits(true);
    filter.setDebits(true);
    filter.setLimit(limit);
    filter.setReversed(lastFirst);
    filter.setTimestampMin(start.toEpochMilli());
    filter.setTimestampMax(end.toEpochMilli());
    var batch = client.getAccountBalances(filter);
    var result = new ArrayList<Balance>();
    while(batch.next()) {
        result.add(
          Balance.builder()
            .accountId(accountId)
            .debitsPending(batch.getDebitsPending())
            .debitsPosted(batch.getDebitsPosted())
            .creditsPending(batch.getCreditsPending())
            .creditsPosted(batch.getCreditsPosted())
            .timestamp(Instant.ofEpochMilli(batch.getTimestamp()))
            .build()
        );
    }
    return result;
 }

Notice that the API’s result doesn’t include information about the associated transactions, thus limiting its use in practice. However, as mentioned in the official documentation, this API is likely to change in future versions.

5.6. Transfer Queries

Currently, the getAccountTransfers() is the most useful of the available query APIs – not a big achievement given there are only two ;^). It works similarly to getAccountBalances(), including the use of AccountFilter to specify the query criteria:

public List<Transfer> listAccountTransfers(UUID accountId, Instant start, Instant end, int limit, boolean lastFirst) {
    var filter = new AccountFilter();
    filter.setAccountId(UInt128.asBytes(accountId));
    filter.setCredits(true);
    filter.setDebits(true);
    filter.setReversed(lastFirst);
    filter.setTimestampMin(start.toEpochMilli());
    filter.setTimestampMax(end.toEpochMilli());
    filter.setLimit(limit);
    var batch = client.getAccountTransfers(filter);
    var result = new ArrayList<Transfer>();
    while(batch.next()) {
        result.add(Transfer.builder()
          .id(UInt128.asUUID(batch.getId()))
          .code(batch.getCode())
          .amount(batch.getAmount())
          .flags(batch.getFlags())
          .ledger(batch.getLedger())
          .creditAccountId(UInt128.asUUID(batch.getCreditAccountId()))
          .debitAccountId(UInt128.asUUID(batch.getDebitAccountId()))
          .userData128(UInt128.asUUID(batch.getUserData128()))
          .userData64(batch.getUserData64())
          .userData32(batch.getUserData32())
          .timestamp(Instant.ofEpochMilli(batch.getTimestamp()))
          .pendingId(UInt128.asUUID(batch.getPendingId()))
          .build());
    }
    return result;
}

5.7. Two-Phase Transfers

TigerBeetle makes a clear distinction between pending and posted transfers. This distinction is made evident by the fact that an Account has four balance fields: two for posted and two for pending values.

In our earlier Transfer example, we didn’t inform its type. In this case, the API defaults to a posted Transfer, meaning the amount will be added directly to the debits_posted or credits_posted field.

To create a pending Transfer, we have to set the PENDING flag:

public UUID createPendingTransfer(UUID sourceAccount, UUID targetAccount, BigInteger amount,
  int ledger, int code, UUID userData128, long userData64, int userData32) throws ConcurrencyExceededException {
    var id = UInt128.id();
    var batch = new TransferBatch(1);
    // ... fill batch data (same as regular Transfer)
    batch.setFlags(TransferFlags.PENDING);
    // ... send transfer and handle results (same as regular Transfer) 
}

A pending Transfer should always be confirmed (POST_PENDING) or canceled (VOID_PENDING) by a later Transfer request. In both cases, we must include the original Transfer identifier in the pendingId field:

public UUID completePendingTransfer(UUID pendingId, boolean success) throws ConcurrencyExceededException {
    var id = UInt128.id();
    var batch = new TransferBatch(1);
    batch.add();
    batch.setId(id)
    batch.setPendingId(UInt128.asBytes(pendingId));
    batch.setFlags(success? TransferFlags.POST_PENDING_TRANSFER : TransferFlags.VOID_PENDING_TRANSFER);
    var batchResults = client.createTransfers(batch);
    if (batchResults.getLength() > 0) {
        batchResults.next();
        throw new TransferException(batchResults.getResult());
    }
    return UInt128.asUUID(id);
}

A typical scenario where this feature can be used is an authorization server that handles requests from an ATM. Firstly, the client informs his account and the requested amount to withdraw. The authorization server then creates a PENDING transaction and returns the generated Transfer identifier.

Next, the ATM proceeds to dispense the money. There are two possible outcomes: if everything goes right, the ATM sends another message to the authorization server and confirms the Transfer.

However, if something goes wrong (e.g. there are no bills available or a stuck dispenser), the ATM cancels the Transfer.

5.8. Two-Phase Transfer Timeouts

To account for a communication failure happening between the initial authorization request and its confirmation or cancellation, we can pass an optional timeout in the first step:

public UUID createExpirablePendingTransfer(UUID sourceAccount, UUID targetAccount, BigInteger amount,
  int ledger, int code, UUID userData128, long userData64, int userData32, int timeout) throws ConcurrencyExceededException {
    var id = UInt128.id();
    var batch = new TransferBatch(1);
    // ... prepare batch (same as regular pending Transfer)
    batch.setTimeout(timeout);
    // ... send batch and handle results (same as regular pending Transfer)
}

If the timeout expires before receiving a request to confirm or cancel it, TigerBeetle will automatically the pending transaction.

5.9. Linked Operations

Often, it’s important to ensure that a group of operations sent to TigerBeetle must either complete or fail as a whole. We can think of it as an analog to regular database transactions, where we can issue multiple inserts and commit them all at once at the end.

To support this scenario TigerBeetle has the concept of linked events. In a nutshell, to create a group of Account or Transfer records as a single transaction, all items except for the last must have the linked flag set:

public List<Map.Entry<UUID,CreateTransferResult>> createLinkedTransfers(List<Transfer> transfers) 
  throws ConcurrencyExceededException {
    var results = new ArrayList<Map.Entry<UUID,CreateTransferResult>>(transfers.size());
    var batch = new TransferBatch(transfers.size());
    for ( Transfer t : transfers) {
        byte[] id = UInt128.id();
        batch.add();
        batch.setId(id);
        // Is this the last transfer to add ?
        if ( batch.getPosition() != transfers.size() -1 ) {
            batch.setFlags(TransferFlags.LINKED);
        }
        batch.setLedger(t.ledger());
        batch.setAmount(t.amount());
        batch.setDebitAccountId(UInt128.asBytes(t.debitAccountId()));
        batch.setCreditAccountId(UInt128.asBytes(t.creditAccountId()));
        if ( t.userData128() != null) {
            batch.setUserData128(UInt128.asBytes(t.userData128()));
        }
        batch.setCode(t.code());
        results.add(new AbstractMap.SimpleImmutableEntry<>(UInt128.asUUID(id), CreateTransferResult.Ok));
    }
    var batchResult = client.createTransfers(batch);
    while(batchResult.next()) {
        var original = results.get(batchResult.getIndex());
        results.set(batchResult.getIndex(), new AbstractMap.SimpleImmutableEntry<>(original.getKey(), batchResult.getResult()));
    }
    return results;
}

TigerBeetle ensures that linked operations will be executed in order and either committed or rolled back fully. Also important is the fact that the side effects of one operation will be visible to the next in the chain.

For instance, consider an Account created with the DEBITS_MUST_NOT_EXCEED_CREDITS flag. If we create two linked transfer commands such that the second one results in an overdraft, both transfers will be rejected:

@Test
void whenSimpleTransfer_thenSuccess() throws Exception {
    var MY_LEDGER = 1000;
    var CHECKING_ACCOUNT = 1000;
    var P2P_TRANSFER = 500;
    var liabilitiesAcc = repo.createAccount(
      BigInteger.valueOf(1000L),
      CHECKING_ACCOUNT,
      MY_LEDGER, 0,0, 0);
    var sourceAcc = repo.createAccount(
      BigInteger.valueOf(1001L),
      CHECKING_ACCOUNT,
      MY_LEDGER, 0,0, AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS);
    var targetAcc = repo.createAccount(
      BigInteger.valueOf(1002L),
      CHECKING_ACCOUNT,
      MY_LEDGER, 0, 0, 0);
    List<Transfer> transfers = List.of(
      Transfer.builder()
        .debitAccountId(liabilitiesAcc.id())
        .ledger(MY_LEDGER)
        .code(P2P_TRANSFER)
        .creditAccountId(sourceAcc.id())
        .amount(BigInteger.valueOf(1_000L))
        .build(),
      Transfer.builder()
        .debitAccountId(sourceAcc.id())
        .ledger(MY_LEDGER)
        .code(P2P_TRANSFER)
        .creditAccountId(targetAcc.id())
        .amount(BigInteger.valueOf(2_000L))
        .build()
      );
    var results = repo.createLinkedTransfers(transfers);
    assertEquals(2, results.size());
    assertEquals(CreateTransferResult.LinkedEventFailed, results.get(0).getValue());
    assertEquals(CreateTransferResult.ExceedsCredits, results.get(1).getValue());
}

In this case, we see that the first Transfer, which would succeed in a non-linked scenario, fails because the second one would result in an overdraft.

6. Conclusion

In this article, we’ve explored the TigerBeetle database and its features. Despite its limited query capabilities, it has great performance and runtime guarantees, making it a good candidate for every application where its double-entry ledger model is applicable.

As always, all code is available over on GitHub.

       

Define Multiple Repositories With Maven

$
0
0

1. Overview

Apache Maven lets us define a repository or a central place where we store and retrieve project artifacts. Sometimes, we need to define multiple repositories, especially when our project depends on libraries available only in specific repositories.

In this tutorial, we’ll explore two primary ways to do this in Maven: through the settings.xml file and the pom.xml file.

2. Uploading Our Own Third-Party JAR to a Private Repository

To demonstrate these approaches, we’ll first deploy our simple GreeterServiceExample to a private remote repository:

public class GreeterServiceExample {
    public Greeting greetInYourLanguage(String language) {
        return switch (language.toLowerCase()) {
            case "english" -> new Greeting("Hello", new Language("English", "en"));
            case "spanish" -> new Greeting("Hola", new Language("Spanish", "es"));
            case "xhosa" -> new Greeting("Molo", new Language("Xhosa", "xh"));
            default -> null;
        };
    }
}

The example service returns a language-specific Greeting based on the selected language.

Before we can deploy our artifact, we need to specify our repository details in either the POM or settings.xml file:

<repositories> 
    <repository>
        <id>internal-maven-repo</id>
        <name>Internal Repository</name>
        <url>https://host/internal-maven-packages</url> 
    </repository>
    <!-- Other repositories -->
</repositories>

Next, let’s update our settings.xml by adding a server directive specifying authentication information for our internal repository:

<server>
    <id>internal-maven-repo</id>
    <username>username</username>
    <password>passphrase_or_token</password>
</server>

In addition to the authentication credentials, we’ve also specified a unique ID that our repository configuration references.

Now, let’s publish our example library to our internal repository:

mvn deploy:deploy-file \
    -DgroupId=com.baeldung \
    -DartifactId=maven-multiple-repositories \
    -Dversion=0.0.1 \
    -Dpackaging=jar \
    -Dfile=./target/GreeterExampleService.jar \
    -DrepositoryId=internal-maven-repo \
    -Durl=<url-of-the-repository-to-deploy>

In our deploy:deploy-file goal, we specify the repositoryId defined in our settings.xml file, as well as the file to be deployed and the repository URL, which are required by the deploy:deploy-file goal.

If we need the artifact to be present in other repositories, we’d need to repeat the Maven deploy process for each repository.

Now that our custom library is deployed, let’s explore how this dependency can be used in other projects.

3. Set up the Repository With the settings.xml File

Maven profiles help us customize our build configuration. Let’s add a profile definition to our settings.xml file:

<profile>
    <id>local-dev</id>
    <repositories>
        <repository>
            <id>internal-maven-repo</id>
            <name>Internal Repository</name>
            <url>https://host/internal-maven-packages</url>
        </repository>
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
        </repository>
    </repositories>
</profile>

In our internal repository definition, we’ve ensured that the repository ID matches the one specified in the server directive. Although the central repository configuration is inherited from the Super POM, we’ve included it here for demonstration purposes.

Next, let’s add the newly defined profile to the list of active profiles in our settings.xml file:

<activeProfiles>
    <activeProfile>local-dev</activeProfile>
</activeProfiles>

Including the profile in this list activates it for all builds.

Let’s specify a dependency for the library we uploaded:

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>maven-multiple-repositories</artifactId>
    <version>0.0.1</version>
</dependency>

Next, let’s run a Maven install goal so that Maven will download our dependencies:

mvn install

Our build succeeds because all dependencies were sourced correctly from their respective repositories. Let’s utilize the imported library in our unit test:

@Test
public void whenGreetingInEnglish_thenAnENCodeShouldBeMadeAvailable() {
    GreeterServiceExample greeterService = new GreeterServiceExample();
    Greeting englishGreeting = greeterService.greetInYourLanguage("English");
    assertEquals("en", englishGreeting.getLanguage().getCode());
}

Configuring repository information in our settings.xml is suitable for local development; however, it can become cumbersome with multiple build environments. Let’s explore another option.

4. Configuring the Repository via pom.xml File

The POM file allows us to specify which repositories to use and to choose whether to define our repository details inside or outside of build profiles. It’s not necessary to define our repositories inside a Maven build profile.

Let’s define our repositories outside of the build profile in our POM file:

<repositories>
    <repository>
        <id>internal-maven-repo</id>
        <name>Internal Repository</name>
        <url>https://host/internal-maven-packages</url>
    </repository>
    <!-- Other repositories -->
</repositories>

Since our internal repository requires authentication, it’s best to leave those credentials in the server directive of our settings.xml file to avoid distributing them with the source code. Once again, we ensure that the repository ID matches the one specified in the server directive of our settings.xml file.

Let’s run a build:

mvn clean install

Since we defined our repositories outside of a build profile, we didn’t include it in the Maven install goal.

5. Conclusion

In this tutorial, we explored two approaches for specifying multiple repositories in Maven: using the settings.xml file and using the pom.xml file. Although both approaches lead to the same outcome, we saw how configuring the repository details in the pom.xml file lends itself better to portability. However, it is preferred that repository authentication details not be included in the pom.xml file due to security considerations, such as distributing sensitive information.

As always, the source code for all the examples can be found over on GitHub.

       

Check if a Variable Is Null Using Hamcrest

$
0
0
Contact Us Featured

1. Overview

When we write unit tests in Java, particularly with the JUnit framework, we often need to verify that certain variables are null or not null. Hamcrest, a popular library of Matchers for creating flexible tests, provides a convenient way to achieve this.

In this quick tutorial, we’ll explore how to check if a variable is null or non-null using JUnit and Hamcrest’s Matchers.

2. Using Hamcrest’s assertThat()

To use Hamcrest, we need to add the dependency into the pom.xml:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>

We can check the latest version in Maven Central.

Hamcrest’s assertThat() method and its Matchers allow us to write flexible assertions. Next, let’s have a look at how to assert a variable is or isn’t null using this approach.

Hamcrest offers null-related matcher methods in the org.hamcrest.core.IsNull class. For example, we can use the static methods nullValue() and notNullValue() to get the Matchers for null and non-null checks. 

Also, Hamcrest groups commonly used Matchers in the org.hamcrest.Matchers class. In the Matchers class, nullValue() and notNullValue() methods are available as well. They simply call the corresponding methods in the IsNull class. Therefore, we can import static the methods from either class to use these methods and make the code easy to read:

import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

We can use them like so:

String theNull = null;
assertThat(theNull, nullValue());
 
String theNotNull = "I am a good String";
assertThat(theNotNull, notNullValue());

For a non-null check, we can also combine Matchers and use not(nullValue()) as an alternative:

assertThat(theNotNull, not(nullValue()));

It’s worth noting that the not() method is from the org.hamcrest.core.IsNot class and it negates the match parameter’s logic.

3. Using JUnit’s null and Non-Null Assertions

As we’ve seen, Hamcrest’s matchers are convenient for performing null or non-null assertions. Alternatively, the assertNull() and assertNotNull() methods shipped with JUnit are straightforward to do these checks:

String theNull = null;
assertNull(theNull); 
 
String theNotNull = "I am a good String";
assertNotNull(theNotNull);

As the code shows, even without Matchers, the JUnit assertion methods remain easy to use and read when checking for nulls.

4. Conclusion

In this quick article, we’ve explored different ways to assert null and non-null variables effectively using JUnit and Hamcrest.

By using Hamcrest’s nullValue() and notNullValue() Matchers, we can easily check if variables are null or non-null in our unit tests. The library’s expressive syntax makes our tests more readable and maintainable.

Alternatively, JUnit’s standard assertNull() and assertNotNull() assertions are straightforward in doing this job.

As always, the complete source code for the examples is available over on GitHub.

       

Difference Between hasItems(), contains(), and containsInAnyOrder() in Hamcrest

$
0
0
start here featured

1. Introduction

Hamcrest is a popular framework for writing matcher objects in Java, providing an expressive way to define match conditions. Matchers like hasItems(), contains(), and containsInAnyOrder() allow us to assert the presence and order of elements within a collection. In this tutorial, we’ll delve into what each of these matchers does, how they differ, and when to use them.

2. Element Ordering

In this section, we’ll explore how hasItems(), contains(), and containsInAnyOrder() treat the order of elements within a collection.

2.1. hasItems()

The hasItems() matcher is designed to check if a collection contains specific elements, without caring about their order:

List<String> myList = Arrays.asList("apple", "banana", "cherry");
assertThat(myList, hasItems("apple", "cherry", "banana"));
assertThat(myList, hasItems("banana", "apple", "cherry"));

2.2. containsInAnyOrder()

Similarly to hasItems(), containsInAnyOrder()  doesn’t consider element order. It only cares that the specified items are present in the collection, regardless of their sequence:

assertThat(myList, containsInAnyOrder("apple", "cherry", "banana"));
assertThat(myList, containsInAnyOrder("banana", "apple", "cherry"));

2.3. contains()

In contrast, the contains() matcher is order-sensitive. It verifies that the collection contains the exact specified items in the same order they’re provided:

assertThat(myList, contains("apple", "banana", "cherry"));
assertThat(myList, not(contains("banana", "apple", "cherry")));

The first assertion passes because the elements’ order matches exactly. In the second assertion, we use the not() matcher, which expects that myList doesn’t contain elements in the exact order.

3. Exact Element Count

In this section, we’ll discuss how hasItems(), contains(), and containsInAnyOrder() handle the exact number of elements in a collection.

3.1. hasItems()

The hasItems() matcher doesn’t require the collection to have an exact count of elements. It focuses on ensuring the presence of specific items without imposing strict requirements on order or collection size:

assertThat(myList, hasItems("apple", "banana"));
assertThat(myList, hasItems("apple", "banana", "cherry"));
assertThat(myList, not(hasItems("apple", "banana", "cherry", "date")));

In the first assertion, we verify that myList contains both “apple” and “banana“. Since both items are present, the assertion passes. Similarly, the second assertion checks for the presence of “apple“, “banana“, and “cherry“, which are all present in the list, so it also passes.

However, the third assertion includes an extra element “date” which isn’t in myList. As a result, the not() matcher return succeeds. This illustrates that while hasItems() is flexible in verifying the presence of elements, it flags an assertion if extra items beyond those specified are found in the collection.

3.2. containsInAnyOrder()

While hasItems() allows for at least some of the specified items, the containsInAnyOrder() matcher requires the collection to contain all the items on our list. Regardless of the ordering, as long as all the specified items are present, the assertion passes:

assertThat(myList, containsInAnyOrder("apple", "banana", "cherry"));
assertThat(myList, not(containsInAnyOrder("apple", "banana")));

In the first assertion, we verify that myList contains “apple“, “banana“, and “cherry” in any order. Since all specified items are present in myList, the assertion passes.

The second assertion checks for “apple” and “banana” but omits “cherry“. Here, we expect not() to succeed because myList must exactly match the specified elements, including “cherry“, which is missing in this test data.

3.3. contains()

Similar to containsInAnyOrder(), the contains() matcher also requires the collection to have an exact count of elements but in the specified order:

assertThat(myList, contains("apple", "banana", "cherry"));
assertThat(myList, not(contains("apple", "banana")));

Similarly to containsInAnyOrder(), the second assertion expects not() matcher to succeed because the “cherry” element is missing in the test data.

4. Handling Duplicates

When dealing with collections that may contain duplicate elements, it’s essential to understand how hasItems(), contains(), and containsInAnyOrder() behave.

4.1. hasItems()

The hasItems() matcher doesn’t concern itself with the presence of duplicate elements. It simply checks for the presence of specified items, regardless of whether they’re duplicated:

List<String> myListWithDuplicate = Arrays.asList("apple", "banana", "cherry", "apple");
assertThat(myListWithDuplicate, hasItems("apple", "banana", "cherry"));

In this assertion, we verify that myListWithDuplicate contains “apple“, “banana“, and “cherry“. The hasItems() matcher checks for the presence of the specified items and ignores any duplicates. Since all specified items are present in the list, the assertion passes.

4.2. containsInAnyOrder()

Similarly, containsInAnyOrder() doesn’t enforce the ordering of elements. However, it ensures that all specified items are present, irrespective of duplicates:

assertThat(myList, containsInAnyOrder("apple", "banana", "cherry", "apple"));
assertThat(myListWithDuplicate, not(containsInAnyOrder("apple", "banana", "cherry")));

In this case, the “apple” duplicated element is missing from the second assertion test data, hence the not() matcher return succeeds.

4.3. contains()

On the other hand, the contains() matcher requires exact matching including the order and exact count of elements. If duplicates are present but not expected, the assertion fails:

assertThat(myList, not(contains("apple", "banana", "cherry")));

In this assertion, the “apple” duplicated element is missing from the test data, therefore the not() matcher return succeeds.

5. Summary

Let’s summarize the key differences among these matchers:

Matcher Order Exact Count Handling Duplicates Purpose
hasItems() No No Ignores duplicates Checks for the presence of specified elements, order does not matter
containsInAnyOrder() No Yes Requires exact elements Checks for exact elements, order does not matter
contains() Yes Yes Requires exact sequence of elements Checks for the exact sequence and exact elements

6. Conclusion

In this article, we’ve explored the difference between hasItems(), contains() and containsInAnyOrder(). We use hasItems() when we only care that the collection contains at least the specified items regardless of their order. If the collection must contain all the specified items, but the order doesn’t matter, we can use containsInAnyOrder(). However, if the collection must contain the exact specified items in the same order they are provided, we should use contains().

As always, the source code for the examples is available over on GitHub.

       

Arithmetic Operations on Arbitrary-Length Binary Integers in Java

$
0
0

1. Overview

In digital signal processing, operations are often performed on the binary representations of signals. In computer graphics, bit manipulation is critical for color, pixel operations, and transformations. Cryptography makes extensive use of bit-level operations to perform encryption and hashing functions efficiently and securely. In all of these cases, working with binary numbers of arbitrary length is critical.

In this tutorial, we’ll look at the methods for performing arbitrary-precision arithmetic operations on binary numbers in Java, exploring both the use of the BigInteger class and alternative approaches for environments where BigInteger isn’t available or desired.

We’ll also look at the limitations of binary literals. This is important to clarify both why we can’t use Java’s primitive types and how negative binary numbers are represented.

2. Number of Bits of Binary Literals

We can use a binary literal to assign a binary integer written in two’s complement notation to a 32-bit Integer or to a 64-bit Long. It’s worth noting that Java APIs such as Integer.toBinaryString() and Long.toBinaryString() also use two’s complement notation.

Two’s complement notation means that positive numbers are written as they are in base 2, while negative numbers are written by adding 232 if they are Integer or 264 if they are Long. This is exactly the same as taking the unsigned negative number written in 32bit or 64bit, inverting all the bits, and adding 1.

Let’s try a simple test:

@Test
void givenTwoBinaryLiterals_whenAdding_thenResultIsCorrect() {
    int a = 0b110100101101010;
    int b = 0b1000;
    int expected = 0b110100101110010; // Result of a + b in binary
    assertEquals(expected, a + b, "The addition of a and b should be correct");
}

Writing negative numbers is less intuitive because of the two’s complement notation. Let’s take the case of -8:

@Test
void whenComparingBinaryToDecimal_thenValuesAreEqual() {
    int c = 0b11111111111111111111111111111000; // (2^32 - 8) written in base 2
    int expected = -8;
    assertEquals(expected, c, "The binary value does not equal the decimal value of -8");
}

The main problem with binary literals is the maximum number of bits. Here is a screenshot from Eclipse:

java binary literal out of range

So binary literals aren’t an option if we need to perform arbitrary-precision arithmetic operations. However, when we implement subtraction between binary numbers of arbitrary length, the two’s complement representation of negative numbers that we’ve just seen will come in handy again.

3. Arbitrary-Length Binary Integers

Binary integers of arbitrary length can be represented by String objects composed of 0s and 1s, or by BigInteger objects.

In general, BigInteger is the simplest, most complete, and least error-prone solution. When using BigInteger, we don’t have to use two’s complement notation. Instead, we can write numbers by specifying the sign (+ or ) followed by their absolute value. This is the usual way we represent numbers in everyday base-10 notation. If a number is positive, we can omit the + sign:

@Test
void givenTwoBigIntegers_whenAdding_thenResultIsCorrect() {
    BigInteger a = new BigInteger("1101001011010101010101010101010101010101010101010101010101010101010", 2);
    BigInteger b = new BigInteger("-10000", 2);
    BigInteger expected = new BigInteger("1101001011010101010101010101010101010101010101010101010101010011010", 2);
    assertEquals(expected, BigIntegerExample.add(a, b), "The addition of a and b should be correct");
}

However, there are special cases where BigInteger isn’t available. For example, Java ME CLDC 8 is used in machine-to-machine (M2M) and Internet of Things (IoT) devices, but it doesn’t include BigInteger. In these cases, implementing arbitrary-precision binary arithmetic from scratch, as we’ve done in the BinaryStringOperations class, helps us address specific scenarios and also has educational value. It allows us to deepen our understanding of low-level operations and the basics of binary arithmetic, which are often abstracted away by higher-level libraries.

Since the BinaryStringOperations class is mostly self-explanatory, we’ll only discuss the most relevant parts of the code.

3.1. Sign Representation and Testing

In our JUnit tests, the number a is 67 bits long to exceed the maximum length of 64 bits allowed by the long primitive type. The sign of the numbers in BinaryStringOperations is represented similarly to BigInteger:

@Test
void givenTwoBinaryStrings_whenAdding_thenResultIsCorrect() {
    String a = "1101001011010101010101010101010101010101010101010101010101010101010";
    String b = "-10000";
    String expected = "1101001011010101010101010101010101010101010101010101010101010011010"; // Expected result of a + b
    assertEquals(expected, BinaryStringOperations.add(a, b), "The addition of a and b should be correct");
}

As for the expected results of our tests, we calculated them with bc to make sure we didn’t make any mistakes:

$ bc -l
[...]
ibase=2
obase=2
1101001011010101010101010101010101010101010101010101010101010101010 + -10000
1101001011010101010101010101010101010101010101010101010101010011010

Our tests for the other operations have the same structure.

3.2. General Structure of the Four Operations

The add(), subtract(), multiply(), and divide() methods in the BinaryStringOperations class share these common structural properties:

  • Validation → Some helper methods ensure that the inputs are valid binary strings
  • Sign handling → We strip the sign with removePlusMinus(), determine the positivity of the numbers with isPositive(), and ensure that the correct sign is returned in the result
  • Conditional logic based on sign → The methods contain conditional statements that handle different scenarios based on whether the input strings are positive or negative
  • Helper methods → unsignedAdd(), unsignedSubtract(), unsignedMultiply(), and unsignedDivide() implement the core bit manipulation and calculation logic
  • Edge case handling → We implemented special handling for edge cases, such as division by zero

Here we see how we applied these structural properties to the add() method:

static String add(String a, String b) {
    String unsignedA = removePlusMinus(a);
    String unsignedB = removePlusMinus(b);
    if (isPositive(a)) {
        if (isPositive(b)) {
            // Example: a=1011, b=1111
            return unsignedAdd(unsignedA, unsignedB);
        } else {
            // Example: a=1011, b=-1111
            return subtract(unsignedA, unsignedB);
        }
    } else {
        if (isPositive(b)) {
            // Example: a=-1011, b=1111
            return subtract(unsignedB, unsignedA);
        } else {
            // Example: a=-1011, b=-1111
            return '-' + unsignedAdd(unsignedA, unsignedB);
        }
    }
}

The subtract(), multiply(), and divide() methods follow a similar structure to ensure consistent handling of signed binary string operations.

3.3. unsignedAdd()

The unsignedAdd() method performs the addition of two unsigned binary strings. Its algorithm is similar to manual binary addition, done bit by bit from right to left, handling the carry bit as necessary.

The comments in the code make the various steps clear:

static String unsignedAdd(String a, String b) {
    validateUnsignedBinaryString(a);
    validateUnsignedBinaryString(b);
    
    // Remove leading zeros
    a = a.replaceFirst("^0+(?!$)", "");
    b = b.replaceFirst("^0+(?!$)", "");
    int length = Math.max(a.length(), b.length());
    // Pad the shorter string with leading zeros to make both strings of equal length
    a = String.format("%" + length + "s", a).replace(' ', '0');
    b = String.format("%" + length + "s", b).replace(' ', '0');
    StringBuilder result = new StringBuilder(length * 2);
    boolean carry = false;
    // Iterate from the LSB (least significant bit) to the MSB (most significant bit)
    for (int i = length - 1; i >= 0; i--) {
        // Determine the bit values of the current position for both strings
        boolean v1 = (a.charAt(i) == '1');
        boolean v2 = (b.charAt(i) == '1');
        // Calculate the result bit for the current position considering the carry
        boolean r = carry && v1 && v2 || carry && !v1 && !v2 || !carry && v1 && !v2 || !carry && !v1 && v2;
        // Update the carry for the next iteration
        carry = carry && v1 || carry && v2 || v1 && v2;
        // Insert the result bit at the beginning of the result string
        result.insert(0, r ? '1' : '0');
    }
    // If there is a carry left, insert it at the beginning of the result string
    if (carry) {
        result.insert(0, '1');
    }
    return result.toString();
}

The most difficult part of the code is the calculation of bits:

boolean r = carry && v1 && v2 || carry && !v1 && !v2 || !carry && v1 && !v2 || !carry && !v1 && v2;
[...]
carry = carry && v1 || carry && v2 || v1 && v2;

In a nutshell, we write the truth tables of the bit calculations on paper and copy them into simplogic.dev. Then it generates the boolean expressions that we insert into the code. We can achieve the same expressions manually by applying Karnaugh mapping and De Morgan’s theorems.

simplogic.dev also allows us to generate the truth tables we used from the code:

simplogic example

We use truth tables only for summation. The other methods don’t need them.

3.4. unsignedSubtract()

We do the subtraction using two’s complement, as we’ve already seen for binary literals. The simplest way is to invert the bits of b, add 1, and then add that value to a:

static String unsignedSubtract(String a, String b) {
    [...]
    // Two's complement step 1: invert all the bits
    StringBuilder inverted = new StringBuilder();
    for (int i = 0; i < b.length(); i++) {
        char c = b.charAt(i);
        if (c == '0') {
            inverted.append('1');
        } else {
            inverted.append('0');
        }
    }
    
    // Two's complement step 2: add 1 to the inverted bits
    String b2complement = addOne(inverted.toString());
    
    // Executes the sum between a and the two's complement of b
    // Since a>=b, the result will always have an extra bit due to the carry out
    // We remove this extra bit by using substring(1)
    String result = unsignedAdd(a, b2complement).substring(1);
    
    [...]
}

By subtracting in two’s complement, we can take advantage of the simpler and better understood process of binary addition, which involves only carrying, not borrowing. Thus, using two’s complement reduces the complexity and potential for error in the subtraction operation.

3.5. unsignedMultiply()

To perform the multiplication, we iterate over the bits of b, and for each bit that is set to 1, we add a shifted version of a to the result:

static String unsignedMultiply(String a, String b) {
    [...]
    
    // Iterate from the LSB (least significant bit) to the MSB (most significant bit) of "b"
    for (int i = b.length() - 1; i >= 0; i--) {
        zeros++;
        if (b.charAt(i) == '1') {
            // Calculate the partial product by appending the necessary number of zeros to "a"
            // and this partial product is added to the result
            result = unsignedAdd(result, appendZeros(a, zeros));
        }
    }
    
    return result;
}

In this way, we treat multiplication as a series of sums, just as we would in manual calculations. This reduces the complexity of the code.

3.6. unsignedDivide()

Again, our algorithm does the calculations as we would by hand. We iterate over the bits of the dividend, keeping track of the remainder, and build the result by comparing the remainder to the divisor at each step:

static String unsignedDivide(String a, String b) {
    [...]
    
    // Iterate through each bit of the dividend "a" from MSB to LSB
    for (int i = 0; i < a.length(); i++) {
        if (result.length() == 0) {
            // Initialize result and remainder if not yet done
            if (compareBinaryStrings(a.substring(0, i + 1), b) >= 0) {
                result.append('1');
                remainder = unsignedSubtract(a.substring(0, i + 1), b);
            }
        } else {
            // Concatenate the current bit of "a" to the remainder
            remainder += a.charAt(i);
            // Compare the current remainder with the divisor "b"
            if (compareBinaryStrings(remainder, b) >= 0) {
                // If remainder is greater than or equal to divisor, append '1' to result
                result.append('1');
                // Subtract divisor "b" from remainder
                remainder = unsignedSubtract(remainder, b);
            } else {
                // If remainder is less than divisor, append '0' to result
                result.append('0');
            }
        }
    }
    return result.toString();
}

In this code, we implement division using the previously implemented subtraction, which in turn uses addition.

3.7. Code Optimization?

Our BinaryStringOperations class provides correct algorithmic implementations for arbitrary-precision arithmetic. If we don’t have any special speed requirements, it’s fine.

However, the BigInteger class is significantly more optimized, with advanced and very complex algorithms that drastically reduce execution time compared to the basic methods we can implement from scratch.

4. Conclusion

In this article, we’ve explored different methods for performing arbitrary-precision arithmetic operations on binary numbers in Java. We discussed the limitations of binary literals, and then looked at the use of the BigInteger class.

We also explored a custom implementation of arbitrary-precision binary arithmetic using the BinaryStringOperations class. This implementation is particularly useful in environments where BigInteger isn’t available, or when we want to delve into the algorithmic challenges of bit manipulation.

As always, the full source code is available over on GitHub.

       
Viewing all 4476 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>