Custom Validator Version 14 - Multiple Messages

Anyone implemented multiple messages throwing by a single validator? As per documentation We have to add the message at translations in new version(we didn’t change during 13 version, so changing now in version 14), rather the validator throwing the message based on selections
The return throws single message, I passed subkey per documentation, but it’s displaying special characters, I believe I am not configuring or utilizing the subkey correctly.


The finalMessage in the createViolation call is the key that will be used to look up the translation. As to what is returned, I’d have to see your configuration. IT could be a number of things.

Thanks for the response. So here is my code which has multiple messages to display some are dynamic messages, based on that document the message will display duplicate id’s

If you see I am using createViolation(Map)
I added at translation like below( I believe this is not right way, but I don’t want to add a message here as this is dynamic)
audience-validation#BlankAcct: ‘’"

The violation message is added to map with a key, my understanding is this message will be displayed based on key and looks up in translation, however it’s not how it’s behaving

two issues to resolve, how can we send the message instead of adding at translation, the reason is messages are dynamic as you see in code in message we send duplicate id’s, we can’t have them at translation- is there a way ?

import org.apache.commons.lang.StringUtils;
import org.apache.tika.Tika;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AudienceValidator implements Validator {
private static final Logger L = LoggerFactory.getLogger(AudienceValidator.class);
private static final String PROPERTY_ACC_NUM_LIST = “staplescms:accountNumberList”;

public Optional<Violation> validate(ValidationContext validationContext, String value){
    String errorMsg = null;
    List<String> messages = new ArrayList<>();
    Map<String, String> errorMessages = new HashMap<>();
    try {

        Node mod = validationContext.getDocumentNode();
        Node contentNode = mod.getNode(PROPERTY_ACC_NUM_LIST);
        String mimeType = contentNode.getProperty("jcr:mimeType").getString();
        Binary binary = contentNode.getProperty("jcr:data"). getBinary();

        Tika tika = new Tika();

           //"Upload only Excel File (.xlsx)";
            return Optional.of(validationContext.createViolation());//added at translation without subkey

        }else {
            final InputStream in = binary.getStream();
            String text = tika.parseToString(in);
            String customerIds = text.trim();

            List<String> blankCustIds = new ArrayList<>();
            List<String> inValidCustIds = new ArrayList<String>();
            List<String> custIds = new ArrayList<String>();

            if (StringUtils.isNotBlank(customerIds)) {

                if ("...please wait while we process your request...".equals(customerIds)) {
                    L.debug("Processing redirect case : \"...please wait while we process your request...\".");
                String lines[] = customerIds.split("\\r?\\n");

                for (int i=0;i<lines.length;i++) {
                    String s = lines[i];
                    if (s.contains("Sheet") || s.contains("CUST")) {
                        //do nothing
                    } else {
                        if((StringUtils.isNotBlank(s) || StringUtils.isNotEmpty(s)) && s.contains("\t")){
                            String individualLines[] = s.split("\\t");
                            String customerId = individualLines[1].trim();

                            if(StringUtils.isBlank(customerId)) {
                            }else {
                                Pattern pattern = Pattern.compile("[0-9]*");
                                Matcher matcher = pattern.matcher(customerId);
                                if(!matcher.matches()) {
                                }else {

                if(!blankCustIds.isEmpty()) {
                    errorMsg = "Customer Ids are blank for the following rows:"+blankCustIds;
                    errorMessages.put("BlankAcct", errorMsg);
                if(!inValidCustIds.isEmpty()) {
                    errorMsg = "The following invalid Customer Ids are present :"+inValidCustIds;
                    errorMessages.put("InvalidAcct", errorMsg);
                if(custIds.isEmpty()) {
                    errorMsg = "The Excel dose not have valid enteries/No Enteries is present";
                    errorMessages.put("InvalidExcel", errorMsg);
                }else {
                    Set<String> duplicateCustIds = findDuplicateInStream(custIds);
                    if(!duplicateCustIds.isEmpty()) {
                        L.debug("The input has the following duplicate customer ids:"+duplicateCustIds);
                        errorMsg = "The input has the following duplicate customer ids:"+duplicateCustIds;
                        errorMessages.put("Duplicate", errorMsg);


                return Optional.of(validationContext.createViolation(errorMessages));

    } catch (RepositoryException e) {
        L.error("FAILURE!", e);
        errorMsg = "The Excel is not valid .Please upload the Excel in the format Attached \'Audience_Sample.xlsx\'";
        return Optional.of(validationContext.createViolation());

    } catch (Exception  e) {
        L.error("Failed to Validate Excel Fields.", e);
        errorMsg = "The Excel is not valid .Please upload the Excel in the format Attached \'Audience_Sample.xlsx\'";
        return Optional.of(validationContext.createViolation());
    return Optional.empty();

public Set<String> findDuplicateInStream(List<String> list) {
    Map<String, Long> counts =
            .collect(Collectors.groupingBy(s -> s, Collectors.counting()));

    return counts.entrySet().stream()
            .filter(entry -> entry.getValue() > 1)


using the createViolation(Map) method won’t work. Those are parameters for the message, not a key to find the message. See org.onehippo.repository.l10n.ResourceBundle#getString(java.lang.String, java.util.Map<java.lang.String,java.lang.String>).

One variable in the String can be replaced by the parameterName and parameterValue. The default definition of a variable is ${variableName}. Convenience method for Strings with only one variable. If there are multiple variables use getString(String, Map).

What about displaying multiple messages from single Validator? Looks like this will work only for single message as we are able to return single violation

Indeed, one message per validator. I guess you can either write a longer message with all information, or split the functionality into more validators.


you can use a parameterized message

messageKey=${parameter} lorem ipsum ${parameter2}

disclaimer, I’ve never used this myself, but this is what the javadoc says.