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

Evaluating H2 as a Production Database

$
0
0

1. Overview

H2 is a very popular database solution, especially when it comes to testing. It was first introduced as an in-memory database to use in test and integration environments because it’s easy to set up and use. It’s also proven reliable and fast, so it’s a very good alternative for slow-to-configure and costly-to-deploy traditional databases.

In time, H2 started supporting disk storage and also a server mode. This way, it became an option for long-persisted storage and a possible database for distributed systems, since different services and/or servers can access it in server mode. So, developers have started thinking of H2 as another option for storage, even in production environments.

In this tutorial, we’ll go through the features that make H2 an option for production storage, the limitations that still exist and we’ll evaluate cases in which H2 can be used in production and others that we should avoid.

2. H2 Features

Let’s first see some of the features of H2 that make it a fast and easy-to-use database solution:

  • it has a very fast database engine
  • it’s easy to configure, especially in Spring Boot applications
  • supports standard SQL and JDBC API
  • provides security, with authentication, encryption functions, and others
  • uses transactions and two phase commit
  • allows multiple connections and row level locking
  • it’s open source and written in Java
  • provides a web console application

On top of that, H2 supports different connection modes:

  • an embedded mode, for local connections, using JDBC
  • a server mode, for remote connections, using JDBC or ODBC over TCP/IP
  • and a mixed mode, which combines both previous modes

2.1. In-Memory

H2 provides in-memory storage. In certain cases, like caching and gaming, we don’t need to persist data and can use in-memory databases. Redis is a very popular in-memory caching solution and is widely used in production environments.

So, in modern applications, where multiple databases can be used by a service, we can use both persistence and in-memory databases to improve performance. If we take into consideration how fast H2 is, as a Java database with the option of embedded mode, then we can understand why people use it in production more and more.

2.2. Disk Storage

When talking about databases, it’s the default thought that data is being persisted. Databases were initially invented to store data and provide durability. In the vast majority of cases, we don’t want to lose data if the database shuts down or restarts.

Over time, there was an increased request for H2 to support some persistence too. In later versions, H2 added disk storage support and it can be used for persisting data. In this mode, data is stored in files of the host machine. So, when the database shuts down, data is retained to be used when it restarts.

2.3. Embedded Mode

In embedded mode, the database and the application will be executed in the same JVM. This is the fastest and easiest H2 mode. The application can connect to the database using JDBC. But other applications/ servers, outside this virtual machine, don’t have access to it.

In this mode, both persistence and in-memory are possible. Also, there are no limits in databases or connections opened simultaneously.

If we have a production environment with multiple servers sharing the same database, then obviously this mode can’t be used. For small applications with only one server, the embedded mode could be the option, especially if we think about the growing usage of databases like SQLite in production in the last few years.

2.4. Server/Mixed Mode

In server mode, the application opens the database remotely, within the same or a different virtual or physical machine. We may see it also referred to as remote mode or server/client mode. One or more applications can then connect to H2 using JDBC or ODBC over TCP/IP.

This mode is slower, since we use TCP/IP, but allows more machines to connect to the same database. Same as in embedded mode, in-memory and persistence are supported and there are no limits on open databases or connections.

Mixed mode is a combination of the embedded and server modes. One application runs in embedded mode, but it also starts a server. Other applications can use this server to connect to the database remotely.

Using server or mixed mode, we can use the H2 database in production, when we need to support multiple servers connecting to the same database.

3. Why Is H2 a Proper Solution for Production?

From what we have seen so far, we can make the statement that H2 is a very fast and easy-to-use database, when used in embedded mode, with in-memory storage. Any application where the need for speed is more important than the need for durability could benefit from H2 over a traditional database.

In the other modes, it’s still a solid solution, compared to other databases, because:

  • it has a very fast engine
  • it’s very easy to use and configure (for Java applications, it could mean to just add a dependency and some properties)
  • it supports clustering, providing durability and no single point of failure
  • it’s very cheap since it’s open source
  • very easy to learn and use

In general, H2 is a simple, easy solution for production when we don’t need many TPS or big data sizes. Moreover, people who have used it in production reports that it would be a good fit for application internals such as caching, keeping short-lived data, loading fast-access needed data for performance improvements, etc (like what SQLite is mostly used for).

4. Why Is H2 Not a Proper Solution for Production?

There are cases when H2 might not be the best option for a production database. It has some obvious limitations, by design. But there are reports about its performance as well. H2 is being used lately in production environments, by different, usually small, applications and there are PoCs of its real-world performance.

Starting with the limitations, in any mode, H2 struggles when dealing with storing and reading large objects. If the objects don’t fit in memory, BLOB and CLOB types can be used, but this increases the complexity and performance.

Availability, scalability, and durability are also concerns since H2 clustering can only support up to two nodes in the cluster, at the moment. This means that for high availability services, it’s not an option to use in production. The same goes for storing critical data since durability might be compromised when having a maximum of only two servers up and running.

Moreover on durability, as stated in the H2 documentation, this database doesn’t guarantee that all committed transactions survive a power failure.

Reading reports and articles from people who have used H2 in production, the general conclusion is that, at the moment, for real apps with real-world data sizes, H2 isn’t reliable. Even if it can handle the size, it causes bugs and sometimes loss of data. Especially in multi-thread/ multi-connection use cases, users have suffered a lot of issues including deadlocks and poor performance when the data grows.

Some more minor limitations are that H2 doesn’t have commercial support and it has fewer features than other, traditional, databases. In the case of in-memory usage, we should consider the extra cost, since memory is more expensive than disk space.

5. Conclusion

In this article, we looked at the main features of the H2 database and focused on the modes that make it an option for production storage. Then, we discussed the strong points and also the limitations it has.

In conclusion, H2 is a good fit for production, in cases we don’t have to handle high volumes of data and high transaction rates. This is especially so when one production server is needed and H2 can be used in embedded mode. On the other hand, we should avoid it when we need high availability or scalability or face high data volumes.

       

Using Apache POI to Extract Column Names From Excel

$
0
0
start here featured

1. Introduction

Handling Excel files efficiently is crucial, whether reading data for processing or generating reports. Apache POI is a powerful library in Java that allows developers to manipulate and interact with Excel files programmatically.

In this tutorial, we’ll explore Apache POI to read column names from an Excel sheet.

We’ll start with a quick overview of the POI API. Then we’ll set the required dependencies and introduce a simple data example. We’ll then see the steps to extract column names from an Excel sheet within a file both in old and new format. Finally, we’ll write unit tests to verify all works as expected.

2. Dependencies and Example Setup

Let’s start with adding the required dependencies in our pom.xml, including poi-ooxml and commons-collections4:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

Let’s start with some sample data stored in two Excel files. The first one is the food_info.xlsx file with the following columns and sample data:

The next one consists of consumer data in the consumer_info.xls file with the older .xls extension and column names consist of these:

We’ll next follow the steps to extract the column names from these files using the provided API by POI.

3. Extracting Column Names From Excel

To read column names from an Excel sheet using Apache POI, we’ll create a method that performs the following steps:

  • Open the Excel file
  • Access the desired sheet
  • Read the header row (first row) to get the column names

3.1. Open Excel File

First, we need to open the Excel file and create a WorkBook instance. POI provides support for both .xls and .xlsx using two different abstractions namely XSSFWorkbook and HSSFWorkbook:

public static Workbook openWorkbook(String filePath) throws IOException {
    try (InputStream fileInputStream = new FileInputStream(filePath)) {
        if (filePath.toLowerCase()
          .endsWith("xlsx")) {
            return new XSSFWorkbook(fileInputStream);
        } else if (filePath.toLowerCase()
          .endsWith("xls")) {
            return new HSSFWorkbook(fileInputStream);
        } else {
            throw new IllegalArgumentException("The specified file is not an Excel file");
        }
    } catch (OLE2NotOfficeXmlFileException | NotOLE2FileException e) {
        throw new IllegalArgumentException(
          "The file format is not supported. Ensure the file is a valid Excel file.", e);
    }
}

Essentially, we’re using the WorkBook interface that represents an Excel workbook. It’s the top-level object for handling Excel files in Apache POI. XSSFWorkbook is a class that implements WorkBook for .xlsx files. On the other hand,  HSSFWorkbook class implements WorkBook for .xls files.

3.2. Access Work Sheet

Now that we have a Workbook, let’s access the desired sheet within the workbook by sheet name:

public static Sheet getSheet(Workbook workbook, String sheetName) {
    return workbook.getSheet(sheetName);
}

The Sheet interface in POI API represents a sheet within an Excel workbook.

3.3. Read the Header Row

Using the Sheet object, we can access its data as desired.

Let’s use the API to read the header row which has the names of all the columns in the sheet. The Row interface represents a row in a sheet. Simply stated, here we’ll access the first row of the sheet passing the index 0 to the sheet.get()  method. Then, we’ll use the Cell interface to extract each column name within the header row.

The Cell interface represents a cell in a row:

public static List<String> getColumnNames(Sheet sheet) {
    Row headerRow = sheet.getRow(0);
    if (headerRow == null) {
        return Collections.EMPTY_LIST;
    }
    return StreamSupport.stream(headerRow.spliterator(), false)
      .filter(cell -> cell.getCellType() != CellType.BLANK)
      .map(Cell::getStringCellValue)
      .filter(cellValue -> cellValue != null && !cellValue.trim()
        .isEmpty())
      .map(String::trim)
      .collect(Collectors.toList());
}

Here, we’re using Java Streams to iterate over each Cell. We filter out blank cells and cells with only whitespace or null values. Then we extract the string value of each remaining cell using the getStringCellValue()  method from Cell. In this case, the API returns the String value of the data in the cell. Additionally, we trimmed the whitespace from these string values. Finally, we collected these cleaned string values into a list and returned the list.

At this point, it’s worth also touching upon a related method called getRichStringTextValue() which retrieves the cell value as a RichTextString. This is useful when handling formatted text, such as text with different fonts, colours, or styles within the same cell. If our use-case requires us not just to extract column names but also preserve the formatting across these column names, then we’ll map using Cell::getRichStringTextValue() instead and store the result as List<RichTextString>

4. Unit Tests

Let’s now setup unit tests to see the POI API in action for both .xls and .xlsx files:

@Test
public void givenExcelFileWithXLSXFormat_whenGetColumnNames_thenReturnsColumnNames() throws IOException {
    Workbook workbook = ExcelUtils.openWorkbook(XLSX_TEST_FILE_PATH);
    Sheet sheet = ExcelUtils.getSheet(workbook, SHEET_NAME);
    List<String> columnNames = ExcelUtils.getColumnNames(sheet);
    assertEquals(4, columnNames.size());
    assertTrue(columnNames.contains("Category"));
    assertTrue(columnNames.contains("Name"));
    assertTrue(columnNames.contains("Measure"));
    assertTrue(columnNames.contains("Calories"));
    workbook.close();
}
@Test
public void givenExcelFileWithXLSFormat_whenGetColumnNames_thenReturnsColumnNames() throws IOException {
    Workbook workbook = ExcelUtils.openWorkbook(XLS_TEST_FILE_PATH);
    Sheet sheet = ExcelUtils.getSheet(workbook, SHEET_NAME);
    List<String> columnNames = ExcelUtils.getColumnNames(sheet);
    assertEquals(3, columnNames.size());
    assertTrue(columnNames.contains("Name"));
    assertTrue(columnNames.contains("Age"));
    assertTrue(columnNames.contains("City"));
    workbook.close();
}

The tests verify that the API supports reading column names from both types of Excel files.

5. Conclusion

In this article, we explored how to use Apache POI to read column names from an Excel sheet. We started with an overview of Apache POI, followed by setting up the necessary dependencies. We then saw a step-by-step guide with code snippets to implement the solution and included unit tests to ensure correctness.

Apache POI is a robust library that simplifies the process of working with Excel files in Java, making it an invaluable tool for developers handling data interchange between applications and Excel.

As always, the full implementation of this article can be found over on GitHub.

       

Passing List as Cucumber Parameter

$
0
0
start here featured

1. Introduction

Cucumber is a popular tool in behavior-driven development (BDD) for writing test scenarios in plain language. Utilizing parameters in Cucumber allows for dynamic and reusable tests.

In this article, we’ll discuss essential techniques for using Cucumber parameters in Java testing.

2. Understanding Cucumber Parameters

Cucumber parameters are placeholders in feature files that allow scenarios to be executed with different inputs.

Here’s a basic example illustrating string parameters:

Feature: User Login
  Scenario: User logs into the system
    Given the user enters username "john_doe" and password "password123"
    When the user clicks on the login button
    Then the dashboard should be displayed

Step definitions capture these parameters using annotations:

public class LoginSteps {
    private static final Logger logger = LoggerFactory.getLogger(LoginSteps.class);
    @Given("the user enters username {string} and password {string}")
    public void enterUsernameAndPassword(String username, String password) {
        logger.info("Username: {}, Password: {}", username, password);
    }
    @When("the user clicks on the login button")
    public void clickLoginButton() {
        logger.info("Login button clicked");
    }
    @Then("the dashboard should be displayed")
    public void verifyDashboardDisplayed() {
        logger.info("Dashboard displayed");
    }
}

Here, we define step definitions that correspond to each step in the feature file. The @Given, @When, and @Then annotations capture the parameters specified in the scenario and pass them to the corresponding methods.

3. Using DataTable for List Parameters

DataTable allows testing with multiple sets of data.

Let’s look at an example of how to use DataTable:

Scenario: Verify user login with multiple accounts
  Given the user tries to log in with the following accounts:
    | username    | password   |
    | john_doe    | password1  |
    | jane_smith  | password2  |
  When each user attempts to log in
  Then each user should access the system successfully

We handle DataTable with a list of custom objects:

public class DataTableLoginSteps {
    private static final Logger logger = LoggerFactory.getLogger(DataTableLoginSteps.class);
    @Given("the user tries to log in with the following accounts:")
    public void loginUser(List<UserCredentials> credentialsList) {
        for (UserCredentials credentials : credentialsList) {
            logger.info("Username: {}, Password: {}", credentials.getUsername(), credentials.getPassword());
        }
    }
    public static class UserCredentials {
        private String username;
        private String password;
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }
}

In this example, we use the DataTable class to map the data table from the feature file to a list of UserCredentials objects. This allows us to iterate over the list and process each set of credentials individually.

4. Using Regular Expressions

Regular expressions (regex) allow flexible parameter matching. Let’s consider the following example for email validation:

Scenario: Verify email validation
  Given the user enters email address "john.doe@example.com"
  When the user submits the registration form
  Then the email should be successfully validated

We capture the email address parameter with step definitions:

public class RegexLoginSteps {
    private static final Logger logger = LoggerFactory.getLogger(RegexLoginSteps.class);
    @Given("the user enters email address \"([^\"]*)\"")
    public void enterEmailAddress(String emailAddress) {
        logger.info("Email: {}", emailAddress);
    }
}

Here, we use a regular expression to capture the email address parameter. The regex \”([^\”]*)\” matches any string enclosed in double quotes, allowing us to handle dynamic email addresses.

5. Using Custom Transformer

Custom transformers convert input strings to specific Java types. Let’s look at an example:

Scenario: Verify date transformation
  Given the user enters birthdate "1990-05-15"
  When the user submits the registration form
  Then the birthdate should be stored correctly

Next, let’s define the step definitions to handle data transformation:

public class LocalDateTransformer {
    private static final Logger logger = LoggerFactory.getLogger(LocalDateTransformer.class);
    @ParameterType("yyyy-MM-dd")
    public LocalDate localdate(String dateString) {
        return LocalDate.parse(dateString);
    }
    @Given("the user enters birthdate {localdate}")
    public void enterBirthdate(LocalDate birthdate) {
        logger.info("Birthdate: {}", birthdate);
    }
}

In this method, we define a custom transformer using the @ParameterType annotation. This transformer converts the input string in “yyyy-MM-dd” format into a LocalDate object, allowing us to work with date objects directly in our step definitions.

6. Conclusion

Passing lists as parameters in Cucumber enhances our ability to test various data sets efficiently. Whether we use DataTable, regular expressions, or custom transformers, the goal remains consistent: to streamline the testing process and ensure comprehensive coverage.

As usual, we can find the full source code and examples over on GitHub.

       

Spring Security 6.3 – What’s New

$
0
0

1. Introduction

Spring Security version 6.3 has introduced a range of security enhancements in the framework.

In this tutorial, we’ll discuss some of the most notable features, highlighting their benefits and usage.

2. Passive JDK Serialization Support

Spring Security 6.3 included passive JDK serialization support. However, before we discuss this further, let’s understand the problem and issues surrounding it.

2.1. Spring Security Serialization Design

Before version 6.3, Spring Security had a strict policy regarding serialization and deserialization of its classes across different versions through JDK serialization. This restriction was a deliberate design decision by the framework to ensure security and stability. The rationale was to prevent incompatibilities and security vulnerabilities from deserializing objects serialized in one version using a different version of Spring Security.

One key aspect of this design is the usage of a global serialVersionUID for the entire Spring Security project. In Java, the serialization and deserialization process uses a unique identifier serialVersionUID to verify that a loaded class corresponds exactly to a serialized object.

By maintaining a global serialVersionUID unique to each release version of Spring Security, the framework ensures that serialized objects from one version cannot be deserialized using another version. This approach effectively creates a version barrier, preventing the deserialization of objects with mismatched serialVersionUID values.

For example, the SecurityContextImpl class in Spring Security represents security context information. The serialized version of this class includes the serialVersionUID specific to that version. When attempting to deserialize this object in a different version of Spring Security, the serialVersionUID mismatch prevents the process from succeeding.

2.2. Challenges Arising from the Serialization Design

While prioritizing enhanced security, this design policy also introduces several challenges. Developers commonly integrate Spring Security with other Spring libraries, such as Spring Session, to manage user login sessions. These sessions encompass crucial user authentication and security context information, typically implemented through Spring Security classes. Furthermore, to optimize user experience and enhance application scalability, developers often store this session data across various persistent storage solutions, including databases.

The following are some of the challenges that arise due to the serialization design. Upgrading applications through the Canary release process can lead to an issue if the Spring Security version changes. In such cases, the persisted session information cannot be deserialized, potentially necessitating users to re-login.

Another issue arises in application architectures utilizing Remote Method Invocation (RMI) with Spring Security. For instance, if the client application employs Spring Security classes in a remote method call, it must serialize them on the client side and deserialize them on the other side. If both applications do not share the same Spring Security version, this invocation fails, resulting in an InvalidClassException exception.

2.3. Workarounds

The typical workarounds for this issue are as follows. We can use a different serialization library other than JDK Serialization, such as Jackson Serialization. With this, instead of serializing the Spring Security class, we obtain a JSON representation of the required details and serialize it with Jackson.

Another option is to extend the required Spring Security class, such as Authentication, and explicitly implement custom serialization support through the readObject and writeObject methods.

2.4. Serialization Changes in Spring Security 6.3

With version 6.3, class serialization undergoes a compatibility check with the preceding minor version. This ensures that upgrading to newer versions allows the deserialization of Spring Security classes seamlessly.

3. Authorization

Spring Security 6.3 has introduced a few notable changes in the Spring Security Authorization. Let us explore these in this section.

3.1. Annotation Parameters

Spring Security’s method security supports meta-annotations. We can take an annotation and improve it’s readability based on the application’s use case. For instance, we can simplify the @PreAuthorize(“hasRole(‘USER’)”) to the following:

@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface IsUser {
    String[] value();
}

Next, we can use this @IsUser annotation in the business code:

@Service
public class MessageService {
    @IsUser
    public Message readMessage() {
        return "Message";
    }
}

Let’s assume we have another role, ADMIN. We can create an annotation named @IsAdmin for this role. However, this would be redundant. It would be more appropriate to use this meta-annotation as a template and include the role as an annotation parameter. Spring Security 6.3 introduces the ability to define such meta-annotations. Let us demonstrate this with a concrete example:

To template a meta-annotation, first, we need to define a bean PrePostTemplateDefaults:

@Bean
PrePostTemplateDefaults prePostTemplateDefaults() {
    return new PrePostTemplateDefaults();
}

This bean definition is required for the template resolution.

Next, we’ll define a meta-annotation @CustomHasAnyRole for the @PreAuthorize annotation that can accept both USER and ADMIN roles:

@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({value})")
public @interface CustomHasAnyRole {
    String[] value();
}

We can use this meta-annotation by supplying the roles:

@Service
public class MessageService {
    private final List<Message> messages;
    public MessageService() {
        messages = new ArrayList<>();
        messages.add(new Message(1, "Message 1"));
    }
    
    @CustomHasAnyRole({"'USER'", "'ADMIN'"})
    public Message readMessage(Integer id) {
        return messages.get(0);
    }
    @CustomHasAnyRole("'ADMIN'")
    public String writeMessage(Message message) {
        return "Message Written";
    }
    
    @CustomHasAnyRole({"'ADMIN'"})
    public String deleteMessage(Integer id) {
        return "Message Deleted";
    }
}

In the above example, we supplied the role values – USER and ADMIN as the annotation parameter.

3.2. Securing Return Values

Another powerful new feature in Spring Security 6.3 is the ability to secure domain objects using the @AuthorizeReturnObject annotation. This enhancement allows for more granular security by enabling authorization checks on the objects returned by methods, ensuring that only authorized users can access specific domain objects.

Let us demonstrate this with an example. Let’s say we have the following Account class with the iban and balance fields. The requirement is that only users with read authority can retrieve the account balance.

public class Account {
    private String iban;
    private Double balance;
    // Constructor
    public String getIban() {
        return iban;
    }
    @PreAuthorize("hasAuthority('read')")
    public Double getBalance() {
        return balance;
    }
}

Next, let us define the AccountService class, which returns an account instance:

@Service
public class AccountService {
    @AuthorizeReturnObject
    public Optional<Account> getAccountByIban(String iban) {
        return Optional.of(new Account("XX1234567809", 2345.6));
    }
}

In the above snippet, we have used the @AuthorizeReturnObject annotation. Spring security ensures that the Account instance can only be accessed by the user with read authority.

3.3. Error Handling

In the section above, we discussed using the @AuthorizeReturnObject annotation to secure the domain objects. Once enabled, the unauthorized access results in an AccessDeniedException. Spring Security 6.3 provides the MethodAuthorizationDeniedHandler interface to handle authorization failures.

Let us demonstrate this with an example. Let us extend the example in section 3.2 and secure the IBAN with the read authority. However, we intend to provide a masked value instead of returning an AccessDeniedException for any unauthorized access.

Let us define an implementation of the MethodAuthorizationDeniedHandler interface:

@Component
public class MaskMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler  {
    @Override
    public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
        return "****";
    }
}

In the above snippet, we provided a masked value if there is an AccessDeniedException. This handler class can be used in the getIban() method, as shown below:

@PreAuthorize("hasAuthority('read')")
@HandleAuthorizationDenied(handlerClass=MaskMethodAuthorizationDeniedHandler.class)
public String getIban() {
    return iban;
}

4. Compromised Password Checking

Spring Security 6.3 provides an implementation for compromised password checks. This implementation checks a provided password against a compromised password database (pwnedpasswords.com). Therefore, applications can verify the user-provided password at registration time. The following code snippets demonstrate the usage.

First, define a bean definition of HaveIBeenPwnedRestApiPasswordChecker class:

@Bean
public HaveIBeenPwnedRestApiPasswordChecker passwordChecker() {
    return new HaveIBeenPwnedRestApiPasswordChecker();
}

Next, use this implementation to check the user-provided password:

@RestController
@RequestMapping("/register")
public class RegistrationController {
    private final HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker;
    @Autowired
    public RegistrationController(HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker) {
        this.haveIBeenPwnedRestApiPasswordChecker = haveIBeenPwnedRestApiPasswordChecker;
    }
    @PostMapping
    public String register(@RequestParam String username, @RequestParam String password) {
        CompromisedPasswordDecision compromisedPasswordDecision = haveIBeenPwnedRestApiPasswordChecker.checkPassword(password);
        if (compromisedPasswordDecision.isCompromised()) {
	    throw new IllegalArgumentException("Compromised Password.");
	}
        // ...
        return "User registered successfully";
    }
}

5. OAuth 2.0 Token Exchange Grant

Spring Security 6.3 also introduced support for the OAuth 2.0 Token Exchange (RFC 8693) grant, allowing clients to exchange tokens while retaining the user’s identity. This feature enables scenarios like impersonation, where a resource server can act as a client to obtain new tokens. Let us elaborate on this with an example.

Let’s assume we have a resource server named loan-service, which provides various APIs for loan accounts. This service is secured, and clients need to supply an access token that must have the audience (aud claim) of the loan service.

Let’s now imagine that loan-service needs to invoke another resource service loan-product-service which exposes details for loan products. The loan-product-service is also secure and requires tokens that must have the audience of the loan-product-service. Since the audience is different in these two services, the token for loan service can’t be used for loan-product-service.

In this case, the resource server, loan-service should become a client and exchange the existing token for a new token for loan-product-service that retains the the original token identity.

Spring Security 6.3 provides a new implementation of the OAuth2AuthorizedClientProvider class named TokenExchangeOAuth2AuthorizedClientProvider for the token-exchange grant.

6. Conclusion

In this article, we discussed various new features introduced in Spring Security 6.3.

The notable changes are enhancement to the authorization framework, passive JDK serialization support, and OAuth 2.0 Token Exchange support.

The source code is available over on GitHub.

       

Unescape HTML Symbols in Java

$
0
0
start here featured

1. Overview

Server-side applications sometimes require parsing of HTML characters. This is where the process of escaping/unescaping comes in helpful. In this tutorial, we’ll demonstrate a couple of ways to unescape HTML characters in Java. We’ll look into some of the available libraries that can handle this task.

2. Ways to Unescape HTML Character

Processing HTML symbols within the JVM can be tricky because these symbols in Java strings represent more than two or more characters. For example, < or > characters in HTML are represented as &lt; and &gt; Strings in Java. For the JVM to interpret these characters correctly, we need ways to escape or unescape them.

We can always write code ourselves to handle unescaping HTML characters, but the process would take time. It is also prone to errors. Instead, we can work with available Java libraries that can handle the task. The sample code in the subsections below tests the conversion of escaped HTML symbols to “unescaped” characters using various Java libraries.

2.1. Unescaping via Apache Commons’ StringEscapeUtils

Apache Commons is a popular Java library with the goal and focus on reusing components. Its StringEscapeUtils class has many convenient methods and among them is StringEscapeUtils.unescapeHtml4() within the package org.apache.commons.text:

String expectedQuote = "\"Hello\" Baeldung";
String escapedQuote = "&quot;Hello&quot; Baeldung";
Assert.assertEquals(expectedQuote, StringEscapeUtils.unescapeHtml4(escapedQuote));
String escapedStringsWithHtmlSymbol = "&lt;p&gt;&lt;strong&gt;Test sentence in bold type.&lt;/strong&gt;&lt;/p&gt;";
String expectedStringsWithHtmlSymbol = "<p><strong>Test sentence in bold type.</strong></p>";
Assert.assertEquals(expectedStringsWithHtmlSymbol, StringEscapeUtils.unescapeHtml4(escapedStringsWithHtmlSymbol));

2.2. Unescaping via Spring Framework’s HtmlUtils

Spring Framework is a solid Java platform that has various infrastructure support for developing applications. It provides the HtmlUtils.htmlUnescape() function which is used to convert escaped HTML characters. It can be found under the package org.springframework.web.util:

String expectedQuote = "\"Code smells\" -Martin Fowler";
String escapedQuote = "&quot;Code smells&quot; -Martin Fowler";
Assert.assertEquals(expectedQuote, HtmlUtils.htmlUnescape(escapedQuote));
String escapedStringsWithHtmlSymbol = "&lt;p&gt;Loren Ipsum is a popular paragraph.&lt;/p&gt;";
String expectedStringsWithHtmlSymbol = "<p>Loren Ipsum is a popular paragraph.</p>";
Assert.assertEquals(expectedStringsWithHtmlSymbol, HtmlUtils.htmlUnescape(escapedStringsWithHtmlSymbol));

2.3. Unescaping via Unbescape’s HtmlEscape

The Unbescape library is for escaping and unescaping many formats of data, among them HTML, JSON, and CSS. The example below shows unescaping HTML characters and tags via the HtmlEscape.unescapeHtml() method:

String expectedQuote = "\"Carpe diem\" -Horace";
String escapedQuote = "&quot;Carpe diem&quot; -Horace";
Assert.assertEquals(expectedQuote, HtmlEscape.unescapeHtml(escapedQuote));
String escapedStringsWithHtmlSymbol = "&lt;p&gt;&lt;em&gt;Pizza is a famous Italian food. Duh.&lt;/em&gt;&lt;/p&gt;";
String expectedStringsWithHtmlSymbol = "<p><em>Pizza is a famous Italian food. Duh.</em></p>";
Assert.assertEquals(expectedStringsWithHtmlSymbol, HtmlEscape.unescapeHtml(escapedStringsWithHtmlSymbol));

2.4. Unescaping via Jsoup’s Entities.unescape()

The Jsoup library is for all sorts of HTML manipulation. Its Java HTML Parser offers a wide range of support for HTML and XML requirements. Entities.unescape() is a function whose main goal is to unescape Strings with escaped HTML characters:

String expectedQuote = "\"Jsoup\" is another strong library";
String escapedQuote = "&quot;Jsoup&quot; is another strong library";
Assert.assertEquals(expectedQuote,  Entities.unescape(escapedQuote));
String escapedStringsWithHtmlSymbol = "&lt;p&gt;It simplifies working with real-world &lt;strong&gt;HTML&lt;/strong&gt; and &lt;strong&gt;XML&lt;/strong&gt;&lt;/p&gt;";
String expectedStringsWithHtmlSymbol = "<p>It simplifies working with real-world <strong>HTML</strong> and <strong>XML</strong></p>";
Assert.assertEquals(expectedStringsWithHtmlSymbol,  Entities.unescape(escapedStringsWithHtmlSymbol));

 3. Conclusion

In this tutorial, we’ve demonstrated ways how to unescape HTML characters by using various libraries available to the Java community. Libraries like Apache Commons and Spring Framework are popular libraries that offer various tools for processing HTML entities. Unbescape and Jsoup offer processing capabilities not just for HTML characters, but also other forms of data formats. They help validate inputs from the server side whenever an application requires it.

All code samples used in the article are available over on GitHub.

       

Getting Arithmetic Results in the Modulo (10^9 + 7) Format

$
0
0

1. Overview

When working with large numbers, intermediate results can exceed data type limits. For such scenarios, modular arithmetic helps keep numbers manageable and prevent overflow errors.

In this tutorial, we’ll learn how to perform modulo-arithmetic operations with 10^9 + 7 as an example format for the modulus.

2. Basics

In this section, let’s brush up on a few important concepts to help us perform the modular arithmetic operations efficiently.

2.1. Symmetric Modulo and Non-negative Remainder

Let’s imagine that we want to compute a mod b. By applying the division algorithm, we can get the positive remainder (r):

a = q.b + r
where, b>0 and r≧0

We must note that we’ll assume that modulus (b) is a positive number for the scope of this article.

Subsequently, we can rewrite the equation to get a negative remainder:

a = (q+1)b - (b - r)
where, -(b-r)≦0

From a purely mathematical point of view, this is called symmetric modulo, where the remainder lies in the range [-b, b). However, most programming implementations prefer the non-negative remainder in the range [0, b) when returning the result of the modulo operation for a positive modulus.

2.2. Congruence Property

Now, let’s see another interesting property in modular arithmetic that deals with congruence relation ():

(a mod n) = (b mod n) <=> a≡b (mod n)

Essentially, two numbers, a and b, are congruent modulo n, if they give the same remainder when divided by n. As a result, we can say that 10 ≡ 4 (mod 3).

2.3. Strategy for Modular Arithmetic

Using the symmetric modulo and congruence properties, we can formulate a strategy for modular arithmetic operations by choosing a smaller intermediate result amongst two numbers whenever feasible.

For this purpose, let’s define the minSymmetricMod() method that returns the remainder that has a lower absolute value:

static int minSymmetricMod(int x) {
    if (Math.abs(x % MOD) <= Math.abs((MOD - x) % MOD)) {
        return x % MOD;
    } else {
        return -1 * ((MOD - x) % MOD);
    }
}

As part of our strategy, we’ll use minSymmetricMod() to compute all the intermediate results.

Further, let’s define the mod() method that’s guaranteed to give a positive remainder value:

static int mod(int x) {
    if (x >= 0) {
        return x % MOD;
    } else {
        return mod(MOD + x);
    }
}

We can use method overloading to support long values.

For our use cases, we’re using MOD = 10^9 + 7. However, unless stated otherwise, the general principle holds true for any other modulus value.

We’ll apply these concepts to addition, subtraction, multiplication, and other modular arithmetic operations.

3. Modular Sum

In this section, we’ll learn to do the modulo sum operation efficiently.

3.1. Distributive Property

The modulo operation follows distributive property over addition:

(a + b) mod n = ((a mod n) + (b mod n)) mod n

Further, we can use the symmetric_mod for all the intermediate modulo operators:

(a + b) mod n = ((a symmetric_mod n) + (b symmetric_mod n)) mod n

The outermost mod operation guarantees a positive remainder. In contrast, the inner symmetric_mod operations reduce the magnitude of the numbers by appropriately choosing the lower of the positive or negative remainders.

3.2. Computation

We can write the modAdd() method to compute the modulo sum of two numbers:

static int modSum(int a, int b) {
    return mod(minSymmetricMod(a) + minSymmetricMod(b));
}

Now, let’s validate our approach by computing modulo sum for a few pairs of numbers:

assertEquals(1000000006, modSum(500000003, 500000003));
assertEquals(1, modSum(1000000006, 2));
assertEquals(999999999, modSum(999999999, 0));
assertEquals(1000000005, modSum(1000000006, 1000000006));

Perfect! We’ve got the right results.

4. Modular Subtraction

Now that we’ve discussed modulo addition operation, we can extend our approach to modulo subtraction in this section.

4.1. Distributive Property

Let’s start by looking at the distributive property of modulo operation over subtraction:

(a - b) mod n = ((a mod n) - (b mod n)) mod n

Further, let’s apply our strategy of using the symmetric_mod operation for the intermediate results:

(a - b) mod n = ((a symmetric_mod n) - (b symmetric_mod n)) mod n

That’s it. We’re now ready to implement this approach for computing modulo subtraction.

4.2. Computation

Let’s write the modSubtract() method to compute the modulo subtraction of two numbers:

static int modSubtract(int a, int b) {
    return mod(minSymmetricMod(a) - minSymmetricMod(b));
}

Under the hood, minSymmetricMod() ensures that the magnitude of intermediate terms is kept as small as possible.

Next, we must test our implementation against different sets of numbers:

assertEquals(0, modSubtract(500000003, 500000003));
assertEquals(1000000005, modSubtract(1, 3));
assertEquals(999999999, modSubtract(999999999, 0));

Great! The result looks correct.

5. Modular Multiplication

In this section, let’s learn how to compute the modular multiplication of two numbers.

5.1. Distributive Property

Like addition, the modulo operation is also distributive over multiplication:

(a * b) mod n = ((a mod n) * (b mod n)) mod n

As a result, we can use the symmetric_mod operation for intermediate terms:

(a * b) mod n = ((a symmetric_mod n) * (b symmetric_mod n)) mod n

For all intermediate results, we’ll choose the remainder with a lower absolute value. Eventually, we’ll apply the mod operation to get a positive remainder.

5.2. Computation

Let’s write the modMultiply() method to compute the modulo multiplication of two numbers:

static long modMultiply(int a, int b) {
    int result = minSymmetricMod((long) minSymmetricMod(a) * (long) minSymmetricMod(b));
    return mod(result);
}

Although the upper bound on the return value of minSymmetricMod() is MOD – 1, the resulting value after multiplication could overflow the limit of the Integer data type. So, we must use a (long) type casting for multiplication.

Further, let’s test our implementation against a few pairs of numbers:

assertEquals(1, modMultiply(1000000006, 1000000006));
assertEquals(0, modMultiply(999999999, 0));
assertEquals(1000000006, modMultiply(500000003, 2));
assertEquals(250000002, modMultiply(500000003, 500000003));

Excellent! It looks like we nailed this one.

6. Modular Exponential

In this section, we’ll extend our approach of modulo multiplication computation to calculate modulo power.

6.1. Modular Exponential Property

Since modulo is distributive over multiplication, we can simplify the modular exponentiation:

(a ^ b) mod n = ((a mod n) ^ b) mod n

We must note that modulo isn’t distributive over the power operation as we didn’t take modulo for the exponent, b.

Like earlier, we can replace the mod for intermediate terms with the symmetric_mod operation:

(a ^ b) mod n = ((a symmetric_mod n) ^ b) mod n

We’ll use this property for computation purposes.

6.2. Computation

Let’s go ahead and write the modPower() method that takes two parameters, namely, base and exp:

static int modPower(int base, int exp) {
    int result = 1;
    int b = base;
    while (exp > 0) {
        if ((exp & 1) == 1) {
            result = minSymmetricMod((long) minSymmetricMod(result) * (long) minSymmetricMod(b));
        }
        b = minSymmetricMod((long) minSymmetricMod(b) * (long) minSymmetricMod(b));
        exp >>= 1;
    }
    return mod(result);
}

We used the fast power approach to calculate the exponential value while using minSymmetricMod() for intermediate modulo values. Additionally, we did a (long) typecast to hold the multiplication result, which could be larger than the Integer.MAX_VALUE value.

Now, let’s see our implementation in action by verifying it for different pairs of numbers:

assertEquals(16, modPower(2, 4));
assertEquals(1, modPower(2, 0));
assertEquals(1000000006, modPower(1000000006, 1));
assertEquals(1000000006, modPower(500000003, 500000003));
assertEquals(500000004, modPower(500000004, 500000004));
assertEquals(250000004, modPower(500000005, 500000005));

Fantastic! Our approach is highly performant and gives correct results.

7. Modular Multiplicative Inverse

The modulo multiplicative inverse of a mod m exists if and only if a and m are co-prime. For our scenario, m=10^9 + 7 is a prime number, which is co-prime with all other numbers, so let’s learn how to find the modular multiplication inverse.

7.1. Bézout’s Identity

According to Bézout’s Identity, we can express gcd(a, b) using two coefficients, x and y:

gcd(a, b) = x*a + y*b

Interestingly, when a and b are co-primes, we can replace gcd(a, b) with 1:

x*a + y*b = 1

Now, let’s apply the mod b operation on both sides of the equation:

(x*a + y*b) % b = 1 % b
=> (x*a) % b = 1

We’ve arrived at the definition of modular multiplicative inverse that states (a-1 * a) % b = 1.

The problem of finding the modular multiplicative inverse boils down to finding Bézout’s coefficient (x). Therefore, we can use the extended Euclidean algorithm to solve our use case.

7.2. Computation With Extended Euclidean Algorithm

Let’s write the extendedGcd() method that returns the greatest common divisor and Bézout’s coefficients:

static int[] extendedGcd(int a, int b) {
    if (b == 0) {
        return new int[] { a, 1, 0 };
    }
    int[] result = extendedGcd(b, a % b);
    int gcd = result[0];
    int x = result[2];
    int y = result[1] - (a / b) * result[2];
    return new int[] { gcd, x, y };
}

As a result, we can now find the modular multiplicative inverse using the extendedGCD() and mod() methods:

static int modInverse(int a) {
    int[] result = extendedGcd(a, MOD);
    int x = result[1];
    return mod(x);
}

Lastly, we must test our implementation for a few numbers:

assertEquals(500000004, modInverse(2));
assertEquals(1, modInverse(1));
assertEquals(1000000006, modInverse(1000000006));
assertEquals(1000000005, modInverse(500000003));

The result looks correct.

8. Modular Division

Modular multiplication and modular inverse are prerequisites to understanding the modular division operation. Further, we can perform modular division only when modular inverse exists.

For two co-prime numbers, a and b, let’s write the modDivide() method to compute modular division:

static int modDivide(int a, int b) {
    return mod(modMultiply(a, modInverse(b)));
}

If we notice clearly, it’s equivalent to the modular multiplication of a and the modular inverse of b.

Now, let’s see modular division in action for some of the sample numbers:

assertEquals(500000004, modDivide(1, 2));
assertEquals(2, modDivide(4, 2));
assertEquals(1000000006, modDivide(1000000006, 1));

It looks like we’ve got this one right.

9. Conclusion

In this article, we learned about the modular arithmetic operations for sum, multiplication, subtraction, power, inverse, and division. Further, we learned that modular multiplicative inverse and division operations are only possible for co-prime numbers.

As always, the code from this article is available over on GitHub.

       

Validation Using the Spring Validator Interface

$
0
0

1. Introduction

The Spring Validator interface provides a flexible and customizable way to validate objects. In this tutorial, we’ll explore how to use the Validator interface to validate objects in a Spring-based application.

2. Spring Validator Interface

The Validator interface is a part of the Spring Framework that provides a way to validate objects.

It’s a simple interface that defines two methods, supports() and validate(). These two methods are used to determine if the validator can validate an object and to perform the validation logic. 

2.1. supports(Class<?> clazz)

The supports() method in the Validator interface determines whether the validator can validate instances of a specific class. This method takes in a parameter Class<?> clazz which represents the class of the object being validated. It’s a generic class (Class<?>) to allow flexibility with different object types.

Specifically, Spring utilizes the isAssignableFrom() method to check if an object can be legally cast to an object of the validator’s supported class. Therefore, if the validator can handle objects of the provided clazz, it returns true, otherwise, it returns false to indicate that another validator should be used:

@Override
public boolean supports(Class<?> clazz) {
    return User.class.isAssignableFrom(clazz);
}

In this example, the validator is configured only to support validating objects of type User or its subclasses. The method isAssignableFrom() verifies compatibility through inheritance – it returns true for the User and its subclasses, and false for any other class type.

2.2. validate(Object target, Errors errors)

On the other hand, the validate() method plays a crucial role in Spring’s validation framework. It’s where we define the custom validation logic for the objects that the validator supports.

This method receives two key parameters:

  • Object target: This parameter represents the actual object to be validated. Spring MVC automatically passes the object we’re trying to validate to this method.
  • Errors: This parameter is an instance of the Errors interface. It provides various methods for adding validation errors to the object.

Here’s an example of a validate() method:

@Override
public void validate(Object target, Errors errors) {
    User user = (User) target;
    if (StringUtils.isEmpty(user.getName())) { 
        errors.rejectValue("name", "name.required", "Name cannot be empty"); 
    }
}

In this example, the validate() method performs various validations on the User object and adds specific error messages to the Errors object using rejectValue() for targeted field errors. Notably, rejectValue() takes three main parameters:

  • field: The name of the field that has an error, e.g., “name
  • errorCode: A unique code that identifies the error, e.g., “name.required
  • defaultMessage: A default error message that displays if no other message is found, e.g., “Name cannot be empty

3. Implementing a Validator

To create a validator, we need to implement the Validator interface. Here is an example of a simple validator that validates a User object:

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;
        if (StringUtils.isEmpty(user.getName())) {
            errors.rejectValue("name", "name.required", "Name cannot be empty");
        }
        if (StringUtils.isEmpty(user.getEmail())) {
            errors.rejectValue("email", "email.required", "Invalid email format");
        }
    }
}

3.1. Creating the User Class

Before applying validation, it’s essential to define the structure of the object we intend to validate. Here’s an example of the User class:

public class User {
    private String name;
    private String email;
    // Getters and Setters
}

3.2. Configuring Spring Beans

Next, to integrate the custom validator into a Spring-based application, we can register it as a bean within our application context using a Spring configuration class. This registration ensures that the validator is available for dependency injection throughout the application lifecycle:

@Configuration
public class AppConfig implements WebMvcConfigurer{
    @Bean
    public UserValidator userValidator() {
        return new UserValidator();
    }
}

By annotating the userValidator() method with @Bean, we ensure it returns an object that Spring registers as a bean in the application context.

3.3. Integrating the Validator in a Spring MVC Controller

Once we register the validator, we can use it to validate a User object in a Spring MVC controller.

Next, let’s create a UserController to handle user-related requests:

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserValidator userValidator;
    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User user) {
        Errors errors = new BeanPropertyBindingResult(user, "user");
        userValidator.validate(user, errors);
        if (errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors.getAllErrors());
        }
        // Save the user object to the database
        return ResponseEntity.ok("User created successfully!");
    }
}

In this example, we’re using Spring’s @RestController annotation to indicate that this controller returns JSON responses. Additionally, we use @RequestBody to bind the incoming JSON request body to a User object. If the validation fails, we return a 400 Bad Request response with a JSON body containing the error messages. Otherwise, we return a 200 OK response with a success message.

4. Testing With Curl

To test this API using curl, we can send a JSON request body with the User object data:

curl -X POST \
  http://localhost:8080/api/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"","email":""}'

This should return a 400 Bad Request response with a JSON body containing the error messages:

[
  {
    "codes": [
      "name.required.user.name",
      "name.required.name",
      "name.required.java.lang.String",
      "name.required"
    ],
    "arguments": null,
    "defaultMessage": "Name cannot be empty",
    "objectName": "user",
    "field": "name",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "name.required"
  },
  {
    "codes": [
      "email.required.user.email",
      "email.required.email",
      "email.required.java.lang.String",
      "email.required"
    ],
    "arguments": null,
    "defaultMessage": "Invalid email format",
    "objectName": "user",
    "field": "email",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "email.required"
  }
]

If we send a valid User object with a name and email, the API should return a 200 OK response along with a success message:

curl -X POST \
  http://localhost:8080/api/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"John Doe","email":"johndoe@example.com"}'

As a result, the request returned a response with a success message:

"User created successfully!"

5. Validation Context

In addition, in some cases, we may want to pass additional context to the validator. Spring’s Validator interface supports validation context through the validate(Object target, Errors errors, Object… validationHints) method. Hence, to use validation context, we can pass additional objects as validation hints when calling the validate() method.

For example, we want to validate a User object based on a specific scenario:

public void validate(Object target, Errors errors, Object... validationHints) {
    User user = (User) target;
    if (validationHints.length > 0) {
        if (validationHints[0] == "create") {
            if (StringUtils.isEmpty(user.getName())) {
                errors.rejectValue("name", "name.required", "Name cannot be empty");
            }
            if (StringUtils.isEmpty(user.getEmail())) {
                errors.rejectValue("email", "email.required", "Invalid email format");
            }
        } else if (validationHints[0] == "update") {
            // Perform update-specific validation
            if (StringUtils.isEmpty(user.getName()) && StringUtils.isEmpty(user.getEmail())) {
                errors.rejectValue("name", "name.or.email.required", "Name or email cannot be empty");
            }
        }
    } else {
        // Perform default validation
    }
}

In this example, UserValidator checks the validationHints array to determine which validation scenario to use. Let’s update the UserController to use the UserValidator with validationHints:

@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody User user) {
    Errors errors = new BeanPropertyBindingResult(user, "user");
    userValidator.validate(user, errors, "update");
    if (errors.hasErrors()) {
        return ResponseEntity.badRequest().body(errors.getAllErrors());
    }
    // Update the user object in the database
    return ResponseEntity.ok("User updated successfully!");
}

Now, let’s send the following curl command with both name and email fields empty:

curl -X PUT \
  http://localhost:8080/api/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"","email":""}'

The UserValidator returns a 400 Bad Request response with an error message:

[
  {
    "codes": [
      "name.or.email.required.user.name",
      "name.or.email.required.name",
      "name.or.email.required.java.lang.String",
      "name.or.email.required"
    ],
    "arguments": null,
    "defaultMessage": "Name or email cannot be empty",
    "objectName": "user",
    "field": "name",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "name.or.email.required"
  }
]

If we only pass one of the fields, for example, the name field, then the UserValidator allows the update to proceed:

curl -X PUT \
  http://localhost:8080/api/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"John Doe"}'

The response is a 200 OK response, indicating that the update was successful:

"User updated successfully!"

6. Conclusion

In this article, we’ve learned how to use the Validator interface to validate objects in a Spring-based application. We’ve explored the two methods of the Validator interface, supports() and validate(), and how to implement a custom validator to validate an object.

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

       

How to Select Value From Dropdown Using Selenium Webdriver

$
0
0

1. Introduction

In this short tutorial, we’ll look at a simple example of how to select an option or a value from a dropdown element using Selenium WebDriver with Java.

For testing, we’ll use JUnit and Selenium to open https://www.baeldung.com/contact and select the value “Bug Reporting” from the “What is your question about?” dropdown.

2. Dependencies

First, we add the selenium-java and Junit dependencies to our project in the pom.xml:

<dependency> 
    <groupId>org.seleniumhq.selenium</groupId> 
    <artifactId>selenium-java</artifactId> 
    <version>4.18.1</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version> <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>webdrivermanager</artifactId>
    <version>5.7.0</version>
</dependency>

3. Configuration

Next, we need to configure WebDriver. In this example, we’ll use its Chrome implementation after downloading its latest version:

@BeforeEach
public void setUp() {
    WebDriverManager.chromedriver().setup();
    driver = new ChromeDriver();
}

We’re using a method annotated with @BeforeEach to do the initial setup before each test. Next, we use WebDriverManager to get the Chrome Driver without explicitly downloading and installing it. This makes our code more portable when compared to using the absolute path of the driver. We still need the Chrome browser installed on the target machine where we’ll run this code.

When the test finishes, we should close the browser window. We can do this by placing the driver.close() statement in a method annotated with @AfterEach. This makes sure it’ll be executed even if the test fails:

@AfterEach
public void cleanUp() {
    driver.close();
}

4. Find the Select Element

Now that the scaffolding is done, we must add code to identify the select element. There are a few different ways to help Selenium pick an element such as by using ID, CSS selector, Class name, Xpath, etc.

We add variables for the page URL and the XPath for the select input and option of interest. These will be needed later in our tests:

private static final String URL = "https://www.baeldung.com/contact";
private static final String INPUT_XPATH = "//select[@name='question-recipient']";
private static final String OPTION_XPATH = 
  "//select[@name='question-recipient']/option[@value='Bug reporting']";

We’ll use XPath selectors as they offer a lot of versatility and power in choosing elements matching a variety of options.

In this example, we’re interested in the option “Bug Reporting” under the select element with the attribute name with the value “question-recipient”Using an Xpath selector allows us to select that exact element without ambiguity using a single expression. 

We then add a test case to confirm that the option of our interest exists. In this case, the option is the one with the text “Bug Reporting”:

@Test
public void givenBaeldungContactPage_whenSelectQuestion_thenContainsOptionBugs() {
    driver.get(URL);
    WebElement inputElement = driver.findElement(By.xpath(OPTION_XPATH));
    assertEquals("Bug reporting", inputElement.getText());
}

Here, we use the driver.get() method to load the webpage. Next, we find the option element and verify its text matches our expected value to ensure we’re in the right place. Next, we’ll write some tests to click on that particular option.

5. Select an Option in the Select Element

We’ve identified the option of our interest. Selenium offers a separate class called Select under the package org.openqa.selenium.support.ui.Select to help work with HTML select dropdowns. We’ll write a few tests to use the different ways to click the option of our interest.

5.1. Select By Value

Here we use the selectByValue() method in the Select class to select an option based on the value it represents:

@Test
public void givenBaeldungContactPage_whenSelectQuestion_thenSelectOptionBugsByValue() {
    driver.get(URL);
    WebElement inputElement = driver.findElement(By.xpath(INPUT_XPATH));
    WebElement optionBug = driver.findElement(By.xpath(OPTION_XPATH));
    Select select = new Select(inputElement);
    select.selectByValue(OPTION_TEXT);
    assertTrue(optionBug.isSelected());
}

5.2. Select by Option Text

Here we use the selectByVisibleText() method in the Select class to select the same option, this time using the text that is visible to us as an end user:

@Test
public void givenBaeldungContactPage_whenSelectQuestion_thenSelectOptionBugsByText() {
    driver.get(URL);
    WebElement inputElement = driver.findElement(By.xpath(INPUT_XPATH));
    WebElement optionBug = driver.findElement(By.xpath(OPTION_XPATH));
    select.selectByVisibleText(OPTION_TEXT);    
    assertTrue(optionBug.isSelected());
}

5.3. Select by Index

The last way to consider this is to use the index of the option to select.  The option of interest is listed as the fifth option in the dropdown. We note that the index in this library is based on zero as the first index so we’re interested in index 4. We use the selectByIndex() method to select the same option:

@Test
public void givenBaeldungContactPage_whenSelectQuestion_thenSelectOptionBugsByIndex() {
    driver.get(URL);
    WebElement inputElement = driver.findElement(By.xpath(INPUT_XPATH));
    WebElement optionBug = driver.findElement(By.xpath(OPTION_XPATH));
    select.selectByIndex(OPTION_INDEX);    
    assertTrue(optionBug.isSelected());
}

6. Conclusion

In this article, we’ve learned how to select a value from the select element using Selenium.

We examined a few different ways to choose the Option of our interest. Selenium offers a special support class called Select for this purpose. The methods of interest are selectByValue(), selectByVisibleText(), and selectByIndex().

The general flow is to use Selenium selectors to identify the dropdown of interest and then use one of the methods on the Select class to click the Option desired.

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

       

Differences Between Solr and Lucene

$
0
0

1. Introduction

Search engines are pivotal in retrieving information quickly and efficiently in the digital age. Two prominent names in this domain are Apache Solr and Apache Lucene. These technologies are essential tools for developers building robust search functionalities.

In this tutorial, we’ll compare and contrast Solr and Lucene, highlighting their differences and understanding their unique strengths and applications.

2. What is Lucene?

Apache Lucene is a high-performance, full-featured text search engine library. Doug Cutting created it in 1999, later becoming an Apache Software Foundation project.

Lucene provides powerful indexing and searching capabilities. We use it widely in many software applications to add search functionality. It excels at full-text indexing and search, offering features such as powerful query syntax, relevance scoring, and various text analysis techniques.

3. What is Solr?

Apache Solr is an open-source search platform built on top of Lucene. The Apache Software Foundation also developed Solr, releasing it in 2004 to provide a more complete and user-friendly search solution. Solr extends Lucene’s capabilities by adding features like faceted search, highlighting, and spell-checking.

It also includes an HTTP-based API, making it easier to integrate with web applications. Moreover, Solr is designed to handle large-scale search applications, providing distributed search and indexing capabilities.

4. Core Components of Solr and Lucene

Lucene is a library that provides the core components needed for indexing and searching text. Key components include the IndexWriter, which handles creating and updating the index, and the IndexReader, which allows searching the index. Moreover, these components collaborate to ensure efficient index management and retrieval.

Lucene also includes Analyzers for text analysis, DocumentObjects to represent indexed content, and QueryParser for parsing search queries. Lucene’s architecture is designed to be highly flexible, allowing us to customize nearly every aspect of indexing and searching.

Solr builds on Lucene’s foundation and adds additional layers of functionality. At the heart of Solr is the SolrCore, which manages individual indexes. Furthermore, Solr uses a schema to define the structure of the indexed data and a solrconfig.xml file to configure various aspects of the search process.

Additionally, Solr extends Lucene’s capabilities with features like faceting, result highlighting, and advanced query handling. Its architecture includes a web-based administration interface and a REST-like HTTP API, making it accessible and easy to manage.

5. Key Differences

Let’s understand some of the key differences between Solr and Lucene.

5.1. Scope and Use Cases

Lucene is a library, meaning it’s a set of components we can use to add search functionality to an application. Furthermore, it’s embedded directly into applications, making it ideal for developers who need fine-grained control over search behavior.

Solr, on the other hand, is a standalone server that provides a complete search platform out of the box. It’s suited for enterprise search applications where ease of use and scalability are crucial.

5.2. Features and Functionalities

Solr offers many out-of-the-box features that Lucene doesn’t. These include faceting, which helps categorize search results, and highlighting, which shows search results snippets with query terms highlighted. Solr also provides spell-checking, and auto-suggest, along with other basic features.

While Lucene offers these capabilities as well, it requires additional implementation effort. Solr’s configuration and extensibility are user-friendly thanks to its XML-based schema and configuration files.

5.3. Performance and Scalability

Both Solr and Lucene can handle large datasets efficiently by design. However, Solr provides built-in features for distributed search and indexing, making it more suitable for large-scale applications.

SolrCloud, a cluster of Solar instances, supports distributed indexing and searching across multiple servers, ensuring high availability and fault tolerance. We can also scale Lucene, but implementing distributed search capabilities requires more effort.

5.4. Ease of Use and Integration

Solr is generally easier to use and integrate, especially for web applications. Its HTTP-based API allows us to interact with Solr using simple HTTP requests, making integration straightforward. Solr also includes a web-based admin interface for managing indexes and configurations.

Lucene, being a library, requires more effort to integrate into applications. It provides greater flexibility but comes with a steeper learning curve.

6. Pros and Cons

Solr provides a rich set of out-of-the-box features, making it easier and faster to deploy a search solution. Its scalability and ease of use are major advantages. However, it might be an overkill for simple search applications, and its additional features may come at the cost of higher resource usage.

Lucene offers powerful search capabilities with high flexibility and control. However, it requires significant effort to implement and integrate. Its learning curve can be steep, and building a complete search solution from scratch can be time-consuming.

7. When to Use Solr and Lucene

Let’s understand when to use Solr and when to use Lucene, including some of their respective use cases:

Criteria Lucene Solr
Embedded Search Functionality Ideal for applications needing embedded search functionality without the overhead of a separate server. Not applicable; Solr is a standalone server.
Fine-Grained Control Offers detailed control over indexing and searching, allowing extensive customization for specific needs. Solr provides less flexibility than Lucene but is sufficient for most enterprise applications.
Minimal Overhead Suitable for lightweight applications with resource constraints, as it has a smaller footprint. Solr requires running a separate server, which adds to the overhead.
Learning and Experimentation Excellent for learning about search technologies and experimenting with different indexing and searching techniques. Solr is suitable for practical, real-world applications but less ideal for deep experimentation.
Enterprise Search Applications Lucene isn’t typically used for large-scale, high-volume search applications. It handles high query volumes and vast datasets, designed for large-scale enterprise search applications.
Out-of-the-Box Features Requires additional implementation for advanced features like faceting, highlighting, and spell-checking. Solr offers numerous out-of-the-box features, such as faceting, highlighting, and spell-checking.
Ease of Integration and Use Lucene requires more effort to integrate into applications, with a steeper learning curve. Integrating and managing with HTTP-based API and web-based admin interface is straightforward.
Distributed Search and High Availability Significant effort is required to implement distributed search capabilities. SolrCloud provides built-in support for distributed search and high availability, ensuring fault tolerance.
Desktop Search Applications Lucene is suitable for desktop applications that need embedded search capabilities (e.g., document management systems and local file search utilities). We don’t typically use Solr for desktop applications.
Custom Search Solutions Ideal for developing custom search solutions with unique requirements, offering extensive customization options. Solr suits standard enterprise applications better, but we can also extend it to custom solutions.
Educational Projects Excellent for educational purposes, providing a deeper understanding of search engine internals for students and researchers. Suitable for practical implementations but less ideal for academic exploration of search engine mechanics.
E-Commerce Websites We can utilize it with some customization to effectively emulate Solr’s default features. E-commerce websites widely use Solr, as it provides fast and accurate product search capabilities, faceting for product categorization, and enhances user experience.
Content Management Systems (CMS) Lucene can be integrated but requires more effort than Solr. CMS platforms integrate Solr to enable efficient content search and retrieval, with features like highlighting to improve search term visibility.
Log and Event Data Analysis Handles large volumes of data but requires custom implementation for distributed search and real-time analysis. Solr indexes and searches large volumes of log and event data, offering built-in distributed search capabilities for real-time data analysis from multiple sources.

This table provides a detailed comparison of when to use Solr versus Lucene, including specific use cases for each one.

8. Conclusion

In this article, we looked at the key differences between Solr and Lucene. While both offer powerful search capabilities, they cater to different needs.

Lucene is ideal for developers needing a flexible, library-based solution with granular control. With its additional features and ease of use, Solr suits large-scale enterprise search applications better.

When choosing between Solr and Lucene, we should consider our specific needs, the complexity of the search functionality required, and the resources available for implementation.

       

Checking if an Array Is Null or Empty in Java

$
0
0
Contact Us Featured

1. Overview

In Java, manipulating arrays is a common task, which often involves checking if an array is null or empty. This is crucial for preventing NullPointerExceptions and ensuring our code behaves correctly when dealing with array data structures.

In this tutorial, we’ll explore how to perform these checks effectively.

2. Preparing Examples

Let’s first prepare some example arrays to quickly demonstrate how to check whether an array is empty or null:

final static String[] STR_ARRAY = new String[] { "a", "b", "c", "d" };
final static BigDecimal[] EMPTY_ARRAY = new BigDecimal[] {};
final static String[] NULL_ARRAY = null;

As the code above shows, we have created three arrays: a regular String array with four elements, an empty BigDecimal array, and a String array referencing null.

Next, we’ll address different ways to examine if these three arrays are null or empty.

3. Creating a Check Method

In Java, we can check if an array is null or empty by performing two simple checks:

  • null check – using == null
  • Empty check – checking the length property of the array to see if it has zero elements

Of course, we want our method to work for all array types. The first idea is to create a generic check method:

static <T> boolean isArrayNullOrEmpty(T[] theArray) {
    return theArray == null || theArray.length == 0;
}

We perform both the null and empty checks. Now, if we pass our example arrays to the method, it achieves the expected results:

assertTrue(isArrayNullOrEmpty(NULL_ARRAY));
assertTrue(isArrayNullOrEmpty(EMPTY_ARRAY));
assertFalse(isArrayNullOrEmpty(STR_ARRAY));

However, we still haven’t discussed one case: primitive type arrays, such as int[], double[], and so on.

So next, let’s take a closer look at primitive type arrays.

4. Handling Primitive Type Arrays

Let’s first create an int[] array as an example:

final static int[] INT_ARRAY = new int[] { 1, 2, 3, 4 };

This array isn’t null or empty. Now, let’s test our generic method using this array and see if we can get the correct result:

assertFalse(isArrayNullOrEmpty(INT_ARRAY));

When we run the test, we have a compiler error:

java: method isArrayNullOrEmpty in class ... cannot be applied to given types;
  required: T[]
  found:    int[]
  reason: inference variable T has incompatible bounds 

This is because Java generics are implemented using “type erasure” for backward compatibility. All generic types are converted to Object at runtime. However, primitive types don’t inherit from Object. Therefore, generics don’t work with primitive types.

So, to make our method support all primitive type arrays, we can overload the method and use the same implementation but accept different primitive types as the parameter. 

Let’s take the generic isArrayNullOrEmpty() method as an example to see how to do it:

static boolean isArrayNullOrEmpty(int[] theArray) {
    return theArray == null || theArray.length == 0;
}
static boolean isArrayNullOrEmpty(short[] theArray) { /* the same implementation */ }
static boolean isArrayNullOrEmpty(long[] theArray) { /* the same implementation */ }
// ... same for float, double, byte, boolean primitive type arrays

Now, if we perform the check for primitive type arrays:

assertFalse(isArrayNullOrEmpty(INT_ARRAY));

The code compiles and the test passes.

5. Using the ArrayUtils.isEmpty() Method

Apache Commons Lang 3 is a widely used library. Its ArrayUtils class provides a rich set of utilities, making array manipulation easier. The class offers an isEmpty() method to check whether an array is null or empty.

To use the ArrayUtils class, we must first add the Apache Commons Lang 3 dependency to our pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

Then, we can use the isEmpty() method in our code:

assertTrue(ArrayUtils.isEmpty(NULL_ARRAY));
assertTrue(ArrayUtils.isEmpty(EMPTY_ARRAY));
assertFalse(ArrayUtils.isEmpty(STR_ARRAY));
//primitive array
assertFalse(ArrayUtils.isEmpty(INT_ARRAY));

It’s worth noting that ArrayUtils also defines a set of isEmpty() methods, one takes Object[] as the parameter, and the rest accept parameters in each primitive type:

public static boolean isEmpty(final Object[] array)
public static boolean isEmpty(final short[] array)
public static boolean isEmpty(final int[] array)
public static boolean isEmpty(final long[] array)
// ... other primitive types

Therefore, ArrayUtils.isEmpty() can check our int[] array without error.

6. Using the ObjectUtils.isEmpty() Method

ObjectUtils from Apache Commons Lang 3 aims to handle null objects gracefully. When we pass an array to the ObjectUtils.isEmpty() method, it can also report if the array is null or empty:

assertTrue(ObjectUtils.isEmpty(NULL_ARRAY));
assertTrue(ObjectUtils.isEmpty(EMPTY_ARRAY));
assertFalse(ObjectUtils.isEmpty(STR_ARRAY));
//primitive array
assertFalse(ObjectUtils.isEmpty(INT_ARRAY));

As the test shows, it works for object and primitive type arrays. Unlike ArrayUtils.isEmpty(), ObjectUtils doesn’t have isEmpty() methods for each primitive type. Instead, isEmpty() accepts an Object as the parameter and checks if it’s an array:

public static boolean isEmpty(final Object object) {
    if (object == null) {
        return true;
    }
  
    if (isArray(object)) {
        return Array.getLength(object) == 0;
    }
    // ... unrelated code omitted
    return false;
}
public static boolean isArray(final Object object) {
    return object != null && object.getClass().isArray();
}

Since primitiveArray.getClass().isArray() is true, isEmpty() works for primitive type arrays.

7. Conclusion

Checking if an array is null or empty is a fundamental task in Java programming. In this article, we’ve explored how to implement these checks for both object and primitive type arrays, creating a robust utility method that can be reused in our applications. Additionally, we’ve demonstrated how to use the convenient ArrayUtils.isEmpty() and ObjectUtils.isEmpty() to do the job.

Following these practices, we can write cleaner, safer, and more reliable Java code.

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

       

Fix ClassCastException: java.math.BigInteger cannot be cast to java.lang.Integer

$
0
0

1. Overview

Java is renowned for its robustness and versatility as a programming language, yet it also presents its fair share of challenges, much like any other language. One prevalent issue that developers frequently confront is the ClassCastException. This exception occurs when attempting to cast an object from one type to another that is not compatible.

In this article, we’ll address the ClassCastException: java.math.BigInteger cannot be cast to java.lang.Integer. This exception is commonly encountered when converting from BigInteger to Integer.

2. Why the ClassCastException Exception Occurs

The BigInteger class in Java facilitates operations involving large integers beyond the range of int and long. Conversely, Integer serves as a wrapper class for the primitive type int. Because these classes serve different purposes and have different internal representations, we cannot directly cast an instance of BigInteger to Integer.

Let’s consider the following code snippet:

Object object = new BigInteger("123");
Integer number = (Integer) obj; // This will throw ClassCastException

In the above example, the ClassCastException exception occurred when attempting to cast a BigInteger to an Integer. It’s crucial to note the importance of verifying the object’s type at runtime before casting to avoid unexpected type conversions and prevent such runtime exceptions.

In the next chapter, we’ll discuss the best practices for avoiding such exceptions.

3. How to Avoid BigInteger to Integer Casting Issues

To avoid the ClassCastException exception, we need to use the right methods and check types for conversions. When working with BigInteger values, we can use the methods provided by the BigInteger class, such as intValue(), to convert the value to an int.

However, we should always check the type of the object before casting it. Additionally, we need to be aware of the potential risk of overflow if the BigInteger value exceeds the range of int (i.e., -2^31 to 2^31-1). If the value is too large, it will wrap around and produce an incorrect result. To mitigate this, we should check if the BigInteger value fits within the int range before converting.

For example, if we have an object that might be a BigInteger or an Integer, we use this method that demonstrates this approach:

int convertIntegerToInt(Object obj) {
    if (obj instanceof BigInteger) {
        BigInteger bigInt = (BigInteger) obj;
        if (bigInt.bitLength() <= 31) {
            return bigInt.intValue();
        } else {
            throw new IllegalArgumentException("BigInteger value is too large to fit in an int");
        }
    } else if (obj instanceof Integer) {
        return (Integer) obj;
    } else {
        throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName());
    }
}

In the convertToInt() method, we first check the object’s type to determine if it is an instance of BigInteger using the instanceof operator. If it meets this condition, we then cast it to BigInteger. Following this, the method verifies whether its bit length falls within the range that an int can represent (i.e., 31 bits or less).

If this condition is fulfilled, we convert the value to an int using intValue(). On the other hand, if the bit length exceeds the acceptable range, we throw an IllegalArgumentException exception.

If the object is already an Integer, we directly cast it to an Integer and print its value. However, if the object’s type does not match either BigInteger or Integer, we throw an IllegalArgumentException.

This comprehensive approach ensures safe conversion and prevents the ClassCastException by explicitly handling type compatibility.

4. Conclusion

We often encounter the ClassCastException in Java, particularly when trying to cast java.math.BigInteger to java.lang.Integer. To resolve this issue, we need to use appropriate conversion methods provided by the classes, conduct thorough type checks before casting, and potentially restructure our code to mitigate such type mismatches.

By understanding the underlying cause and implementing these solutions, we can proficiently manage and prevent this exception in our Java applications

As always, the complete code samples for this article are available over on GitHub.

       

Intro to SpotBugs

$
0
0

1. Overview

Identifying bugs in Java programs is a critical challenge in software development. SpotBugs is an open-source static analysis tool used to find bugs in Java code. It operates on Java bytecode, rather than source code to identify potential issues in code, such as bugs, performance issues, or bad practices. SpotBugs is the successor to FindBugs and builds upon its capabilities, offering more detailed and precise bug detection. In this article, well explore how to set up SpotBugs on a Java project and integrate it into the IDE and the Maven build.

2. Bug Patterns

SpotBugs checks more than 400 bug patterns such as null pointer dereferencing, infinite recursive loops, bad uses of the Java libraries, and deadlocks. In SpotBugs, bug patterns are classified by means of several variables, such as the type of violation, its category, the rank of the bug, and the confidence of the discovering process. SpotBugs has ten bug patterns categories:

  • Bad Practice: Detects bad coding practices that might not cause immediate problems but could lead to issues in the future. Examples include hash code and equals problems, cloneable idiom, dropped exceptions, Serializable problems, and misuse of finalize.
  • Correctness: Identifies code that is likely to be incorrect and might lead to runtime errors such as a probable bug.
  • Experimental: Refers to bug patterns that are relatively new, experimental, or not fully validated.
  • Internationalization: Detects potential issues in Java code related to internationalization and locale.
  • Malicious Code Vulnerability: Flags code that could be exploited by an attacker.
  • Multithreaded Correctness: Checks for potential issues in multi-threaded code, such as race conditions and deadlocks.
  • Bogus Random Noise: Intended to be useful as a control in data mining experiments, not in finding actual bugs in software.
  • Performance: Identifies code that isn’t necessarily incorrect but may be inefficient.
  • Security: Highlights security vulnerabilities in the code.
  • Dodgy Code: Looks for code that isn’t necessarily incorrect but suspicious and potentially problematic. Code that is confusing, anomalous, or written in a way that leads itself to errors. Examples include dead local stores, switch fall through, unconfirmed casts, and redundant null check of value known to be null.

One of the features of SpotBugs is the ability to categorize bugs into different severity ranks. SpotBugs ranks bugs on a numeric scale from 1 to 20, with the ranking indicating the severity of the issue. The numeric rankings can be categorized as follows:

  • Scariest (High Priority): Ranks 1 to 4
  • Scary (Medium Priority): Ranks 5 to 9
  • Troubling (Low Priority): Ranks 10 to 14
  • Of Concern (Informational): Ranks 15 to 20

3. SpotBugs Maven Plugin

Spotbugs can be used as a standalone application, as well as through several integrations including Maven, Gradle, Eclipse, and IntelliJ. In this section, we’re focusing on Maven integration.

3.1. Maven Configuration

Let’s start by importing the spotbugs-maven-plugin plugin in the <plugins> section of the pom.xml:

<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.8.5.0</version>
    <dependencies>
        <dependency>
	    <groupId>com.github.spotbugs</groupId>
            <artifactId>spotbugs</artifactId>
	    <version>4.8.5</version>
        </dependency>
    </dependencies>
</plugin>

3.2. Generating the Report

Once the plugin has been added, we can open a terminal and run the following command:

mvn spotbugs:check

This runs an analysis of our source code and then outputs a list of warnings that we need to fix. To generate a bug report we need some code to work with. To keep things simple, we’ll be using the project available on GitHub. Let’s assume that we’re using the following class:

public class Application {
    public static final String NAME = "Name: ";
    private Application() {
    }
    public static String readName() {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();
        return NAME.concat(input);
    }
}

Now, we can run the mvn spotbugs:check command. The following was detected when it was run at one point:

[INFO] BugInstance size is 1
[INFO] Error size is 0
[INFO] Total bugs: 1
[ERROR] High: Found reliance on default encoding in com.baeldung.systemin.Application.readName(): new java.util.Scanner(InputStream) [com.baeldung.systemin.Application] At Application.java:[line 13] DM_DEFAULT_ENCODING

From this report, we can see that our Application class has one bug with high priority.

3.3. Viewing the Result

SpotBugs generates a report in an XML format in target/spotbugsXml.xml. To have a prettier report, we need to add htmlOutput configuration in the SpotBugs plugin:

<configuration>
    <htmlOutput>true</htmlOutput>
</configuration>

Now, we run the mvn clean install and mvn spotbugs:check commands. Then, we navigate to the target/spotbugs.html and open the file to see the results directly in a browser:   Also, we can see bug details using the Spotbugs GUI using the Maven command mvn spotbugs:gui:   With this feedback, we can now update our code to fix the bug proactively.

3.4. Fixing Bugs

Our Application class has the DM_DEFAULT_ENCODING bug. The bug indicates the use of the default character encoding when performing I/O operations. This can lead to unexpected behavior if the default encoding varies across different environments or platforms. By explicitly specifying the character encoding, we ensure consistent behavior regardless of the platform’s default encoding. To fix this, we can add StandardCharsets to the Scanner class:

public static String readName() {
    Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8.displayName());
    String input = scanner.next();
    return NAME.concat(input);
}

After adding the previously suggested change, running mvn spotbugs:check again produces a bugs-free report:

[INFO] BugInstance size is 0
[INFO] Error size is 0
[INFO] No errors/warnings found

4. SpotBugs IntelliJ IDEA Plugin

IntelliJ SpotBugs plugin provides static byte code analysis to look for bugs in Java code from within IntelliJ IDEA. The plugin uses SpotBugs under the hood.

4.1. Installation

To install the SpotBugs plugin in IntelliJ IDEA, we open the application and navigate to “Welcome Screen“. If a project is open, we go to “File -> Settings” (or “IntelliJ IDEA -> Preferences” on macOS). In the settings window, we select “Plugins” and then go to the “Marketplace” tab. Using the search bar, we find “SpotBugs” and, once located, click the “Install” button. After the installation completes, we restart IntelliJ IDEA to activate the plugin:   For a manual installation of the SpotBugs plugin, we first download the plugin from the JetBrains Plugin Repository. After downloading the plugin zip file, we open IntelliJ IDEA and navigate to the “Welcome Screen“, then go to “File > Settings“. In the settings window, we select “Plugins“, click on the “Gear Icon” in the top right corner, and choose “Install Plugin from Disk“. We then navigate to the location where we downloaded the SpotBugs plugin zip file, select it, and click “OK“. Finally, we restart IntelliJ IDEA to activate the plugin.

4.2. Reports Browsing

In order to launch static analysis in IDEA, we can click on “Analyze Current File” in the SpotBugs-IDEA panel and then inspect the results:   We can use the second column of commands on the left side of the screenshot to group bugs by various factors, such as bug category, class, package, priority, or bug rank. It’s also possible to export the reports in XML/HTML format, by clicking the “export” button in the third column of commands.

4.3. Configuration

The SpotBugs plugin for IntelliJ IDEA offers various preferences and settings that allow users to customize the analysis process and manage how bugs are reported. The below figure shows the SpotBugs plugin preferences:   Here’s an overview of the typical preferences available in the SpotBugs plugin:

  • Bug Categories and Priorities: We can configure which categories of bugs we want SpotBugs to detect. We can also set the minimum rank for bug detection, allowing us to focus on more critical issues.
  • Filter Files: This tab allows us to manage which parts of the codebase are included or excluded from analysis. We can define patterns to exclude or include specific packages, classes, or methods from the analysis.
  • Annotations: The plugin supports configuring annotations for classes, methods, and fields. These annotations can be used to suppress specific warnings or to provide additional information to SpotBugs.
  • Detector: It provides settings related to the detection mechanisms used by SpotBugs. We can enable or disable specific bug detectors based on our needs. For example, one can focus on security issues by enabling security-related detectors and disabling others.

These preferences and settings provide flexibility and control over how SpotBugs analyzes the code and reports potential issues, helping developers maintain high code quality and address bugs effectively.

5. SpotBugs Eclipse Plugin

In this section, we focus on the SpotBugs Eclipse Plugin, delving into its installation process, and usage within the Eclipse IDE.

5.1. Installation

We begin by launching Eclipse IDE and navigating to the “Help” in the menu bar. After that, we select “Eclipse Marketplace“. In the Marketplace window, we type “SpotBugs” into the search box and press “Enter“. When the search results appear, we find SpotBugs and click on the “Install” button next to it. We follow the installation wizard to complete the installation process. Finally, we restart Eclipse to activate the plugin:   Alternatively, if SpotBugs isn’t available in “Eclipse Marketplace” or if we prefer to install it via the update site, we can open Eclipse and go to the “Help” menu at the top. From the dropdown menu, we select “Install New Software” and then click on the “Add” button in the dialog that appears. In the “Add Repository” dialog, we enter a name for the repository, such as “SpotBugs“, and input the https://spotbugs.github.io/eclipse URL in the “Location” field. After clicking “OK“, we check the box next to “SpotBugs” in the list of available software, click “Next“, and follow the prompts to complete the installation. Finally, we restart Eclipse to activate the plugin.

5.2. Reports Browsing

To launch a static analysis on a project using the SpotBugs Eclipse plugin, we need to right-click the project in the “Project Explorer“, and then navigate to “SpotBugs -> Find Bugs“. After that, Eclipse shows the results under the “Bug Explorer” window.

5.3. Configuration

We can check the configuration interface by going to “Window -> Preferences -> Java -> SpotBugs“:   We can freely uncheck unwanted categories, raise the minimum rank to report, specify the minimum confidence to report, and customize markers for bug ranks. Under the “Detector configuration” tab, the Eclipse SpotBugs plugin allows us to customize which bug detectors are enabled or disabled. This tab provides a detailed list of available detectors, each designed to identify specific types of potential issues in the code. Under the “Filter files” panel, we can create custom file filters, allowing us to include or exclude specific files or directories from the SpotBugs analysis. This feature enables fine-tuned control over which parts of the codebase are analyzed, ensuring that irrelevant or non-essential files aren’t included in the bug detection process.

6. Conclusion

In this article, we’ve discussed the basic key points to using SpotBugs in a Java project. SpotBugs is a static analysis tool that helps identify potential bugs in our code by examining bytecode. We learned how to configure SpotBugs with Maven, a widely used build automation tool in the Java ecosystem, to automate the detection of issues during the build process. Additionally, we walked through the process of integrating SpotBugs into popular development environments, including IntelliJ IDEA and Eclipse, ensuring that we can easily incorporate bug detection into our workflow. As always, the full source code is available over on GitHub.

       

Fixing Spring Boot H2 Exception: “Schema not found”

$
0
0

1. Overview

H2 is an open-source SQL database often used for testing purposes in the Java community. Its in-memory nature doesn’t persist anything to the disk and that makes it very fast.

We may encounter an error message “Schema not found” when we integrate it with Spring Boot. In this tutorial, we’ll explore the cause of it and look into two different approaches to resolve it.

2. Understanding the Cause

The default schema of H2 is PUBLIC. If we map a JPA entity class not using the PUBLIC schema, we must ensure the schema is created on H2. Spring Boot reports an error message “Schema not found” when the target schema doesn’t exist.

To mimic the scenario, let’s create the following entity class and repository in a Spring Boot application. The @Table annotation specifies the table mapping details that the entity maps to the student table within the test schema:

@Entity
@Table(name = "student", schema = "test")
public class Student {
    @Id
    @Column(name = "student_id", length = 10)
    private String studentId;
    @Column(name = "name", length = 100)
    private String name;
    // constructor, getters and setters
}
public interface StudentRepository extends JpaRepository<Student, String> {
}

Next, we start the Spring Boot application and access the repository. We’ll encounter an exception thrown indicating the schema doesn’t exist. We can verify this with an integration test:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SampleSchemaApplication.class)
class SampleSchemaApplicationIntegrationTest {
    @Autowired
    private StudentRepository studentRepository;
    @Test
    void whenSaveStudent_thenThrowsException() {
        Student student = Student.builder()
          .studentId("24567433")
          .name("David Lloyds")
          .build();
        assertThatThrownBy(() -> studentRepository.save(student))
          .isInstanceOf(InvalidDataAccessResourceUsageException.class);
    }
}

We’ll see the following error message in the console upon the test execution:

org.hibernate.exception.SQLGrammarException: could not prepare statement [Schema "TEST" not found; SQL statement:
select s1_0.student_id,s1_0.name from test.student s1_0 where s1_0.student_id=? [90079-214]] [select s1_0.student_id,s1_0.name from test.student s1_0 where s1_0.student_id=?]

3. Schema Creation via Database URL

To tackle this issue, we must create the corresponding schema when the Spring Boot application starts. There are two different ways to do it.

The first approach is to create the database schema when we establish the database connection. The H2 database URL allows us to execute DDL or DML commands when a client connects to the database via the INIT property. In a Spring Boot application, we could define the spring.datasource.url properties in the application.yaml:

spring:
  jpa:
    hibernate:
      ddl-auto: create
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:test;INIT=CREATE SCHEMA IF NOT EXISTS test

The initialization DDL creates the schema if it doesn’t exist. Notably, this approach is a dedicated approach for the H2 database, and may not work on other databases.

This approach creates the schema via the database URL without explicitly creating the tables. We rely on automatic schema creation in Hibernate by setting the properties spring.jpa.hibernate.ddl-auto to create in the YAML file.

4. Schema Creation via Initialization Script

The second approach is generic and can also apply to other databases. We create all database components including the schema and tables by an initialization script.

Spring Boot initializes the JPA persistence unit before executing the initialization scripts. Thus, we explicitly disable the automatic schema generation of Hibernate in the application.yaml as our initialization script takes care of it:

spring:
  jpa:
    hibernate:
      ddl-auto: none

If we don’t disable it, we encounter the exception of “Schema TEST not found” during the application startup. The schema hasn’t been created yet during the JPA persistence unit initialization.

Now, we can put the schema.sql that creates the test schema and the student table in the resources folder:

CREATE SCHEMA IF NOT EXISTS test;
CREATE TABLE test.student (
  student_id VARCHAR(10) PRIMARY KEY,
  name VARCHAR(100)
);

Spring Boot looks for the DDL script schema.sql in the resources folder to initialize the database during the application startup by default.

5. Conclusion

A “Schema not found” exception is a common issue during Spring Boot application startup integrating with the H2 database. We can avoid these exceptions by ensuring the schema is created through database URL configurations or initialization scripts.

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

       

Reducing Duplication With Spock’s Helper Methods

$
0
0

1. Introduction

When writing tests we often assert comparisons on various attributes of an object. Spock has some useful language features that help us eliminate duplication when we compare objects.

In this tutorial, we’ll learn how to refactor our tests using Spock’s Helper Methods, with() and verifyAll(), to make our tests more readable.

2. Setup

First, let’s create an Account class with a name, a current balance, and an overdraft limit:

public class Account {
    private String accountName;
    private BigDecimal currentBalance;
    private long overdraftLimit;
    // getters and setters
}

3. Our Basic Test

Now let’s create an AccountTest Specification with a test that verifies our getters and setters for account name, current balance, and overdraft limit. We’ll declare the Account as our @Subject and create a new account for each test:

class AccountTest extends Specification {
    @Subject
    Account account = new Account()
    def "given an account when we set its attributes then we get the same values back"() {
        when: "we set attributes on our account"
        account.setAccountName("My Account")
        account.setCurrentBalance(BigDecimal.TEN)
        account.setOverdraftLimit(0)
        then: "the values we retrieve match the ones that we set"
        account.getAccountName() == "My Account"
        account.getCurrentBalance() == BigDecimal.TEN
        account.getOverdraftLimit() == 0
    }
}

Here, we’ve repeatedly referenced our account object in our expectations. The more attributes we have to compare, the more repetition we need.

4. Refactoring Options

Let’s refactor our code to remove some of this duplication.

4.1. Assertion Traps

Our initial attempt might extract the getter comparisons into a separate method:

void verifyAccount(Account accountToVerify) {
    accountToVerify.getAccountName() == "My Account"
    accountToVerify.getCurrentBalance() == BigDecimal.TEN
    accountToVerify.getOverdraftLimit() == 0
}

But, there’s a problem with this. Let’s see what it is by creating a verifyAccountRefactoringTrap method to compare our account but use the values from someone else’s account:

void verifyAccountRefactoringTrap(Account accountToVerify) {
    accountToVerify.getAccountName() == "Someone else's account"
    accountToVerify.getCurrentBalance() == BigDecimal.ZERO
    accountToVerify.getOverdraftLimit() == 9999
}

Now, let’s invoke our method in our then block:

then: "the values we retrieve match the ones that we set"
verifyAccountRefactoringTrap(account)

When we run our test, it passes even though the values don’t match! So, what’s going on?

Although the code looked like it was comparing the values, our verifyAccountRefactoringTrap method contains boolean expressions but no assertions!

Spock’s implicit assertions only occur when we use them in the test method, not from inside called methods.

So, how do we fix it?

4.2. Assert Inside Methods

When we move our comparisons into a separate method, Spock can no longer enforce them automatically, so we must add the assert keyword ourselves.

So, let’s create a verifyAccountAsserted method that asserts our original account values:

void verifyAccountAsserted(Account accountToVerify) {
    assert accountToVerify.getAccountName() == "My Account"
    assert accountToVerify.getCurrentBalance() == BigDecimal.TEN
    assert accountToVerify.getOverdraftLimit() == 0
}

And let’s call our verifyAccountAsserted method in our then block:

then: "the values we retrieve match the ones that we set"
verifyAccountAsserted(account)

When we run our test it still passes and when we change one of the asserted values it will fail just like we expect.

4.3. Return a Boolean

Another way we can ensure our method is treated as an assertion is to return a boolean result. Let’s combine our assertions with the boolean and operator, knowing that Groovy will return the result of the last executed statement in the method:

boolean matchesAccount(Account accountToVerify) {
    accountToVerify.getAccountName() == "My Account"
      && accountToVerify.getCurrentBalance() == BigDecimal.TEN
      && accountToVerify.getOverdraftLimit() == 0
}

Our test passes when all conditions match and fails when one or more don’t, but there’s a drawback. When our test fails, we won’t know which of our three conditions wasn’t met.

Although we can use these approaches to refactor our test, Spock’s helper methods give us a better approach.

5. Helper Methods

Spock provides two helper methods “with” and “verifyAll” that help us solve our problem more elegantly! Both work in much the same way, so let’s start by learning how to use with.

5.1. The with() Helper Method

Spock’s with() helper method takes an object and a closure for that object. When we pass an object to the helper method with(), our object’s attributes and methods are added to our context. This means we don’t need to prefix them with the object name when we’re inside the scope of our with() closure.

So, one option is to refactor our method to use with:

void verifyAccountWith(Account accountToVerify) {
    with(accountToVerify) {
        getAccountName() == "My Account"
        getCurrentBalance() == BigDecimal.TEN
        getOverdraftLimit() == 0
    }
}

Notice that power assertions apply inside Spock’s helper method, so we don’t need any asserts even though it’s in a separate method!

Usually, we don’t even need a separate method, so let’s use with() directly in the expectations of our test:

then: "the values we retrieve match the ones that we set"
with(account) {
    getAccountName() == "My Account"
    getCurrentBalance() == BigDecimal.TEN
    getOverdraftLimit() == 0
}

And now let’s invoke our method from our assertions:

then: "the values we retrieve match the ones that we set"
verifyAccountWith(account)

Our with() method passes but fails a test on the first comparison that doesn’t match.

5.2. With for Mocks

We can also use with when asserting interactions. Let’s create a Mock Account and invoke some of its setters:

given: 'a mock account'
Account mockAccount = Mock()
when: "we invoke its setters"
mockAccount.setAccountName("A Name")
mockAccount.setOverdraftLimit(0)

Now let’s verify some interactions using with applied to our mockAccount:

with(mockAccount) {
    1 * setAccountName(_ as String)
    1 * setOverdraftLimit(_)
}

Notice that we can omit mockAccount when verifying mockAccount.setAccountName as it’s in our with‘s scope.

5.3. The verifyAll() Helper Method

Sometimes we’d rather know every assertion that fails when we run our test. In this case, we can use Spock’s verifyAll() helper method, in the same way that we used with.

So, let’s add a check using verifyAll():

verifyAll(accountToVerify) {
    getAccountName() == "My Account"
    getCurrentBalance() == BigDecimal.TEN
    getOverdraftLimit() == 0
}

Instead of failing a test when one comparison fails, the verifyAll() method will continue executing and report all failing comparisons inside verifyAll’s scope.

5.4. Nested Helper Methods

When we have an object within an object to compare we can nest our helper methods.

To see how let’s first create an Address with a street and a city and add it to our Account:

public class Address {
    String street;
    String city;
    // getters and setters
}
public class Account {
    private Address address;
    // getter and setter and rest of class
}

Now that we have an Address class, let’s create one in our test:

given: "an address"
Address myAddress = new Address()
def myStreet = "1, The Place"
def myCity = "My City"
myAddress.setStreet(myStreet)
myAddress.setCity(myCity)

And add it to our account:

when: "we set attributes on our account"
account.setAddress(myAddress)
account.setAccountName("My Account")

Next, let’s compare our address. When we don’t use helper methods, our most basic approach is:

account.getAddress().getStreet() == myStreet
account.getAddress().getCity() == myCity

We could improve this when by extracting an address variable:

def address = account.getAddress()
address.getCity() == myCity
address.getStreet() == myStreet

But better still, let’s use the with helper method for a cleaner comparison:

with(account.getAddress()) {
    getStreet() == myStreet
    getCity() == myCity
}

Now that we’re using with to compare our address, let’s nest it within our comparison of the Account. Since with(account) brings our account into scope, we can drop it from account.getAddress() and use with(getAddress()) instead:

then: "the values we retrieve match the ones that we set"
with(account) {
    getAccountName() == "My Account"
    with(getAddress()) {
        getStreet() == myStreet
        getCity() == myCity
    }
}

Since Groovy can derive getters and setters for us, we can also refer to our object’s properties by name.

So, let’s make our test yet more readable by using the property names rather than getters:

with(account) {
    accountName == "My Account"
    with(address) {
        street == myStreet
        city == myCity
    }
}

6. How Do They Work?

We’ve learned how with() and verifyAll() help us to make our tests more readable, but how do they do that?

Let’s look at the method signature for with():

with(Object, Closure)

So we can use with() by passing it a Closure as the second argument:

with(account, (acct) -> {
    acct.getAccountName() == "My Account"
    acct.getOverdraftLimit() == 0
})

But Groovy has special support for methods where the last argument is a Closure. It allows us to declare the Closure outside of the parenthesis.

So, let’s use Groovy’s more readable equivalent, but more succinct form:

with(account) {
    getAccountName() == "My Account"
    getOverdraftLimit() == 0
}

Notice that there are two arguments yet in our tests we only passed one argument, account, to with().

The second, Closure, argument is the code within the curly braces immediately after our with, which gets passed the first, account, argument to operate on.

7. Conclusion

In this tutorial, we learned how to use Spock’s with() and verifyAll() helper methods to reduce the boilerplate in our tests when we compare objects. We learned how helper methods can be used for simple objects, and nested when our objects are more complex. We also saw how to use Groovy’s property notation to make our helper assertions even cleaner and how to use helper methods with Mocks. Finally, we learned how we gain from Groovy’s alternative, cleaner, syntax for methods whose last parameter is a Closure when using helper methods.

As usual, the source for this tutorial is over on GitHub.

       

Counting Subrrays Having a Specific Arithmetic Mean in Java

$
0
0

1. Overview

In this short tutorial, we’ll learn how to efficiently find the number of contiguous subarrays of an array with a given arithmetic mean.

We’ll start with a naive approach that computes the task in a quadratic time complexity O(n^2). Then, we’ll analyze why it’s inefficient and optimize it to a linear O(n) solution using prefix sums and a frequency map.

2. Problem Statement

Let’s first understand what the problem we’re trying to solve is.

Suppose we have an array of integers and a target mean, which is just a single integer. Now, we want to count the number of contiguous subarrays having the specific arithmetic mean starting from an input array. For example, for the array [5, 3, 6, 2] and the target mean of 4, the output should be 3. This is because the subarrays [5, 3], [6, 2], and [5, 3, 6, 2] all have a mean of 4.

Additionally, we want to impose the following constraints:

  • The array can contain up to 100,000 elements.
  • Each number in the array is in the range of [-1,000,000,000, +1,000,000,000].
  • The target mean is in the same range.

Let’s start by solving this problem with a brute-force approach.

3. Brute Force Solution

The most straightforward approach to solve this problem is to start with two nested loops. One is for iterating over all possible indices of the subarray, and the other is for calculating the sum of the subarray and checking if the mean is equal to the target mean:

static int countSubarraysWithMean(int[] inputArray, int mean) {
    int count = 0;
    for (int i = 0; i < inputArray.length; i++) {
        long sum = 0;
        for (int j = i; j < inputArray.length; j++) {
            sum += inputArray[j];
            if (sum * 1.0 / (j - i + 1) == mean) {
                count++;
            }
        }
    }
    return count;
}

Here, we calculate the sum and length for each subarray to find the mean. If the mean equals the target mean, we increment the count. Also, we multiply by a floating number so the mean is calculated with more precision.

However simple this solution might seem, it’s not performant. When talking about algorithm complexity, we’re usually interested in time complexity. This brute-force solution has a time complexity of O(n^2), which is inefficient. In fact, for our input constraint of 100,000 elements, we’d need to perform 10 billion operations, which is too slow.

Let’s find an alternative, more efficient solution.

4. Linear Solution

Before moving forward, we need to understand two key ideas to optimize the solution to a linear time complexity.

4.1. Understanding Prefix Sums and Frequency Maps

First, the idea of prefix sums allows us to calculate the sum of any subarray in O(1) time. Second, the concept of a frequency map helps us count subarrays by keeping track of the frequency of certain values.

Now, suppose we have an input array X and a target mean S. Let’s define two arrays to represent the prefix sum and the adjusted prefix sum of the input array:

P[i] = X[0] + X[1] + ... + X[i] (prefix sum array)
ADJUSTED_PREFIX_SUM_ARRAY[i] = P[i] - S * i (adjusted prefix sum array)

Then, for any subarray [i, j] with average S, we define Q:

ADJUSTED_PREFIX_SUM_ARRAY[j] = P[j] - S * j
     = (P[j] - P[i-1]) - S * (j - (i-1))
     = (sum of subarray [i, j]) - (length of subarray [i, j]) * S
     = ADJUSTED_PREFIX_SUM_ARRAY[i-1]

ADJUSTED_PREFIX_SUM_ARRAY calculates the sum of the subarray [i, j] and subtracts the expected sum of the subarray with average S. Now, with these values, we can count the number of subarrays with average S by counting the number of pairs of indices (i-1, j) where ADJUSTED_PREFIX_SUM_ARRAY[j] = ADJUSTED_PREFIX_SUM_ARRAY[i-1].

The key insight here is that if we find two indices in the ADJUSTED_PREFIX_SUM_ARRAY array with the same value, the subarray between these indices (not including the earlier index) has the mean S. This means that the subarray from i to j has exactly the mean S:

(P[j] - P[i-1]) - S * (j - (i-1)) = 0

This subarray is equivalent to this one:

P[j] - S * j = P[i-1] - S * (i-1)

The left side is ADJUSTED_PREFIX_SUM_ARRAY[j], and the right side is ADJUSTED_PREFIX_SUM_ARRAY[i-1].

Let’s implement this.

4.2. Java Implementation

Let’s start by creating two arrays:

static int countSubarraysWithMean(int[] inputArray, int mean) {
    int n = inputArray.length;
    long[] prefixSums = new long[n+1];
    long[] adjustedPrefixes = new long[n+1];
    // More code
}

Here, we use long values instead of integers to avoid overflow when calculating the sum of sub-arrays containing large values. Then, we calculate the prefix sum array P and the adjusted prefix sum array Q:

for (int i = 0; i < n; i++) {
    prefixSums[i+1] = prefixSums[i] + inputArray[i];
    adjustedPrefixes[i+1] = prefixSums[i+1] - (long) mean * (i+1);
}

Next, we create a frequency map to count the number of subarrays and return the total count:

Map<Long, Integer> count = new HashMap<>();
int total = 0;
for (long adjustedPrefix : adjustedPrefixes) {
    total += count.getOrDefault(adjustedPrefix, 0);
    count.put(adjustedPrefix, count.getOrDefault(adjustedPrefix, 0) + 1);
}
return total;

For each adjusted prefix in the prefixes array, we increment the total count by the frequency of adjustedPrefixes seen so far. If the frequency map doesn’t contain the prefix, we return 0.

This solution has a time complexity of O(n). We’re running a for loop twice. First, we calculate the prefix sum and the adjusted prefix sum, and the second time, we count the number of subarrays. The space complexity is also O(n) as we’re using two arrays of size n and a frequency map that could store n entries.

5. Conclusion

In this article, we solved the problem of counting subarrays with a given arithmetic mean. First, we implemented a brute force solution with an O(n^2) time complexity. Then, we optimized it to a linear time complexity O(n) using prefix sums and a frequency map.

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

       

Getting Month Number From Its Name in Java

$
0
0

1. Overview

When working with months in Java, we often leverage the numerical format because it helps standardize across different languages and regions as month names vary widely.

In this short tutorial, we’ll learn different ways of converting a month’s name to its respective number. First, we’ll see how to do this using JDK. Then, we’ll discuss how to achieve the same outcome using the third-party Joda-Time library.

2. Using the Java 8+ Date-Time API

Java 8 is frequently praised for its introduction of the new Date-Time API, designed to address the shortcomings of the legacy date API. So, let’s see how to use this new API to answer our central question.

2.1. Using Month

The easiest solution would be using the Month enum. As the name implies, it models the 12 months of the year. In addition to the textual names, each constant has an int value from 1 (January) to 12 (December).

So, let’s see it in action:

@Test
void givenMonthName_whenUsingMonthEnum_thenReturnNumber() {
    String givenMonthName = "October";
    int expectedMonthNumber = 10;
    int monthNumber = Month.valueOf(givenMonthName.toUpperCase())
      .getValue();
    assertEquals(expectedMonthNumber, monthNumber);
}

As we see above, we use the valueOf() method to get the int value of the passed month name. Since it’s an enum, we use the toUpperCase() method to convert the input to uppercase before calling valueOf().

2.2. Using ChronoField

ChronoField is another enum that we can use to get the number of a given month. It denotes a standard set of fields that provide access to temporal information such as days, months, and years.

So, let’s see it in practice:

@Test
void givenMonthName_whenUsingChronoFieldEnum_thenReturnNumber() {
    String givenMonthName = "Sep";
    int expectedMonthNumber = 9;
    int monthNumber = DateTimeFormatter.ofPattern("MMM")
      .withLocale(Locale.ENGLISH)
      .parse(givenMonthName)
      .get(ChronoField.MONTH_OF_YEAR);
    assertEquals(expectedMonthNumber, monthNumber);
}

As we can see, we used the MMM format which is a short form of the month using three characters. Then, we passed the MONTH_OF_YEAR  constant to the get() method. That way we get the number of the given month “Sep”.

3. Using the Legacy Date API

Before Java 8, we typically used Date or Calendar to manipulate or work with temporal objects. So, let’s go down the rabbit hole and see how to use these legacy classes to convert a month’s name to its corresponding number:

@Test
void givenMonthName_whenUsingDateLegacyAPI_thenReturnNumber() throws ParseException {
    String givenMonthName = "May";
    int expectedMonthNumber = 5;
    Date date = new SimpleDateFormat("MMM", Locale.ENGLISH).parse(givenMonthName);
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    int monthNumber = calendar.get(Calendar.MONTH) + 1;
    assertEquals(expectedMonthNumber, monthNumber);
}

Here, we use SimpleDateFormat with the pattern MMM to parse the abbreviated month name into a Date object. Furthermore, we create an instance of Calendar and set the Date object, which represents the parsed month, as the time for the calendar.

Lastly, we retrieve the month from the calendar using the get() method. Typically, Calendar.MONTH is zero-based (January is 0, February is 1, etc.),  so we add 1 to get the correct month number.

4. Using Joda-Time

Joda-Time is another option to consider to tackle our challenge. First, let’s add its dependency to the pom.xml file:

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.12.7</version>
</dependency>

Similarly, Joda-Time offers a convenient method called getMonthOfYear() that we can use to get the number of a given month:

@Test
void givenMonthName_whenUsingJodaTime_thenReturnNumber() {
    String givenMonthName = "April";
    int expectedMonthNumber = 4;
    int monthNumber = DateTimeFormat.forPattern("MMM")
      .withLocale(Locale.ENGLISH)
      .parseDateTime(givenMonthName)
      .getMonthOfYear();
    assertEquals(expectedMonthNumber, monthNumber);
}

As illustrated, the logic remains the same as previously seen. Here, we use the getMonthOfYear() to get the numerical value of the parsed month name.

5. Conclusion

In this short article, we explored different ways of converting a month’s name into its respective number.

First, we saw how to do the conversion using JDK. Then, we learned how to achieve the same result using the Joda-Time library.

As always, the code used in this article can be found over on GitHub.

       

Java Weekly, Issue 551

$
0
0

1. Spring and Java

>> Structured Concurrency is More Than ShutdownOnFailure [foojay.io]

Going beyond shutdown on failure: throttling, circuit breaking, list conversion, default values, and critical failures!

>> Java Virtual Threads: A Case Study [infoq.com]

Evaluating virtual thread effectiveness in different use cases: throughput, ramp-up time, and memory footprint — great write-up!

Also worth reading:

Webinars and presentations:

Time to upgrade:

2. Technical & Musings

>> Advanced URL rewriting with Apache APISIX [frankel.ch]

Meet proxy-rewrite: a plugin enabling one of APISIX’s best features, rerouting requests!

Also worth reading:

3. Pick of the Week

>> James Gosling is retiring [linkedin.com]

       

Parse JSON with Manifold

$
0
0

1. Introduction

In this article, we’re going to look at Manifold JSON for JSON Schema-aware parsing of JSON documents. We’ll see what it is, what we can do with it, and how to use it.

Manifold is a suite of compiler plugins that each provide some highly productive features we can use in our applications. In this article, we’re going to explore using it for parsing and building JSON documents based on provided JSON Schema files.

2. Installation

Before we can use Manifold, we need to ensure that it’s available to our compiler. The most important integration is as a plugin to our Java compiler. We do this by configuring our Maven or Gradle project appropriately. In addition, Manifest provides plugins for some IDEs to make our development process easier.

2.1. Compiler Plugin in Maven

Integrating Manifold into our application in Maven requires us to add both a dependency and a compiler plugin. These need to both be the same version, which is 2024.1.20 at the time of writing.

Adding our dependency is the same as any other:

<dependency>
    <groupId>systems.manifold</groupId>
    <artifactId>manifold-json-rt</artifactId>
    <version>2024.1.20</version>
</dependency>

Adding our compiler plugin requires us to configure the maven-compiler-plugin within our module:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>
            <!-- Configure manifold plugin-->
            <arg>-Xplugin:Manifold</arg>
        </compilerArgs>
        <!-- Add the processor path for the plugin -->
        <annotationProcessorPaths>
            <path>
                <groupId>systems.manifold</groupId>
                <artifactId>manifold-json</artifactId>
                <version>2024.1.20</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Here we’re adding the -Xplugin:Manifold command-line argument when the compiler is executed, as well as adding the Manifold JSON annotation processor.

This annotation processor is what does all the work of generating code from our JSON schema files.

2.2. Compiler Plugin in Gradle

Integrating Manifold into our application with Gradle needs to achieve the same, but the configuration is slightly simpler. Gradle supports adding annotation processors to the Java compiler in the same way as any other dependency:

dependencies {
    implementation 'systems.manifold:manifold-json-rt:2024.1.20'
    annotationProcessor 'systems.manifold:manifold-json:2024.1.20'
    testAnnotationProcessor 'systems.manifold:manifold-json:2024.1.20'
}

However, we still need to ensure that the -Xplugin:Manifold parameter is passed to the compiler:

tasks.withType(JavaCompile) {
    options.compilerArgs += ['-Xplugin:Manifold']
}

At this point, we’re ready to start using Manifold JSON.

2.3. IDE Plugins

In addition to the plugins in our Maven or Gradle build, Manifold provides plugins for IntelliJ and Android Studio. These can be installed from the standard plugin marketplace:

Once installed, this will allow the compiler plugins that we’ve previously set up in our project to be used when code is compiled from within the IDE, instead of relying only on the Maven or Gradle builds to do the right thing.

3. Defining Classes with JSON Schema

Once we’ve got Manifold JSON set up in our project, we can use it to define classes. We do this by writing JSON Schema files within the src/main/resources (or src/test/resources) directory. The full path to the file then becomes the fully qualified class name.

For example, we can create a com.baeldung.manifold.SimpleUser class by writing our JSON schema in src/main/resources/com/baeldung/manifold/SimpleUser.json:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "http://baeldung.com/manifold/SimpleUser.json",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "maxLength": 100
    },
    "name": {
      "type": "string",
      "maxLength": 80
    },
    "email": {
      "type": "string",
      "format": "email",
      "maxLength": 260
    }
  },
  "required": [
    "username",
    "name"
  ]
}

This represents a class with three fields – username, name, and email – all of type String.

However, Manifold doesn’t do anything it doesn’t need to do. As such, writing this file won’t generate any code if nothing references it. This reference can be anything that the compiler needs though – even something as simple as a variable definition.

4. Instantiating Manifold Classes

Once we’ve got a class definition, we want to be able to use it. However, we’ll quickly discover that these aren’t regular classes. Manifold generates all of our expected classes as Java interfaces instead. This means that we can’t simply create new instances using new.

However, the generated code provides a static create() method that we can use as a constructor method. This method will need all of the required values in the order they were specified in the required array. For example, the above JSON Schema will produce the following:

public static SimpleUser create(String username, String name);

We then have setters that we can use to populate any of the remaining fields.

So we can then create a new instance of the SimpleUser class with:

SimpleUser user = SimpleUser.create("testuser", "Test User");
user.setEmail("testuser@example.com");

In addition to this, Manifold provides us with a build() method that we can use for the builder pattern. This takes the same set of required parameters, but instead returns a builder object that we can use to populate any other fields:

SimpleUser user = SimpleUser.builder("testuser", "Test User")
  .withEmail("testuser@example.com")
  .build();

If we’re using fields that are both optional and read-only then the builder pattern is the only way that we can provide values – once we’ve got a created instance then we can no longer set the values of these fields.

5. Parsing JSON

Once we’ve got our generated classes, we can use them to interact with JSON sources. Our generated classes provide the means to load data from various types of input and parse it directly into our target class.

The route into this is always triggered using the static load() method on our generated class. This provides us with a manifold.json.rt.api.Loader<> interface that gives us various methods for loading our JSON data.

The simplest of these is the ability to parse a JSON string that has been provided to us in some manner. This is done using the fromJson() method, which takes a string and returns a fully-formed instance of our class:

SimpleUser user = SimpleUser.load().fromJson("""
    {
        "username": "testuser",
        "name": "Test User",
        "email": "testuser@example.com"
    }
    """);

On success, this gives us our desired result. On failure, we get a RuntimeException that wraps a manifold.rt.api.ScriptException indicating exactly what has gone wrong.

We also have various other ways that we can provide the data:

  • fromJsonFile() which reads the data from a java.io.File.
  • fromJsonReader() which reads the data from a java.io.Reader.
  • fromJsonUrl() which reads the data from a URL – in which case Manifold will go and fetch the data this URL points at.

Note however that the fromJsonUrl() method ultimately uses java.net.URL.openStream() to read the data, which may not be as efficient as we’d like.

All of these work in the same way – we call them with the appropriate source of data and it returns us a fully formed object, or throws a RuntimeException if the data can’t be parsed:

InputStream is = getClass().getResourceAsStream("/com/baeldung/manifold/simpleUserData.json");
InputStreamReader reader = new InputStreamReader(is);
SimpleUser user = SimpleUser.load().fromJsonReader(reader);

6. Generating JSON

As well as being able to parse JSON into our objects, we can go the other way and generate JSON from our objects.

In the same way that Manifold produces a load() method to load JSON into our objects, we also have a write() method to write JSON from our object. This returns us an instance of manifold.json.rt.api.Writer which gives us methods for writing our JSON from our object.

The simplest of these is the toJson() method, which returns the JSON as a String, from which we can do whatever we want:

SimpleUser user = SimpleUser.builder("testuser", "Test User")
  .withEmail("testuser@example.com")
  .build();
String json = user.write().toJson();

Alternatively, we can write JSON to anything that implements the Appendable interface. This includes, amongst many other things, the java.io.Writer interface or the StringBuilder and StringBuffer classes:

SimpleUser user = SimpleUser.builder("testuser", "Test User")
  .withEmail("testuser@example.com")
  .build();
user.write().toJson(writer);

This writer can then write to any target – including memory buffers, files, or network connections.

7. Alternative Formats

The primary purpose of manifold-json is to be able to parse and generate JSON content. However, we also have the ability for other formats – CSV, XML, and YAML.

For each of these, we need to add a particular dependency to our project – systems.manifold:manifold-csv-rt for CSV, systems.manifold:manifold-xml-rt for XML or systems.manifold:manifold-yaml-rt for YAML. We can add as many or as few of these as we need.

Once this is done, we can use the appropriate methods on the Loader and Writer interfaces that Manifold provides to us:

SimpleUser user = SimpleUser.load().fromXmlReader(reader);
String yaml = user.write().toYaml();

8. JSON Schema Features

Manifold uses JSON Schema files to describe the structure of our classes. We can use these files to describe the classes to generate and the fields to be present in them. However, we can describe more than just this with JSON Schema, and Manifold JSON supports some of these extra features.

8.1. Read-Only and Write-Only Fields

Fields that are marked as read-only in the schema are generated without setters. This means that we can populate them at construction time, or from parsing input files, but can never change the values afterward:

"username": {
    "type": "string",
    "readOnly": true
},

Conversely, the generator creates fields marked as write-only in the schema without getters. This means that we can populate them however we wish – at construction time, parsing from input files, or using setters – but we can never get the value back out.

"mfaCode": {
    "type": "string",
    "writeOnly": true
},

Note that the system still renders write-only properties in any generated output, so we can access them through that route. However, we cannot read these properties from the Java classes.

8.2. Formatted Types

Some types in a JSON Schema allow us to provide additional format information. For example, we can specify that a field is a string but has a format of date-time. This is a hint to anything using the schema of the type of data that we should see in these fields.

Manifold JSON will do its best to understand these formats and produce appropriate Java types for them. For example, a string that is formatted as date-time will be generated as a java.time.LocalDateTime in our code.

8.3. Additional Properties

JSON Schema lets us define open-ended schemas through the use of additionalProperties and patternProperties.

The additionalProperties flag indicates that the type can have an arbitrary number of extra properties of any type. Essentially it means that the schema allows any other JSON to match. Manifold JSON defaults to having this defined as true, but we can explicitly set it to false in our schema if we wish.

If this is set to true then Manifold will provide two additional methods on our generated class – get(name) and put(name, value). Using these we can work with any arbitrary fields that we wish:

SimpleUser user = SimpleUser.builder("testuser", "Test User")
  .withEmail("testuser@example.com")
  .build();
user.put("note", "This isn't specified in the schema");

Note that no validation is done on these values. This includes checking if the name collides with other defined fields. As such, this can be used to overwrite fields defined in our schema – including ignoring validation rules such as type or readOnly.

JSON Schema also has support for a more advanced concept called “pattern properties”. We define these as full property definitions, but we use a regex to define the property name instead of a fixed string. For example, this definition will allow us to specify fields note0 up to note9 all as type string:

"patternProperties": {
  "note[0-9]": {
    "type": "string"
  }
}

Manifold JSON has partial support for this. Instead of generating explicit code to handle this, it will treat the presence of any patternProperties in a type the same as if additionalProperties were set to true. This includes if we’d explicitly set additionalProperties to false.

8.4. Nested Types

JSON Schema has support for us to define one type nested within another, instead of needing to define everything at the top level and use references. This can be very useful for providing localized structure to our JSON. For example:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "http://baeldung.com/manifold/User.json",
  "type": "object",
  "properties": {
    "email": {
      "type": "object",
      "properties": {
        "address": {
          "type": "string",
          "maxLength": 260
        },
        "verified": {
          "type": "boolean"
        }
      },
      "required": ["address", "verified"]
    }
  }
}

Here we’ve defined one object where one of the fields is another object. We’d then represent this with JSON looking like:

{
    "email": {
        "address": "testuser@example.com",
        "verified": false
    }
}

Manifold JSON has full support for this, and will automatically generate appropriately named inner classes. We can then use those classes exactly as any other:

User user = User.builder()
  .withEmail(User.email.builder("testuser@example.com", false).build())
  .build();

8.5. Composition

JSON Schema supports composing different types together, using the allOf, anyOf, or oneOf keywords. Each of these takes a collection of other schemas – either by reference or directly specified – and the resulting schema must match all of them, at least one of them, or exactly one of them respectively. Manifold JSON has a level of support for these keywords.

When using allOf, Manifold will generate a class that includes all of the definitions from the composed types. If we define these inline, the system will create a single class with all of the definitions:

"allOf": [
  {
    "type": "object",
    "properties": {
      "username": {
        "type": "string"
      }
    }
  },
  {
    "type": "object",
    "properties": {
      "roles": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  }
]

Alternatively, if we compose using references, the resulting interface will extend all of the types that we referenced:

"allOf": [
  {"$ref":  "#/definitions/user"},
  {"$ref":  "#/definitions/adminUser"}
]

In both cases, we can use the generated class as if it fits both sets of definitions:

Composed.user.builder()
    .withUsername("testuser")
    .withRoles(List.of("admin"))
    .build()

If instead, we use anyOf or oneOf then Manifold will generate code that can accept the alternative options in a typesafe manner. This requires us to use references so that Manifold can infer type names:

"anyOf": [
  {"$ref":  "#/definitions/Dog"},
  {"$ref":  "#/definitions/Cat"}
]

When doing this, our types gain typesafe methods for interacting with the different options:

Composed composed = Composed.builder()
  .withAnimalAsCat(Composed.Cat.builder()
    .withColor("ginger")
    .build())
  .build();
assertEquals("ginger", composed.getAnimalAsCat().getColor());

Here we can see that our builder has gained a withAnimalAsCat method – where the “Animal” portion is the field name within the outer object and the “Cat” portion is the inferred type name from our definition. Our actual object has also gained getAnimalAsCat and setAnimalAsCat methods in the same way.

9. Conclusion

In this article, we’ve given a broad introduction to Manifold JSON. This library can do much more, so why not try it out and see?

All of the examples are available over on GitHub.

       

Ignoring Fields During Comparison Using AssertJ

$
0
0
start here featured

1. Introduction

Ignoring fields during comparison with AssertJ is a powerful feature that helps to simplify tests by focusing only on the relevant parts of the objects. It’s beneficial when dealing with objects that have fields with dynamic or irrelevant values.

In this article, we’ll look at various methods within the AssertJ fluent API. We’ll start by setting the correct dependency and providing a simple example of an employee class. We’ll then explore various use cases for ignoring required fields within the object comparisons using methods provided by AssertJ.

2. Maven Dependency and Example Setup

Let’s start with setting up the required dependency enabling us to use this feature. We’ll add the assertj-guava dependency in pom.xml:

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-guava</artifactId>
    <version>3.4.0</version>
</dependency>

Next, we’ll construct a simple Employee class with the following fields:

public class Employee {
    public Long id;
    public String name;
    public String department;
    public String homeAddress;
    public String workAddress;
    public LocalDate dateOfBirth;
    public Double grossSalary;
    public Double netSalary;
    ...// constructor
    ...//getters and setters
}

In the next sections, we’ll focus on methods provided by the AssertJ fluent API to ignore desired fields in Employee class tests in various cases.

3. Using ignoringFields()

Firstly, let’s see how we can specify one or more fields to ignore when comparing actual and expected objects. Here, we can use the APIs ignoringFields() method. It lets us specify which fields should be ignored during the comparison. This is particularly useful when we want to exclude fields that may vary between instances but are irrelevant to the test.

Let’ ‘s use the example of the Employee class created previously. We’ll create two instances of the Employee class and then compare them using AssertJ while ignoring some fields:

@Test
public void givenEmployeesWithDifferentAddresses_whenComparingIgnoringSpecificFields_thenEmployeesAreEqual() {
    // Given
    Employee employee1 = new Employee();
    employee1.id = 1L;
    employee1.name = "John Doe";
    employee1.department = "Engineering";
    employee1.homeAddress = "123 Home St";
    employee1.workAddress = "456 Work Ave";
    employee1.dateOfBirth = LocalDate.of(1990, 1, 1);
    employee1.grossSalary = 100000.0;
    employee1.netSalary = 75000.0;
    Employee employee2 = new Employee();
    employee2.id = 2L;
    employee2.name = "John Doe";
    employee2.department = "Engineering";
    employee2.homeAddress = "789 Home St";
    employee2.workAddress = "101 Work Ave";
    employee2.dateOfBirth = LocalDate.of(1990, 1, 1);
    employee2.grossSalary = 110000.0;
    employee2.netSalary = 80000.0;
    // When & Then
    Assertions.assertThat(employee1)
      .usingRecursiveComparison()
      .ignoringFields("id", "homeAddress", "workAddress", "grossSalary", "netSalary")
      .isEqualTo(employee2);
}

Here, we want to ignore fields namely id, homeAddress, workAddress, grossSalary, and netSalary when comparing two Employee objects. This means that equality holds even if all these ignored fields are different for employee1 and employee2.

4. Using ignoringFieldsMatchingRegexes() 

AssertJ also provides the ignoringFieldsMatchingRegexes() method, allowing us to ignore fields based on regular expressions. This is useful when we have multiple fields with similar names that we may want to exclude.

Let’s assume we have an Employee class where fields like homeAddress, workAddress, etc., should be ignored in the comparison. Similarly, we want to ignore all the fields ending with “Salary”:

@Test
public void givenEmployeesWithDifferentSalaries_whenComparingIgnoringFieldsMatchingRegex_thenEmployeesAreEqual() {
    Employee employee1 = new Employee();
    employee1.id = 1L;
    employee1.name = "Jane Smith";
    employee1.department = "Marketing";
    employee1.homeAddress = "123 Home St";
    employee1.workAddress = "456 Work Ave";
    employee1.dateOfBirth = LocalDate.of(1990, 1, 1);
    employee1.grossSalary = 95000.0;
    employee1.netSalary = 70000.0;
    Employee employee2 = new Employee();
    employee2.id = 2L;
    employee2.name = "Jane Smith";
    employee2.department = "Marketing";
    employee2.homeAddress = "789 Home St";
    employee2.workAddress = "101 Work Ave";
    employee2.dateOfBirth = LocalDate.of(1990, 1, 1);
    employee2.grossSalary = 98000.0;
    employee2.netSalary = 72000.0;
    Assertions.assertThat(employee1)
      .usingRecursiveComparison()
      .ignoringFields("id")
      .ignoringFieldsMatchingRegexes(".*Address", ".*Salary")
      .isEqualTo(employee2);
}

Here, all fields ending with Address and Salary are ignored using ignoringFieldMatchingRegex() in addition to the id field.

5. Using ignoringExpectedNullFields()

Finally, we’ll look into the ignoreExpectedNullFields() method in the AssertJ API. This method ignores fields that are null in the expected object during comparison. This is particularly useful when only some fields are significant in the expected object, and others aren’t set or are irrelevant.

Let’s suppose we want to compare two Employee objects, but in the expected object, some fields are null and should be ignored:

@Test
public void givenEmployeesWithNullExpectedFields_whenComparingIgnoringExpectedNullFields_thenEmployeesAreEqual() {
    Employee expectedEmployee = new Employee();
    expectedEmployee.id = null;
    expectedEmployee.name = "Alice Johnson";
    expectedEmployee.department = null;
    expectedEmployee.homeAddress = null;
    expectedEmployee.workAddress = null;
    expectedEmployee.dateOfBirth = LocalDate.of(1985, 5, 15);
    expectedEmployee.grossSalary = null;
    expectedEmployee.netSalary = null;
    Employee actualEmployee = new Employee();
    actualEmployee.id = 3L;
    actualEmployee.name = "Alice Johnson";
    actualEmployee.department = "HR";
    actualEmployee.homeAddress = "789 Home St";
    actualEmployee.workAddress = "123 Work Ave";
    actualEmployee.dateOfBirth = LocalDate.of(1985, 5, 15);
    actualEmployee.grossSalary = 90000.0;
    actualEmployee.netSalary = 65000.0;
    Assertions.assertThat(actualEmployee)
      .usingRecursiveComparison()
      .ignoringExpectedNullFields()
      .isEqualTo(expectedEmployee);
}

Here, the comparison is made between the expectedEmployee and actualEmployee object based on all the non-null fields in the expectedEmployee object.

6. Conclusion

In this tutorial, we looked at various methods provided by AssertJ to ignore certain fields during object comparisons in testing. Using methods like ignoringFields(), ignoringFieldsMatchingRegexes(), and ignoringExpectedNullFields(), we can make our tests more flexible and easy to maintain.

As always, the full implementation of this article can be found over on GitHub.

       

Get Date and Time From a Datetime String in Java

$
0
0

1. Introduction

Working with date and time is a common task in many Java applications. Whether for parsing input, formatting output, or manipulating dates and times within the program, Java provides robust tools to handle these tasks efficiently. Often, we receive a datetime string that needs manipulation, such as extracting the date and time separately for further processing.

In this article, we’ll look at various ways to extract the date and time separately from the input string.

2. Understanding the Problem

The most crucial step when working with datetime strings is deciding on a particular format. Without a standard format, correctly handling input strings becomes nearly impossible. Therefore, the format needs to be agreed upon beforehand.

For this tutorial, we’ll define a sample datetime string:

String datetime = "2024-07-04 11:15:24"

We’ll use the format yyyy-MM-dd HH:mm:ss as the expected standard. Additionally, we’ll support handling milliseconds with the format yyyy-MM-dd HH:mm:ss.SSS.

Given this input string, we should separate it into two strings: 2024-07-04 and 11:15:24.

3. Using split()

We can split the input string by space and separate the date and time part. Let’s look at the implementation:

@Test
void givenDateTimeString_whenUsingSplit_thenGetDateAndTimeParts() {
    String dateTimeStr = "2024-07-04 11:15:24";
    String[] split = dateTimeStr.split("\\s");
    assertEquals(2, split.length);
    assertEquals("2024-07-04", split[0]);
    assertEquals("11:15:24", split[1]);
}

This splits the string into date and time components. However, it’s important to note that this code will split even invalid strings without indicating any errors about the validity of the datetime string. Consequently, there might be more elegant solutions.

On the other hand, this approach can still split any input string if the date and time are separated by a space, making it applicable to many formats without specifying the exact pattern.

Let’s look at another example:

String dateTimeStr = "2024/07/04 11:15:24.233";
String[] split = dateTimeStr.split("\\s");
assertEquals(2, split.length);
assertEquals("2024/07/04", split[0]);
assertEquals("11:15:24.233", split[1]);

In the above example, the datetime doesn’t follow the expected pattern. However, we can still use this method since a space character separates the date and time parts.

However, if there is more than one space character in the string, then the splitting goes wrong. In such case we can use split() with limit parameter:

String dateTimeStr = "8/29/2011 11:16:12 AM";
String[] split = dateTimeStr.split("\\s", 2);
assertEquals(2, split.length);
assertEquals("8/29/2011", split[0]);
assertEquals("11:16:12 AM", split[1]);

Here, we use the split() function with a limit 2. This ensures the string is split at the first occurrence of the space character, with the remaining part returned as the second part. This method allows us to extract the date and time separately, even when additional markers such as AM/PM are present in the datetime string.

4. Using DateTimeFormatter

Another way to split the date and time component is by using the java.time APIs. We can parse the string into a LocalDateTime object using the DateTimeFormatter. After that, we can use the methods on the LocalDateTime object to separate the date and time components.

Let’s look at an example:

String dateTimeStr = "2024-07-04 11:15:24";
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH);
DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.ENGLISH);
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, format);
assertEquals("2024-07-04", dateTime.toLocalDate().format(dateFormat));
assertEquals("11:15:24", dateTime.toLocalTime().format(timeFormat));

In this case, we created an instance of the DateTimeFormatter with the expected pattern for the input string. We can then parse the string into LocalDateTime using the formatter. After that, we can use its methods to retrieve the date and time components.

The advantage of this method over the split() function is that it allows us to validate whether the datetime string is valid. However, the downside is that we need to know the exact pattern beforehand, whereas the split() approach can handle multiple formats without knowing the exact format beforehand.

Let’s explore improving this implementation to support multiple formats while validating the datetime. We can use the DateTimeFormatterBuilder to define multiple formats. Here is an example:

DateTimeFormatter format1 = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH:mm:ss.SSS", Locale.ENGLISH);
DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder();
DateTimeFormatter multiFormatter = dateTimeFormatterBuilder
  .appendOptional(format1)
  .appendOptional(format2)
  .toFormatter(Locale.ENGLISH);
// case 1
LocalDateTime dateTime1 = LocalDateTime.parse("2024-07-04 11:15:24", multiFormatter);
String date1 = dateTime1.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String time1 = dateTime1.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
assertEquals("2024-07-04", date1);
assertEquals("11:15:24", time1);
// case 2
LocalDateTime dateTime2 = LocalDateTime.parse("04-07-2024T11:15:24.123", multiFormatter);
String date2 = dateTime2.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String time2 = dateTime2.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
assertEquals("2024-07-04", date2);
assertEquals("11:15:24.123", time2);

In the above code sample, we utilized DateTimeFormatterBuilder to define multiple supported formats using the appendOptional() method. We then obtained a DateTimeFormatter instance by invoking the toFormatter() method. Consequently, we used this formatter to parse the input string. This approach allows us to parse three different formats easily.

However, the downside is that we must explicitly provide the exact format when converting back to separate date and time strings.

5. Using Regular Expressions

We can also use regular expressions to extract the date and time components separately. Let’s look at the usage of regular expression with a sample code:

String dateTimeStr = "2024-07-04 11:15:24.123";
Pattern pattern = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})\\s(\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?)");
Matcher matcher = pattern.matcher(dateTimeStr);
assertTrue(matcher.matches());
assertEquals("2024-07-04", matcher.group(1));
assertEquals("11:15:24.123", matcher.group(2));

In this case, we use the regular expression to match and extract the date and time components from the string. This approach provides flexibility in handling various formats but requires careful regular expression crafting.

6. Conclusion

In this article, we explored various methods to separate date and time components from a datetime string in Java. We covered simple string splitting, using DateTimeFormatter, and employing DateTimeFormatterBuilder for multiple formats. Additionally, we discussed the use of regular expressions. Each method has pros and cons, and the choice depends on our application’s requirements and constraints.

As always, the sample code used in this article is available over on GitHub.

       
Viewing all 4533 articles
Browse latest View live


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