-
Notifications
You must be signed in to change notification settings - Fork 49
Fix enum decoding issue and improve error message #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
oschwald
commented
Dec 6, 2025
- Fix enum pointer decoding with @MaxMindDbCreator
- Update maxmind-db test data submodule
- Improve error messages for constructor argument mismatches
When an enum with @MaxMindDbCreator was accessed via a pointer in the database, decoding failed with ConstructorNotFoundException. This affected enums like ConnectionType in GeoIP2 when data was deduplicated via pointers. The bug was in requiresLookupContext() which called loadConstructorMetadata() before checking for creator methods. Enums don't have usable public constructors, causing the exception. Fix: - Add cls.isEnum() to early-return checks (zero overhead for enums) - Add @MaxMindDbCreator check for non-enum classes with creators - Add negative caching to getCachedCreator() to avoid repeated method scanning for classes without creators Fixes: maxmind/GeoIP2-java#644 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When constructor invocation fails with IllegalArgumentException, the error diagnostic now: 1. Reports null-to-primitive issues: "null value for primitive double parameter 'ip_risk'" - this identifies the actual root cause when a database field is missing and the model uses a primitive type. 2. Uses proper type compatibility checks for boxed/primitive pairs. Previously, Boolean/boolean pairs were incorrectly reported as type mismatches even though auto-unboxing handles them correctly. This makes debugging much easier by pointing to the actual problem instead of reporting misleading type mismatches. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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 fixes a bug where enums with @MaxMindDbCreator would throw exceptions when data was stored via pointers in the database, and improves error messages for constructor argument mismatches.
Key Changes:
- Fixed enum decoding by checking for creator methods before loading constructor metadata in
requiresLookupContext() - Enhanced error messages to distinguish between null-to-primitive issues and type mismatches
- Added comprehensive test coverage for enum pointer decoding and error message improvements
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/test/resources/maxmind-db | Updated test data submodule to include new test cases |
| src/test/java/com/maxmind/db/ReaderTest.java | Added tests for enum creator with pointer data and null-to-primitive error messages; corrected expected IPv6 address format |
| src/main/java/com/maxmind/db/Decoder.java | Fixed enum pointer decoding logic, improved error messages, and added primitive/boxed type checking |
| CHANGELOG.md | Documented bug fix and error message improvements |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (primitive == boolean.class) { | ||
| return Boolean.class; | ||
| } | ||
| if (primitive == byte.class) { | ||
| return Byte.class; | ||
| } | ||
| if (primitive == short.class) { | ||
| return Short.class; | ||
| } | ||
| if (primitive == int.class) { | ||
| return Integer.class; | ||
| } | ||
| if (primitive == long.class) { | ||
| return Long.class; | ||
| } | ||
| if (primitive == float.class) { | ||
| return Float.class; | ||
| } | ||
| if (primitive == double.class) { | ||
| return Double.class; | ||
| } | ||
| if (primitive == char.class) { | ||
| return Character.class; | ||
| } | ||
| return primitive; |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The boxedType method uses a chain of if statements to map primitive types to their boxed equivalents. Consider using a switch expression for better readability and maintainability, similar to the pattern used in ConnectionTypeEnum.fromString().
| if (primitive == boolean.class) { | |
| return Boolean.class; | |
| } | |
| if (primitive == byte.class) { | |
| return Byte.class; | |
| } | |
| if (primitive == short.class) { | |
| return Short.class; | |
| } | |
| if (primitive == int.class) { | |
| return Integer.class; | |
| } | |
| if (primitive == long.class) { | |
| return Long.class; | |
| } | |
| if (primitive == float.class) { | |
| return Float.class; | |
| } | |
| if (primitive == double.class) { | |
| return Double.class; | |
| } | |
| if (primitive == char.class) { | |
| return Character.class; | |
| } | |
| return primitive; | |
| return switch (primitive.getName()) { | |
| case "boolean" -> Boolean.class; | |
| case "byte" -> Byte.class; | |
| case "short" -> Short.class; | |
| case "int" -> Integer.class; | |
| case "long" -> Long.class; | |
| case "float" -> Float.class; | |
| case "double" -> Double.class; | |
| case "char" -> Character.class; | |
| default -> primitive; | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love hardcoding the string values like this. I am going to reject this approach. Another option would be a static map, but the original seems fine, if a bit verbose.