@@ -446,6 +446,7 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
446446 Class <?>[] parameterTypes ;
447447 java .lang .reflect .Type [] parameterGenericTypes ;
448448 Map <String , Integer > parameterIndexes ;
449+ Object [] parameterDefaults ;
449450 if (cachedConstructor == null ) {
450451 constructor = findConstructor (cls );
451452
@@ -454,9 +455,11 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
454455 parameterGenericTypes = constructor .getGenericParameterTypes ();
455456
456457 parameterIndexes = new HashMap <>();
458+ parameterDefaults = new Object [constructor .getParameterCount ()];
457459 var annotations = constructor .getParameterAnnotations ();
458460 for (int i = 0 ; i < constructor .getParameterCount (); i ++) {
459- var name = getParameterName (annotations [i ]);
461+ var ann = getParameterAnnotation (annotations [i ]);
462+ var name = ann != null ? ann .name () : null ;
460463 if (name == null ) {
461464 // Fallbacks: record component name, then Java parameter name
462465 // (requires -parameters)
@@ -474,6 +477,10 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
474477 }
475478 }
476479 }
480+ // Prepare parsed defaults once and cache them
481+ if (ann != null && ann .useDefault ()) {
482+ parameterDefaults [i ] = parseDefault (ann .defaultValue (), parameterTypes [i ]);
483+ }
477484 parameterIndexes .put (name , i );
478485 }
479486
@@ -483,14 +490,16 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
483490 constructor ,
484491 parameterTypes ,
485492 parameterGenericTypes ,
486- parameterIndexes
493+ parameterIndexes ,
494+ parameterDefaults
487495 )
488496 );
489497 } else {
490498 constructor = cachedConstructor .constructor ();
491499 parameterTypes = cachedConstructor .parameterTypes ();
492500 parameterGenericTypes = cachedConstructor .parameterGenericTypes ();
493501 parameterIndexes = cachedConstructor .parameterIndexes ();
502+ parameterDefaults = cachedConstructor .parameterDefaults ();
494503 }
495504
496505 var parameters = new Object [parameterTypes .length ];
@@ -510,6 +519,13 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
510519 ).value ();
511520 }
512521
522+ // Apply cached defaults for missing parameters, if any
523+ for (int i = 0 ; i < parameters .length ; i ++) {
524+ if (parameters [i ] == null && parameterDefaults [i ] != null ) {
525+ parameters [i ] = parameterDefaults [i ];
526+ }
527+ }
528+
513529 try {
514530 return constructor .newInstance (parameters );
515531 } catch (InstantiationException
@@ -583,17 +599,60 @@ private static <T> Constructor<T> findConstructor(Class<T> cls)
583599 + "provide a record canonical constructor, or a single public constructor." );
584600 }
585601
586- private static String getParameterName (Annotation [] annotations ) {
602+ private static MaxMindDbParameter getParameterAnnotation (Annotation [] annotations ) {
587603 for (var annotation : annotations ) {
588604 if (!annotation .annotationType ().equals (MaxMindDbParameter .class )) {
589605 continue ;
590606 }
591- var paramAnnotation = (MaxMindDbParameter ) annotation ;
592- return paramAnnotation .name ();
607+ return (MaxMindDbParameter ) annotation ;
593608 }
594609 return null ;
595610 }
596611
612+ private static Object parseDefault (String value , Class <?> target ) {
613+ try {
614+ if (target .equals (Boolean .TYPE ) || target .equals (Boolean .class )) {
615+ return value .isEmpty () ? false : Boolean .parseBoolean (value );
616+ }
617+ if (target .equals (Byte .TYPE ) || target .equals (Byte .class )) {
618+ var v = value .isEmpty () ? 0 : Integer .parseInt (value );
619+ if (v < Byte .MIN_VALUE || v > Byte .MAX_VALUE ) {
620+ throw new DeserializationException (
621+ "Default value out of range for byte" );
622+ }
623+ return (byte ) v ;
624+ }
625+ if (target .equals (Short .TYPE ) || target .equals (Short .class )) {
626+ var v = value .isEmpty () ? 0 : Integer .parseInt (value );
627+ if (v < Short .MIN_VALUE || v > Short .MAX_VALUE ) {
628+ throw new DeserializationException (
629+ "Default value out of range for short" );
630+ }
631+ return (short ) v ;
632+ }
633+ if (target .equals (Integer .TYPE ) || target .equals (Integer .class )) {
634+ return value .isEmpty () ? 0 : Integer .parseInt (value );
635+ }
636+ if (target .equals (Long .TYPE ) || target .equals (Long .class )) {
637+ return value .isEmpty () ? 0L : Long .parseLong (value );
638+ }
639+ if (target .equals (Float .TYPE ) || target .equals (Float .class )) {
640+ return value .isEmpty () ? 0.0f : Float .parseFloat (value );
641+ }
642+ if (target .equals (Double .TYPE ) || target .equals (Double .class )) {
643+ return value .isEmpty () ? 0.0d : Double .parseDouble (value );
644+ }
645+ if (target .equals (String .class )) {
646+ return value ;
647+ }
648+ } catch (NumberFormatException e ) {
649+ throw new DeserializationException (
650+ "Invalid default '" + value + "' for type " + target .getSimpleName (), e );
651+ }
652+ throw new DeserializationException (
653+ "Defaults are only supported for primitives, boxed types, and String." );
654+ }
655+
597656 private long nextValueOffset (long offset , int numberToSkip )
598657 throws InvalidDatabaseException {
599658 if (numberToSkip == 0 ) {
0 commit comments