Java bug digest: infinite loops and missing exit conditions

Overview

This digest outlines some of the most interesting or impactful bugs encountered by our AI assistant over the past period. Our goal is to highlight issues that can inform and improve future development by learning from past challenges.

Findings

Bug 1

Description

In a weather data processing system, there’s a bug in the extractTemperatureReading() method. This method attempts to extract a valid temperature reading from a list of sensor data. However, it can potentially enter an infinite loop when processing invalid data.

Solution

To fix the bug in the extractTemperatureReading() method, modify the while loop condition to also check if the sensorData size is still greater than or equal to TEMPERATURE_READING_LENGTH:

while (sensorData.size() >= TEMPERATURE_READING_LENGTH) {
    List<Integer> chunk = sensorData.subList(0, TEMPERATURE_READING_LENGTH);
    if (isValidTemperatureReading(chunk)) {
        return List.copyOf(chunk);
    }
    LOGGER.warn("Chunk {} is invalid, moving to next chunk", chunk);
    sensorData.removeFirst();
    trimSensorDataUntilValidStart();
    if (sensorData.size() < TEMPERATURE_READING_LENGTH) {
        break;
    }
}

This modification ensures that the loop will exit if the sensorData size becomes less than TEMPERATURE_READING_LENGTH, preventing an infinite loop.

Key Takeaway

Always include appropriate exit conditions in loops that modify data structures to prevent infinite loops, especially when dealing with variable-length input data.

Bug 2

Description

In a weather forecasting application, there’s a bug in the WeatherDataProcessor class. The getter methods for various weather attributes inconsistently handle null values, potentially leading to null pointer exceptions or unexpected empty strings in the output.

Solution

To fix this bug, we should use Optional to handle null values consistently across all getter methods. Here’s an example of how to implement this for the getWindSpeed() method:

public String getWindSpeed() {
    return Optional.ofNullable(windSpeed)
        .map(WeatherUtil::formatWindSpeed)
        .orElse("");
}

Apply the same pattern to getTemperature(), getHumidity(), and getPrecipitation() methods to ensure consistent null handling throughout the class.

Key Takeaway

When dealing with potentially null values in getter methods, use Optional to provide a consistent and null-safe approach, reducing the risk of null pointer exceptions and improving code readability.

Bug 3

Description

In a recipe rating system, there’s a bug in the method that calculates the average rating for a recipe. The code assumes the AI-powered rating service always returns a valid numeric string, but it might return non-numeric data, causing a potential crash.

Solution

To fix the bug in the calculateAverageRating method, add error handling to catch potential NumberFormatException:

private Recipe calculateAverageRating(Recipe recipe) {
    try {
        String aiRating = aiRatingService.getRating(recipe.getIngredients());
        float rating = Float.parseFloat(aiRating);
        recipe.setAverageRating(rating == 0 ? 0 : rating / 5);
    } catch (NumberFormatException e) {
        // Log the error and set a default rating, or handle it as appropriate
        recipe.setAverageRating(0);
    }
    return recipe;
}

Key Takeaway

Always validate and handle potential exceptions when parsing data from external services, especially when converting strings to numeric types.

Bug 4

Description

In a recipe management system, there’s a bug in the getIngredientCategories method. This method retrieves ingredient categories from an external API and maps them to internal category names. However, the current implementation may inadvertently lose some categories during the mapping process.

Solution

To address this issue in the getIngredientCategories method, implement a more robust mapping strategy. Instead of silently using unmapped categories as-is, log a warning and use a default category for unknown inputs. Here’s an improved version:

public List<String> getIngredientCategories(Kitchen kitchen) {
    return ingredientApiClient.getCategories(kitchen.getId())
        .stream().map(category -> {
            String mappedCategory = categoryMappings.get(category);
            if (mappedCategory == null) {
                logger.warn("Unrecognized ingredient category: " + category);
                return "Miscellaneous";
            }
            return mappedCategory;
        })
        .collect(toList());
}

This solution ensures all categories are accounted for and provides visibility into any unexpected categories that might be introduced in the future.

Key Takeaway

When mapping between external and internal data representations, always handle unmapped cases explicitly to prevent data loss and maintain consistency across your application.

Bug 5

Description

In a file management system, there’s a potential resource leak in the saveDocument method. This method attempts to save documents to a remote storage service but doesn’t properly handle exceptions or manage resources.

Solution

To fix the bug, wrap the remote storage operation in a try-catch block and ensure proper resource management. Here’s how you can modify the saveDocument method:

public void saveDocument(String userId, List<Document> documents) {
    for (Document doc : documents) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String fileName = userId + "/" + timestamp + ".doc";

        SaveRequest saveRequest = SaveRequest.builder()
                .storage(STORAGE_NAME)
                .metadata(Map.of("doctype", doc.getDocumentType().getName()))
                .fileName(fileName)
                .build();

        try {
            remoteStorage.saveFile(saveRequest, FileContent.fromString(doc.getContent()));
        } catch (Exception e) {
            throw new StorageException("Failed to save document to remote storage: " + fileName + "\n" + e.getMessage());
        }
    }
}

Key Takeaway

Always wrap operations that interact with external resources (like remote storage services) in try-catch blocks to handle exceptions properly and prevent resource leaks. This ensures better error handling and resource management in your application.

Bug 6

Description

In a weather monitoring system, there’s a configuration class WeatherStationConfig that implements the SensorRegistryConfig interface. This class is responsible for setting up and configuring various weather sensors. However, the implementation of two critical methods, sensorPrefix() and getSensorSetting(String key), is incorrect, potentially causing malfunctions in the weather monitoring system.

Solution

To fix this issue, properly implement the sensorPrefix() and getSensorSetting(String key) methods in the WeatherStationConfig class. For example:

@Override
public String sensorPrefix() {
    return "weather";
}

@Override
public String getSensorSetting(String key) {
    if ("interval".equals(key)) {
        return "5m";
    }
    if ("unit".equals(key)) {
        return "celsius";
    }
    return null; // or throw an exception for unknown keys
}

Adjust the implementation based on your specific weather station configuration and requirements.

Key Takeaway

When implementing an interface, ensure all methods are properly implemented with meaningful return values. Returning null or empty implementations can lead to unexpected behavior and errors in the system.

Bug 7

Description

The following code snippet demonstrates a common misuse of Lombok annotations in a data transfer object (DTO) for a recipe management system:

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class RecipeDTO {
    private String title;
    private IngredientsDTO ingredients;

    @Getter
    @RequiredArgsConstructor
    public static class IngredientsDTO {
        private String mainIngredient;
        private String secondaryIngredient;
    }
}

Solution

The @RequiredArgsConstructor annotation on both RecipeDTO and IngredientsDTO classes is ineffective because there are no final fields in either class. This will result in a default no-args constructor being generated instead of a constructor with required arguments. To fix this, either remove the @RequiredArgsConstructor annotation from both classes, or mark the fields as final if they are meant to be immutable. If a no-args constructor is needed, use @NoArgsConstructor instead.

Key Takeaway

When using Lombok annotations, ensure that they align with the intended behavior of your classes. For @RequiredArgsConstructor to be effective, the class should have final fields that need initialization through a constructor.

Bug 8

Description

In a task scheduling application, there’s a bug in the method responsible for canceling scheduled tasks. The code attempts to cancel a task twice, which could lead to unexpected behavior or errors.

Solution

In the cancelScheduledTask method, remove the redundant call to task.cancel(). The corrected code should look like this:

if (task != null && task.isScheduled() && !task.isCancelled()) {
  try {
    task.cancel();
  } catch (IllegalStateException e) {
    logger.error("Error canceling task: {}, error: {}", task.getId(), e.getMessage());
  }
  // Remove this line:
  // task.cancel();
} else {
  logger.info("Task {} is not scheduled or already cancelled", task.getId());
}

This change ensures that the cancel() method is called only once, preventing potential issues that could arise from attempting to cancel an already cancelled task.

Key Takeaway

Always review your error handling code to ensure that actions are not unnecessarily repeated after catching exceptions. This helps maintain clean, efficient code and prevents unintended side effects.

Bug 9

Description

In a user authentication system for a music streaming service, there’s a bug in the method responsible for extracting user email from a JWT token. The method creates an unnecessary object that’s never used, leading to potential performance issues and code clutter.

Solution

Remove the following line from the getEmailFromJwtToken method:

UserCredentials credentials = new UserCredentials();

This UserCredentials object is not used in the method and can be safely removed without affecting the functionality. The method should focus solely on extracting and returning the email from the JWT token.

Key Takeaway

Always review your code for unused variables or objects. Removing unnecessary allocations, even if small, can improve code readability and potentially enhance performance, especially in frequently called methods.

Conclusions

Thank you for reading this Mavka Digest! Stay tuned for more insights and updates on code reviews, top comments, and useful development tips. Subscribe to keep up with our weekly, monthly, and yearly digests, and make sure not to miss out on the latest from Mavka. Follow us for continuous learning and improvement in your coding journey!

author photo

Max Dav

Coder with 15 years of experience, including 13 years at prominent tech companies before founding an independent software business.