Added an overload to Configuration.getSupportedBuiltInNames and Configuration.getSupportedBuiltInDirectiveNames that has a namingConvention parameter. This is useful for tooling as since 2.3.23 we support both camel case naming convention (like s?upperCase) and the legacy one (like s?upper_case). Furthermore the old 0 argument overload will now utilize Configuration.getNamingConvention() to only return the relevant names if it's not AUTO_DETECT_NAMING_CONVENTION.
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 144d70b..ab796b0 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -24,6 +24,8 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
 import freemarker.core.BuiltInsForDates.iso_BI;
 import freemarker.core.BuiltInsForDates.iso_utc_or_local_BI;
@@ -77,8 +79,11 @@
     protected Expression target;
     protected String key;
 
+    static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
+    static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
+    
     static final int NUMBER_OF_BIS = 259;
-    static final HashMap builtins = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
+    static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
     static {
         // Note that you must update NUMBER_OF_BIS if you add new items here!
         
@@ -272,7 +277,7 @@
         putBI("url", new BuiltInsForStringsEncoding.urlBI());
         putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI());
         putBI("values", new BuiltInsForHashes.valuesBI());
-        putBI("web_safe", "webSafe", (BuiltIn) builtins.get("html"));  // deprecated; use ?html instead
+        putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html"));  // deprecated; use ?html instead
         putBI("word_list", "wordList", new BuiltInsForStringsBasic.word_listBI());
         putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI());
         putBI("xml", new BuiltInsForStringsEncoding.xmlBI());
@@ -280,18 +285,22 @@
         putBI("groups", new BuiltInsForStringsRegexp.groupsBI());
         putBI("replace", new BuiltInsForStringsRegexp.replace_reBI());
         
-        if (NUMBER_OF_BIS < builtins.size()) {
-            throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + builtins.size());
+        if (NUMBER_OF_BIS < BUILT_INS_BY_NAME.size()) {
+            throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + BUILT_INS_BY_NAME.size());
         }
     }
     
     private static void putBI(String name, BuiltIn bi) {
-        builtins.put(name, bi);
+        BUILT_INS_BY_NAME.put(name, bi);
+        SNAKE_CASE_NAMES.add(name);
+        CAMEL_CASE_NAMES.add(name);
     }
 
-    private static void putBI(String name, String nameCamelCase, BuiltIn bi) {
-        builtins.put(name, bi);
-        builtins.put(nameCamelCase, bi);
+    private static void putBI(String nameSnakeCase, String nameCamelCase, BuiltIn bi) {
+        BUILT_INS_BY_NAME.put(nameSnakeCase, bi);
+        BUILT_INS_BY_NAME.put(nameCamelCase, bi);
+        SNAKE_CASE_NAMES.add(nameSnakeCase);
+        CAMEL_CASE_NAMES.add(nameCamelCase);
     }
     
     /**
@@ -303,7 +312,7 @@
     static BuiltIn newBuiltIn(int incompatibleImprovements, Expression target, Token keyTk,
             FMParserTokenManager tokenManager) throws ParseException {
         String key = keyTk.image;
-        BuiltIn bi = (BuiltIn) builtins.get(key);
+        BuiltIn bi = BUILT_INS_BY_NAME.get(key);
         if (bi == null) {
             StringBuilder buf = new StringBuilder("Unknown built-in: ").append(StringUtil.jQuote(key)).append(". ");
             
@@ -311,8 +320,8 @@
                     "Help (latest version): http://freemarker.org/docs/ref_builtins.html; "
                     + "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n" 
                     + "The alphabetical list of built-ins:");
-            List names = new ArrayList(builtins.keySet().size());
-            names.addAll(builtins.keySet());
+            List names = new ArrayList(BUILT_INS_BY_NAME.keySet().size());
+            names.addAll(BUILT_INS_BY_NAME.keySet());
             Collections.sort(names);
             char lastLetter = 0;
             
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index 826f29e..df95196 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -25,6 +25,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.TemplateDirectiveBody;
 import freemarker.template.TemplateException;
@@ -45,69 +46,101 @@
 
     // Can't be instantiated
     private _CoreAPI() { }
+
+    private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+            String commonName) {
+        allNames.add(commonName);
+        lcNames.add(commonName);
+        ccNames.add(commonName);
+    }
     
-    public static final Set/*<String>*/ BUILT_IN_DIRECTIVE_NAMES;
+    private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+            String lcName, String ccName) {
+        allNames.add(lcName);
+        allNames.add(ccName);
+        lcNames.add(lcName);
+        ccNames.add(ccName);
+    }
+    
+    public static final Set<String> ALL_BUILT_IN_DIRECTIVE_NAMES;
+    public static final Set<String> LEGACY_BUILT_IN_DIRECTIVE_NAMES;
+    public static final Set<String> CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
     static {
-        Set/*<String>*/ names = new TreeSet();
-        names.add("assign");
-        names.add("attempt");
-        names.add("autoEsc");
-        names.add("autoesc");
-        names.add("break");
-        names.add("call");
-        names.add("case");
-        names.add("comment");
-        names.add("compress");
-        names.add("default");
-        names.add("else");
-        names.add("elseif");
-        names.add("elseIf");
-        names.add("escape");
-        names.add("fallback");
-        names.add("flush");
-        names.add("foreach");
-        names.add("forEach");
-        names.add("ftl");
-        names.add("function");
-        names.add("global");
-        names.add("if");
-        names.add("import");
-        names.add("include");
-        names.add("items");
-        names.add("list");
-        names.add("local");
-        names.add("lt");
-        names.add("macro");
-        names.add("nested");
-        names.add("noautoesc");
-        names.add("noAutoEsc");
-        names.add("noescape");
-        names.add("noEscape");
-        names.add("noparse");
-        names.add("noParse");
-        names.add("nt");
-        names.add("outputformat");
-        names.add("outputFormat");
-        names.add("recover");
-        names.add("recurse");
-        names.add("return");
-        names.add("rt");
-        names.add("sep");
-        names.add("setting");
-        names.add("stop");
-        names.add("switch");
-        names.add("t");
-        names.add("transform");
-        names.add("visit");
-        BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(names);
+        Set<String> allNames = new TreeSet();
+        Set<String> lcNames = new TreeSet();
+        Set<String> ccNames = new TreeSet();
+        
+        addName(allNames, lcNames, ccNames, "assign");
+        addName(allNames, lcNames, ccNames, "attempt");
+        addName(allNames, lcNames, ccNames, "autoesc", "autoEsc");
+        addName(allNames, lcNames, ccNames, "break");
+        addName(allNames, lcNames, ccNames, "call");
+        addName(allNames, lcNames, ccNames, "case");
+        addName(allNames, lcNames, ccNames, "comment");
+        addName(allNames, lcNames, ccNames, "compress");
+        addName(allNames, lcNames, ccNames, "default");
+        addName(allNames, lcNames, ccNames, "else");
+        addName(allNames, lcNames, ccNames, "elseif", "elseIf");
+        addName(allNames, lcNames, ccNames, "escape");
+        addName(allNames, lcNames, ccNames, "fallback");
+        addName(allNames, lcNames, ccNames, "flush");
+        addName(allNames, lcNames, ccNames, "foreach", "forEach");
+        addName(allNames, lcNames, ccNames, "ftl");
+        addName(allNames, lcNames, ccNames, "function");
+        addName(allNames, lcNames, ccNames, "global");
+        addName(allNames, lcNames, ccNames, "if");
+        addName(allNames, lcNames, ccNames, "import");
+        addName(allNames, lcNames, ccNames, "include");
+        addName(allNames, lcNames, ccNames, "items");
+        addName(allNames, lcNames, ccNames, "list");
+        addName(allNames, lcNames, ccNames, "local");
+        addName(allNames, lcNames, ccNames, "lt");
+        addName(allNames, lcNames, ccNames, "macro");
+        addName(allNames, lcNames, ccNames, "nested");
+        addName(allNames, lcNames, ccNames, "noautoesc", "noAutoEsc");
+        addName(allNames, lcNames, ccNames, "noescape", "noEscape");
+        addName(allNames, lcNames, ccNames, "noparse", "noParse");
+        addName(allNames, lcNames, ccNames, "nt");
+        addName(allNames, lcNames, ccNames, "outputformat", "outputFormat");
+        addName(allNames, lcNames, ccNames, "recover");
+        addName(allNames, lcNames, ccNames, "recurse");
+        addName(allNames, lcNames, ccNames, "return");
+        addName(allNames, lcNames, ccNames, "rt");
+        addName(allNames, lcNames, ccNames, "sep");
+        addName(allNames, lcNames, ccNames, "setting");
+        addName(allNames, lcNames, ccNames, "stop");
+        addName(allNames, lcNames, ccNames, "switch");
+        addName(allNames, lcNames, ccNames, "t");
+        addName(allNames, lcNames, ccNames, "transform");
+        addName(allNames, lcNames, ccNames, "visit");
+        
+        ALL_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(allNames);
+        LEGACY_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(lcNames);
+        CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(ccNames);
     }
     
     /**
      * Returns the names of the currently supported "built-ins" ({@code expr?builtin_name}-like things).
-     * @return {@link Set} of {@link String}-s. 
+     * 
+     * @param namingConvention
+     *            One of {@link Configuration#AUTO_DETECT_NAMING_CONVENTION},
+     *            {@link Configuration#LEGACY_NAMING_CONVENTION}, and
+     *            {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it's
+     *            {@link Configuration#AUTO_DETECT_NAMING_CONVENTION} then the union of the names in all the naming
+     *            conventions is returned.
      */
-    public static Set/*<String>*/ getSupportedBuiltInNames() {
-        return Collections.unmodifiableSet(BuiltIn.builtins.keySet());
+    public static Set<String> getSupportedBuiltInNames(int namingConvention) {
+        Set<String> names;
+        if (namingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION) {
+            names = BuiltIn.BUILT_INS_BY_NAME.keySet();
+        } else if (namingConvention == Configuration.LEGACY_NAMING_CONVENTION) {
+            names = BuiltIn.SNAKE_CASE_NAMES;
+        } else if (namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+            names = BuiltIn.CAMEL_CASE_NAMES;
+        } else {
+            throw new IllegalArgumentException("Unsupported naming convention constant: " + namingConvention);
+        }
+        return Collections.unmodifiableSet(names);
     }
     
     public static void appendInstructionStackItem(TemplateElement stackEl, StringBuilder sb) {
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index bd7d46c..8be37db 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -3157,30 +3157,62 @@
     }
 
     /**
-     * Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
-     * writing, this information doesn't depend on the configuration options, so it could be a static method, but
-     * to be future-proof, it's an instance method. 
-     * 
-     * @return {@link Set} of {@link String}-s.
+     * Same as {@link #getSupportedBuiltInNames(int)} with argument {@link #getNamingConvention()}.
      * 
      * @since 2.3.20
      */
     public Set getSupportedBuiltInNames() {
-        return _CoreAPI.getSupportedBuiltInNames();
+        return getSupportedBuiltInNames(getNamingConvention());
+    }
+
+    /**
+     * Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
+     * writing, this information doesn't depend on the configuration options, so it could be a static method, but
+     * to be future-proof, it's an instance method. 
+     * 
+     * @param namingConvention
+     *            One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
+     *            {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
+     *            of the names in all the naming conventions is returned. 
+     * 
+     * @since 2.3.24
+     */
+    public Set<String> getSupportedBuiltInNames(int namingConvention) {
+        return _CoreAPI.getSupportedBuiltInNames(namingConvention);
     }
     
     /**
-     * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
-     * <tt>&lt;#directiveName ...&gt;</tt>.
-     * 
-     * @return {@link Set} of {@link String}-s.
+     * Same as {@link #getSupportedBuiltInDirectiveNames(int)} with argument {@link #getNamingConvention()}.
      * 
      * @since 2.3.21
      */
     public Set getSupportedBuiltInDirectiveNames() {
-        return _CoreAPI.BUILT_IN_DIRECTIVE_NAMES;
+        return getSupportedBuiltInDirectiveNames(getNamingConvention());
     }
 
+    /**
+     * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
+     * <tt>&lt;#directiveName ...&gt;</tt>.
+     * 
+     * @param namingConvention
+     *            One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
+     *            {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
+     *            of the names in all the naming conventions is returned. 
+     * 
+     * @since 2.3.24
+     */
+    public Set<String> getSupportedBuiltInDirectiveNames(int namingConvention) {
+        if (namingConvention == AUTO_DETECT_NAMING_CONVENTION) {
+            return _CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES;
+        } else if (namingConvention == LEGACY_NAMING_CONVENTION) {
+            return _CoreAPI.LEGACY_BUILT_IN_DIRECTIVE_NAMES;
+        } else if (namingConvention == CAMEL_CASE_NAMING_CONVENTION) {
+            return _CoreAPI.CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
+        } else {
+            throw new IllegalArgumentException("Unsupported naming convention constant: " + namingConvention);
+        }
+    }
+    
     private static String getRequiredVersionProperty(Properties vp, String properyName) {
         String s = vp.getProperty(properyName);
         if (s == null) {
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 6318f75..9c888d2 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26986,6 +26986,22 @@
             </listitem>
 
             <listitem>
+              <para>Added an overload to
+              <literal>Configuration.getSupportedBuiltInNames</literal> and
+              <literal>Configuration.getSupportedBuiltInDirectiveNames</literal>
+              that has a <literal>namingConvention</literal> parameter. This
+              is useful for tooling as since 2.3.23 we support both camel case
+              naming convention (like
+              <literal><replaceable>s</replaceable>?upperCase</literal>) and
+              the legacy one (like
+              <literal><replaceable>s</replaceable>?upper_case</literal>).
+              Furthermore the old 0 argument overload will now utilize
+              <literal>Configuration.getNamingConvention()</literal> to only
+              return the relevant names if it's not
+              <literal>AUTO_DETECT_NAMING_CONVENTION</literal>.</para>
+            </listitem>
+
+            <listitem>
               <para>Internal reworking to simplify the AST (the
               <literal>TemplateElement</literal> structure). The API related
               technically public API was marked as internal for a good while.
@@ -27356,6 +27372,22 @@
                 </listitem>
               </itemizedlist>
             </listitem>
+
+            <listitem>
+              <para>Added an overload to
+              <literal>Configuration.getSupportedBuiltInNames</literal> and
+              <literal>Configuration.getSupportedBuiltInDirectiveNames</literal>
+              that has a <literal>namingConvention</literal> parameter. This
+              is useful for tooling as since 2.3.23 we support both camel case
+              naming convention (like
+              <literal><replaceable>s</replaceable>?upperCase</literal>) and
+              the legacy one (like
+              <literal><replaceable>s</replaceable>?upper_case</literal>).
+              Furthermore the old 0 argument overload will now utilize
+              <literal>Configuration.getNamingConvention()</literal> to only
+              return the relevant names if it's not
+              <literal>AUTO_DETECT_NAMING_CONVENTION</literal>.</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>
diff --git a/src/test/java/freemarker/core/CamelCaseTest.java b/src/test/java/freemarker/core/CamelCaseTest.java
index ece5730..16232c1 100644
--- a/src/test/java/freemarker/core/CamelCaseTest.java
+++ b/src/test/java/freemarker/core/CamelCaseTest.java
@@ -256,8 +256,8 @@
         assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() {
 
             public void assertPair(String name1, String name2) {
-                BuiltIn bi1  = (BuiltIn) BuiltIn.builtins.get(name1);
-                BuiltIn bi2 = (BuiltIn) BuiltIn.builtins.get(name2);
+                BuiltIn bi1  = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name1);
+                BuiltIn bi2 = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name2);
                 assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't belong to the same BI object.",
                         bi1 == bi2);
             }
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 9e69d90..39ee7b6 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -32,6 +32,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 
 import org.junit.Test;
@@ -1640,6 +1641,46 @@
         }
     }
     
+    @Test
+    public void testGetSupportedBuiltInDirectiveNames() {
+        Configuration cfg = new Configuration();
+        
+        Set<String> allNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+        Set<String> lNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.LEGACY_NAMING_CONVENTION);
+        Set<String> cNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        
+        checkNamingConventionNameSets(allNames, lNames, cNames);
+        
+        for (String name : cNames) {
+            assertThat(name.toLowerCase(), isIn(lNames));
+        }
+    }
+
+    @Test
+    public void testGetSupportedBuiltInNames() {
+        Configuration cfg = new Configuration();
+        
+        Set<String> allNames = cfg.getSupportedBuiltInNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+        Set<String> lNames = cfg.getSupportedBuiltInNames(Configuration.LEGACY_NAMING_CONVENTION);
+        Set<String> cNames = cfg.getSupportedBuiltInNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        
+        checkNamingConventionNameSets(allNames, lNames, cNames);
+    }
+
+    private void checkNamingConventionNameSets(Set<String> allNames, Set<String> lNames, Set<String> cNames) {
+        for (String name : lNames) {
+            assertThat(allNames, hasItem(name));
+            assertTrue("Should be all-lowercase: " + name, name.equals(name.toLowerCase()));
+        }
+        for (String name : cNames) {
+            assertThat(allNames, hasItem(name));
+        }
+        for (String name : allNames) {
+            assertThat(name, anyOf(isIn(lNames), isIn(cNames)));
+        }
+        assertEquals(lNames.size(), cNames.size());
+    }
+    
     @SuppressWarnings("boxing")
     private void assertStartsWith(List<String> list, List<String> headList) {
         int index = 0;