private Object replaceVariables(
            String paramString, DocumentContext documentContext, String taskId, int depth) {
        var matcher = PATTERN.matcher(paramString);
        var replacements = new LinkedList<Replacement>();
        while (matcher.find()) {
            var start = matcher.start();
            var end = matcher.end();
            var match = paramString.substring(start, end);
            String paramPath = match.substring(2, match.length() - 1);
            paramPath = replaceVariables(paramPath, documentContext, taskId, depth + 1).toString();
            // if the paramPath is blank, meaning no value in between ${ and }
            // like ${}, ${  } etc, set the value to empty string
            if (StringUtils.isBlank(paramPath)) {
                replacements.add(new Replacement("", start, end));
                continue;
            }
            if (EnvUtils.isEnvironmentVariable(paramPath)) {
                String sysValue = EnvUtils.getSystemParametersValue(paramPath, taskId);
                if (sysValue != null) {
                    replacements.add(new Replacement(sysValue, start, end));
                }
            } else {
                try {
                    replacements.add(new Replacement(documentContext.read(paramPath), start, end));
                } catch (Exception e) {
                    LOGGER.warn(
                            "Error reading documentContext for paramPath: {}. Exception: {}",
                            paramPath,
                            e);
                    replacements.add(new Replacement(null, start, end));
                }
            }
        }
        if (replacements.size() == 1
                && replacements.getFirst().getStartIndex() == 0
                && replacements.getFirst().getEndIndex() == paramString.length()
                && depth == 0) {
            return replacements.get(0).getReplacement();
        }
        Collections.sort(replacements);
        var builder = new StringBuilder(paramString);
        for (int i = replacements.size() - 1; i >= 0; i--) {
            var replacement = replacements.get(i);
            builder.replace(
                    replacement.getStartIndex(),
                    replacement.getEndIndex(),
                    Objects.toString(replacement.getReplacement()));
        }
        return builder.toString().replaceAll("\\$\\$\\{", "\\${");
    }
Used to parse and resolve the JSONPath bindings in the workflow and task definitions.
core/src/main/java/com/netflix/conductor/core/utils/ParametersUtils.java