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.