Confirmed that MessageSource doesn't support named parameters, only numbered ones, at least in the Spring version I'm using (from the pom.xml):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath />
</parent>
I've debugged a test using a ReloadableResourceBundleMessageSource messageSource and here's the result.
Java code line causing the exception:
String imsg = messageSource.getMessage("constraints.Size.message", new Object[] { 0, 255 }, Locale.getDefault());
Content of messages_en.properties:
constraints.Size.message=A message specifying min: {min} and max: {max} parameters
Exception Stack trace:
java.lang.IllegalArgumentException: can't parse argument number: min
at java.base/java.text.MessageFormat.makeFormat(MessageFormat.java:1451)
at java.base/java.text.MessageFormat.applyPattern(MessageFormat.java:491)
at java.base/java.text.MessageFormat.<init>(MessageFormat.java:390)
at org.springframework.context.support.MessageSourceSupport.createMessageFormat(MessageSourceSupport.java:159)
at org.springframework.context.support.ReloadableResourceBundleMessageSource$PropertiesHolder.getMessageFormat(ReloadableResourceBundleMessageSource.java:627)
at org.springframework.context.support.ReloadableResourceBundleMessageSource.resolveCode(ReloadableResourceBundleMessageSource.java:206)
at org.springframework.context.support.AbstractMessageSource.getMessageInternal(AbstractMessageSource.java:224)
at org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:153)
Delving into org.springframework.context.support.AbstractMessageSource.getMessageInternal():
/**
* Resolve the given code and arguments as message in the given Locale,
* returning {@code null} if not found. Does <i>not</i> fall back to
* the code as default message. Invoked by {@code getMessage} methods.
* @param code the code to lookup up, such as 'calculator.noRateSet'
* @param args array of arguments that will be filled in for params
* within the message
* @param locale the locale in which to do the lookup
* @return the resolved message, or {@code null} if not found
* @see #getMessage(String, Object[], String, Locale)
* @see #getMessage(String, Object[], Locale)
* @see #getMessage(MessageSourceResolvable, Locale)
* @see #setUseCodeAsDefaultMessage
*/
@Nullable
protected String getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable Locale locale) {
if (code == null) {
return null;
}
if (locale == null) {
locale = Locale.getDefault();
}
Object[] argsToUse = args;
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
// Optimized resolution: no arguments to apply,
// therefore no MessageFormat needs to be involved.
// Note that the default implementation still uses MessageFormat;
// this can be overridden in specific subclasses.
String message = resolveCodeWithoutArguments(code, locale);
if (message != null) {
return message;
}
}
else {
// Resolve arguments eagerly, for the case where the message
// is defined in a parent MessageSource but resolvable arguments
// are defined in the child MessageSource.
argsToUse = resolveArguments(args, locale);
MessageFormat messageFormat = resolveCode(code, locale);
if (messageFormat != null) {
synchronized (messageFormat) {
return messageFormat.format(argsToUse);
}
}
}
// Check locale-independent common messages for the given message code.
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}
// Not found -> check parent, if any.
return getMessageFromParent(code, argsToUse, locale);
}
you can see that when the code fails deep into resolveCode(code, locale), the arguments argsToUse have not even been passed to it. So there's no way that by changing them for instance to a map, you can force a different behaviour from the MessageResource object.
I think you might want to use a custom message interpolator, the same one used by whatever library is implementing the javax.validation, which is patently able to resolve named parameters.