@@ -388,8 +388,25 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
388388 parameterIndexes = new HashMap <>();
389389 var annotations = constructor .getParameterAnnotations ();
390390 for (int i = 0 ; i < constructor .getParameterCount (); i ++) {
391- var parameterName = getParameterName (cls , i , annotations [i ]);
392- parameterIndexes .put (parameterName , i );
391+ var name = getParameterName (annotations [i ]);
392+ if (name == null ) {
393+ // Fallbacks: record component name, then Java parameter name
394+ // (requires -parameters)
395+ if (cls .isRecord ()) {
396+ name = cls .getRecordComponents ()[i ].getName ();
397+ } else {
398+ var param = constructor .getParameters ()[i ];
399+ if (param .isNamePresent ()) {
400+ name = param .getName ();
401+ } else {
402+ throw new ParameterNotFoundException (
403+ "Parameter name for index " + i + " on class " + cls .getName ()
404+ + " is not available. Annotate with @MaxMindDbParameter "
405+ + "or compile with -parameters." );
406+ }
407+ }
408+ }
409+ parameterIndexes .put (name , i );
393410 }
394411
395412 this .constructors .put (
@@ -457,34 +474,56 @@ private <T> CachedConstructor<T> getCachedConstructor(Class<T> cls) {
457474 private static <T > Constructor <T > findConstructor (Class <T > cls )
458475 throws ConstructorNotFoundException {
459476 var constructors = cls .getConstructors ();
477+ // Prefer explicitly annotated constructor
460478 for (var constructor : constructors ) {
461479 if (constructor .getAnnotation (MaxMindDbConstructor .class ) == null ) {
462480 continue ;
463481 }
464482 @ SuppressWarnings ("unchecked" )
465- Constructor < T > constructor2 = (Constructor <T >) constructor ;
466- return constructor2 ;
483+ var selected = (Constructor <T >) constructor ;
484+ return selected ;
467485 }
468486
469- throw new ConstructorNotFoundException ("No constructor on class " + cls .getName ()
470- + " with the MaxMindDbConstructor annotation was found." );
487+ // Fallback for records: use canonical constructor
488+ if (cls .isRecord ()) {
489+ try {
490+ var components = cls .getRecordComponents ();
491+ var types = new Class <?>[components .length ];
492+ for (int i = 0 ; i < components .length ; i ++) {
493+ types [i ] = components [i ].getType ();
494+ }
495+ var c = cls .getDeclaredConstructor (types );
496+ @ SuppressWarnings ("unchecked" )
497+ var selected = (Constructor <T >) c ;
498+ return selected ;
499+ } catch (NoSuchMethodException e ) {
500+ // ignore and continue to next fallback
501+ }
502+ }
503+
504+ // Fallback for single-constructor classes
505+ if (constructors .length == 1 ) {
506+ var only = constructors [0 ];
507+ @ SuppressWarnings ("unchecked" )
508+ var selected = (Constructor <T >) only ;
509+ return selected ;
510+ }
511+
512+ throw new ConstructorNotFoundException (
513+ "No usable constructor on class " + cls .getName ()
514+ + ". Annotate a constructor with MaxMindDbConstructor, "
515+ + "provide a record canonical constructor, or a single public constructor." );
471516 }
472517
473- private static <T > String getParameterName (
474- Class <T > cls ,
475- int index ,
476- Annotation [] annotations
477- ) throws ParameterNotFoundException {
518+ private static String getParameterName (Annotation [] annotations ) {
478519 for (var annotation : annotations ) {
479520 if (!annotation .annotationType ().equals (MaxMindDbParameter .class )) {
480521 continue ;
481522 }
482523 var paramAnnotation = (MaxMindDbParameter ) annotation ;
483524 return paramAnnotation .name ();
484525 }
485- throw new ParameterNotFoundException (
486- "Constructor parameter " + index + " on class " + cls .getName ()
487- + " is not annotated with MaxMindDbParameter." );
526+ return null ;
488527 }
489528
490529 private long nextValueOffset (long offset , int numberToSkip )
0 commit comments