Skip to content

Conversation

@sushantmane
Copy link
Contributor

[common] Fix NumberFormatException when parsing empty string properties

Modified VeniceProperties numeric parsing methods to handle empty strings gracefully: -
getLong/getInt/getDouble/getSizeInBytes methods with default values now return the default
when property value is empty or whitespace-only - Methods without default values throw
descriptive VeniceException instead of NumberFormatException - getOptionalInt returns
Optional.empty() for empty values

Code changes

  • Added new code behind a config. If so list the config names and their default values in the PR description.
  • Introduced new log lines.
    • Confirmed if logs need to be rate limited to avoid excessive logging.

Concurrency-Specific Checks

Both reviewer and PR author to verify

  • Code has no race conditions or thread safety issues.
  • Proper synchronization mechanisms (e.g., synchronized, RWLock) are used where needed.
  • No blocking calls inside critical sections that could lead to deadlocks or performance degradation.
  • Verified thread-safe collections are used (e.g., ConcurrentHashMap, CopyOnWriteArrayList).
  • Validated proper exception handling in multi-threaded code to avoid silent thread termination.

How was this PR tested?

  • New unit tests added.
  • New integration tests added.
  • Modified or extended existing tests.
  • Verified backward compatibility (if applicable).

Does this PR introduce any user-facing or breaking changes?

  • No. You can skip the rest of this section.
  • Yes. Clearly explain the behavior change and its impact.

Copilot AI review requested due to automatic review settings January 13, 2026 00:24
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix NumberFormatException when parsing empty string properties in VeniceProperties. The changes modify numeric parsing methods (getLong, getInt, getDouble, getSizeInBytes, and getOptionalInt) to handle empty or whitespace-only strings gracefully by either returning default values or throwing descriptive VeniceException instead of NumberFormatException. The PR also includes comprehensive test coverage for these scenarios and unrelated logging level changes in AsyncStoreChangeNotifier.

Changes:

  • Modified VeniceProperties numeric parsing methods to check for empty/whitespace-only values before parsing
  • Added comprehensive test coverage for empty string handling in all affected methods
  • Changed logging level from info to debug in AsyncStoreChangeNotifier (unrelated to main purpose)

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.

File Description
internal/venice-client-common/src/main/java/com/linkedin/venice/utils/VeniceProperties.java Added empty string checks to getLong, getInt, getDouble, getSizeInBytes, and getOptionalInt methods
internal/venice-client-common/src/test/java/com/linkedin/venice/utils/VenicePropertiesTest.java Added comprehensive test cases for empty string handling in numeric parsing methods
internal/venice-common/src/main/java/com/linkedin/venice/meta/AsyncStoreChangeNotifier.java Changed logging level from info to debug for task registration and execution messages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Modified VeniceProperties numeric parsing methods to handle empty strings gracefully:
- getLong/getInt/getDouble/getSizeInBytes methods with default values now return the default when property value is empty or whitespace-only
- Methods without default values throw descriptive VeniceException instead of NumberFormatException
- getOptionalInt returns Optional.empty() for empty values

Added comprehensive TestNG tests covering all modified methods with empty string, whitespace-only, valid, and missing property scenarios.
- Fix whitespace handling bug in numeric parsing methods by trimming
  values before passing to parse methods (Long.parseLong, Integer.parseInt,
  Double.parseDouble, convertSizeFromLiteral)
- Add comprehensive test coverage for values with leading/trailing whitespace
- Revert unrelated logging level changes in AsyncStoreChangeNotifier
- Fix SpotBugs CNT_ROUGH_CONSTANT_VALUE violation by changing test value
  from 3.14 to 3.15

All tests pass and SpotBugs checks pass with 0 violations.
Previously each method called trim() twice:
- Once in the isEmpty check: value.trim().isEmpty()
- Once for parsing: Long.parseLong(value.trim())

Now each method:
1. Checks for null first
2. Calls trim() once and stores in trimmedValue variable
3. Checks if trimmedValue is empty
4. Parses trimmedValue

This reduces string operations and improves performance while maintaining
the same behavior.
@sushantmane sushantmane force-pushed the fix-number-format-exception-in-vpj branch from b5f6211 to 0ef4ee9 Compare January 13, 2026 19:32
public long getLong(String name, long defaultValue) {
if (containsKey(name)) {
return Long.parseLong(get(name));
String value = get(name);
Copy link
Contributor

@majisourav99 majisourav99 Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String value = props.get(name);
if (value == null) {
return defaultValue;
}
....

@sushantmane
Copy link
Contributor Author

sushantmane commented Jan 13, 2026

Thanks @arjun4084346 & @majisourav99 for the review!

Copilot AI review requested due to automatic review settings January 13, 2026 22:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (containsKey(name)) {
return Long.parseLong(get(name));
} else {
if (!containsKey(name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why double checking containsKey? you already check it in getTrimmedValueOrNull

Addresses review feedback by eliminating redundant containsKey checks in
getTrimmedValueOrNull method. Changed from using get() method (which internally
checks containsKey) to directly using props.get(), reducing map lookups from
3 to 1 in the happy path. Also deferred containsKey checks in getLong/getInt/
getDouble/getSizeInBytes methods to only execute when distinguishing between
missing keys and empty values.
} else {
throw new UndefinedPropertyException(name);
String trimmedValue = getTrimmedValueOrNull(name);
if (trimmedValue == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for minor comments; but i think L365-L368 can also go inside method getTrimmedValueOrNull and it can be renamed like getTrimmedNonEmptyValueOrThrowException

@manujose0
Copy link
Contributor

From Claude Code PR Review

⏺ Pull Request Review: #2372

Title: [common] Fix NumberFormatException when parsing empty string properties

Summary

This PR fixes a bug where empty string property values cause NumberFormatException when parsing numeric values in VeniceProperties.java. The fix centralizes empty/null handling across all numeric getter methods.


Changes Overview

Files Modified: 3 files (+61/-49 lines)

  • VeniceProperties.java - Core logic changes
  • VenicePropertiesTest.java - Test coverage
  • TestChangelogConsumer.java - Integration tests

What I Like ✓

  1. Smart Refactoring: Introduces getTrimmedValueOrNull() helper that centralizes the logic for handling null/empty/whitespace strings across 6+ numeric parsing methods
  2. Consistent Behavior: All numeric getters (getLong(), getInt(), getDouble(), getSizeInBytes()) now handle empty strings uniformly
  3. Clear Error Semantics:
    - Missing properties → UndefinedPropertyException
    - Empty/whitespace properties → VeniceException
    - This distinction is valuable for debugging
  4. Performance Optimization: Final commit removes redundant containsKey() checks that would cause duplicate map lookups
  5. Good Test Coverage: Tests include edge cases like whitespace-only strings

Potential Concerns / Questions

  1. Breaking Change?: The behavior change for empty strings (now throws exception vs previously possibly using default) could affect existing deployments. Was this considered? Are there callers relying on the previous behavior?
  2. Error Messages: When throwing VeniceException for empty strings, does the error message clearly indicate that an empty string was the problem? This would help operators diagnose misconfigurations.
  3. Null vs Empty Semantics: Should empty strings be treated as "not provided" and use defaults, or is throwing an exception the right choice? The current implementation treats empty as invalid, which seems reasonable but worth documenting.
  4. getSizeInBytes() lowercase conversion: The change to convert to lowercase after trimming is good, but verify that all size literals are case-insensitive in the docs.

Code Quality: 8.5/10

This is a solid defensive programming fix that improves robustness. The refactoring reduces code duplication and makes the codebase more maintainable. The main consideration is ensuring this behavior change doesn't break existing configurations.

Recommendation: ✅ Approve with minor consideration for communication about the behavior change in release notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants