diff --git a/examples/vollt_examples/adql/parse/A_SimpleQueryParsing.java b/examples/vollt_examples/adql/parse/A_SimpleQueryParsing.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0e98dd143f2c22573b6df981bcf2a889c55ffd0
--- /dev/null
+++ b/examples/vollt_examples/adql/parse/A_SimpleQueryParsing.java
@@ -0,0 +1,49 @@
+package vollt_examples.adql.parse;
+
+import adql.parser.ADQLParser;
+import adql.parser.grammar.ParseException;
+import adql.query.ADQLQuery;
+
+/**
+ * Minimal lines required to parse an ADQL query.
+ *
+ * @author Grégory Mantelet (CDS)
+ * @version 08/2019
+ */
+public class A_SimpleQueryParsing {
+
+	public static void main(final String[] args) {
+
+		// Input query:
+		final String QUERY = "Select name, ra || ' - ' || dec as \"Position\"\nFrom data\nWhere Contains(Point('ICRS', ra, dec), Circle('ICRS', 10, 5, 1)) = 1\nOrder By name;";
+		System.out.println("\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// 1. CREATE A PARSER:
+			ADQLParser parser = new ADQLParser();
+			/*
+			 * To create a parser of a specific version of the ADQL grammar:
+			 *
+			 *   import adql.parser.ADQLParser.ADQLVersion;
+			 *   [...]
+			 *   ADQLParser parser = new ADQLParser(ADQLVersion.V2_0);
+			 *
+			 */
+
+			// 2. PARSE AN ADQL QUERY:
+			ADQLQuery query = parser.parseQuery(QUERY);
+
+			System.out.println("\n((i)) Correct ADQL query ((i))");
+
+			System.out.println("\n((i)) As interpreted: ((i))\n    " + query.toADQL().replaceAll("\n", "\n    "));
+
+		}
+		// 3. EVENTUALLY DEAL WITH ERRORS:
+		catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getMessage());
+		}
+
+	}
+
+}
diff --git a/examples/vollt_examples/adql/parse/B_SimpleClauseParsing.java b/examples/vollt_examples/adql/parse/B_SimpleClauseParsing.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3ca4d62e9fe3d0da8704764eb59715f1fb21bdb
--- /dev/null
+++ b/examples/vollt_examples/adql/parse/B_SimpleClauseParsing.java
@@ -0,0 +1,49 @@
+package vollt_examples.adql.parse;
+
+import adql.parser.ADQLParser;
+import adql.parser.grammar.ParseException;
+import adql.query.ClauseConstraints;
+
+/**
+ * Minimal lines required to parse an ADQL clause (here: <code>WHERE</code>).
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 08/2019
+ */
+public class B_SimpleClauseParsing {
+
+	public static void main(final String[] args) {
+
+		// Input ADQL expression:
+		final String CLAUSE = "Where Contains(Point('ICRS', ra, dec), Circle('ICRS', 10, 5, 1)) = 1";
+		System.out.println("\n    " + CLAUSE);
+
+		try {
+
+			// 1. CREATE A PARSER:
+			ADQLParser parser = new ADQLParser();
+			/*
+			 * To create a parser of a specific version of the ADQL grammar:
+			 *
+			 *   import adql.parser.ADQLParser.ADQLVersion;
+			 *   [...]
+			 *   ADQLParser parser = new ADQLParser(ADQLVersion.V2_0);
+			 *
+			 */
+
+			// 2. PARSE A SPECIFIC ADQL CLAUSE:
+			ClauseConstraints constraints = parser.parseWhere(CLAUSE);
+
+			System.out.println("\n((i)) Correct WHERE clause ((i))");
+
+			System.out.println("\n((i)) As interpreted: `" + constraints.toADQL() + "` ((i))");
+
+		}
+		// 3. EVENTUALLY DEAL WITH ERRORS:
+		catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT CLAUSE! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getPosition() + " " + ex.getMessage());
+		}
+
+	}
+
+}
diff --git a/examples/vollt_examples/adql/parse/C_HandleParseException.java b/examples/vollt_examples/adql/parse/C_HandleParseException.java
new file mode 100644
index 0000000000000000000000000000000000000000..36ca42546418d1fbc4716642a94245ec35698415
--- /dev/null
+++ b/examples/vollt_examples/adql/parse/C_HandleParseException.java
@@ -0,0 +1,172 @@
+package vollt_examples.adql.parse;
+
+import java.util.HashSet;
+
+import adql.db.DBChecker;
+import adql.db.DBTable;
+import adql.db.exception.UnresolvedIdentifiersException;
+import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
+import adql.parser.feature.FeatureSet;
+import adql.parser.grammar.ParseException;
+import adql.query.TextPosition;
+
+/**
+ * Examples and explanations on how to handle with different kinds of parsing
+ * error.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 08/2019
+ */
+public class C_HandleParseException {
+
+	public static void main(final String[] args) {
+
+		////////////////////////////////////////////////////////////////////////
+		//                    CASE 1/3: Simple error                          //
+		////////////////////////////////////////////////////////////////////////
+
+		// Input query (i.e. SELECT keyword missing):
+		String QUERY = "name, ra || ' - ' || dec as \"Position\"\nFrom data\nWhere Contains(Point('ICRS', ra, dec), Circle('ICRS', 10, 5, 1)) = 1\nOrder By name;";
+		System.out.println("\n##### QUERY #1 #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// Parse the query:
+			(new ADQLParser()).parseQuery(QUERY);
+
+			System.err.println("((X)) This message should never be visible! ((X)");
+
+		} catch(ParseException ex) {
+
+			// Full name of the caught exception:
+			final String CLASS = ex.getClass().getName();
+			/*
+			 * NOTES:
+			 *   ParseException is the root type of all possible exceptions that
+			 *   can possibly be generated by an ADQLParser. Special types of
+			 *   ParseException are going to be demonstrated with other examples
+			 *   below.
+			 */
+
+			// Position of the error inside the input ADQL query:
+			final TextPosition POSITION = ex.getPosition();
+			/*
+			 * NOTES:
+			 *   A TextPosition keeps the begin and end line and column numbers.
+			 *   They can be got individually directly with the attributes:
+			 *     - TextPosition.beginLine
+			 *     - TextPosition.endLine
+			 *     - TextPosition.beginColumn
+			 *     - TextPosition.endColumn
+			 *
+			 *   TextPosition.toString() will conveniently print all these
+			 *   information in a string like: `[l.1 c.5 - l.1 c.9]`.
+			 */
+
+			// Error message
+			final String MESSAGE = ex.getMessage();
+			/*
+			 * NOTES:
+			 *   A ParseException is still an error. So a message with
+			 *   a human description of the error is also available.
+			 */
+
+			System.out.println("\n((X)) INCORRECT QUERY! " + CLASS + " ((X))\n" + POSITION + " " + MESSAGE);
+
+		}
+
+		////////////////////////////////////////////////////////////////////////
+		//                  CASE 2/3: Unresolved identifiers                  //
+		////////////////////////////////////////////////////////////////////////
+
+		// Input query (i.e. SELECT keyword missing):
+		QUERY = "SELECT name, ra || ' - ' || dec as \"Position\"\nFrom data\nWhere Contains(Point('ICRS', ra, dec), Circle('ICRS', 10, 5, 1)) = 1\nOrder By name;";
+		System.out.println("\n##### QUERY #2 #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			/* Parse the query (with an empty list of DB tables
+			 *  => no table/column can be resolved in any parsed query): */
+			(new ADQLParser(null, new DBChecker(new HashSet<DBTable>(0)), null, null)).parseQuery(QUERY);
+
+			System.err.println("\n((X)) This message should never be visible! ((X)");
+
+		} catch(UnresolvedIdentifiersException ex) {
+
+			/*
+			 * NOTES:
+			 *   UnresolvedIdentifiersException is the only extension of
+			 *   ParseException able to represent more than one error at a time.
+			 *
+			 *   It is possible to count and to list these errors with:
+			 *
+			 *     - UnresolvedIdentifiersException.getNbErrors()
+			 *
+			 *     - UnresolvedIdentifiersException.getErrors()
+			 *       or UnresolvedIdentifiersException.iterator()
+			 *
+			 *   As suggested by its name, this exception is very often used to
+			 *   list unresolved table/column names.
+			 */
+
+			System.out.println("\n((X))) INCORRECT QUERY! Cause: " + ex.getNbErrors() + " unresolved identifiers! ((X))");
+			for(ParseException pe : ex) {
+				System.out.println("  - " + pe.getPosition() + " " + pe.getMessage());
+			}
+
+		} catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getPosition() + " " + ex.getMessage());
+		}
+
+		////////////////////////////////////////////////////////////////////////
+		//                  CASE 3/3: Unsupported features                    //
+		////////////////////////////////////////////////////////////////////////
+
+		// Input query:
+		QUERY = "Select LOWER(name), MY_UDF('foo')\nFrom data;";
+		System.out.println("\n##### QUERY #3 #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			/* Parse the query (as above, with an empty list of DB tables
+			 *  BUT ALSO with absolutely no optional feature supported): */
+			(new ADQLParser(ADQLVersion.V2_1, new DBChecker(new HashSet<DBTable>(0)), null, new FeatureSet(false, false))).parseQuery(QUERY);
+
+			System.err.println("\n((X)) This message should never be visible! ((X)");
+
+		} catch(UnresolvedIdentifiersException ex) {
+
+			/*
+			 * NOTES:
+			 *   Since ADQL-2.1, there is the notion of optional language
+			 *   features.
+			 *
+			 *   Because several unsupported features may be used in a same
+			 *   ADQL query, UnresolvedIdentifiersException is now also used to
+			 *   list all of them.
+			 *
+			 *   However, unresolved identifiers and unsupported features can
+			 *   not be raised in the same time. First, supported features are
+			 *   tested. If no unsupported features are detected, then,
+			 *   identifiers resolution are tested. So, if unsupported features
+			 *   are detected, an UnresolvedIdentifiersException listing all of
+			 *   them is immediately thrown, which prevent testing the
+			 *   identifiers.
+			 *
+			 *   So, errors about support of language features always have the
+			 *   priority (as illustrated in this example).
+			 */
+
+			System.out.println("\n((X))) INCORRECT QUERY! Cause: " + ex.getNbErrors() + " unsupported features! ((X))");
+			for(ParseException pe : ex) {
+				System.out.println("  - " + pe.getPosition() + " " + pe.getMessage());
+			}
+
+		} catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getPosition() + " " + ex.getMessage());
+		}
+
+	}
+
+}
diff --git a/examples/vollt_examples/adql/parse/D_DeclareOptionalFeatures.java b/examples/vollt_examples/adql/parse/D_DeclareOptionalFeatures.java
new file mode 100644
index 0000000000000000000000000000000000000000..676b767ec92cf18ac1639120e8af49a44ee57710
--- /dev/null
+++ b/examples/vollt_examples/adql/parse/D_DeclareOptionalFeatures.java
@@ -0,0 +1,108 @@
+package vollt_examples.adql.parse;
+
+import adql.db.exception.UnresolvedIdentifiersException;
+import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
+import adql.parser.feature.FeatureSet;
+import adql.parser.feature.LanguageFeature;
+import adql.parser.grammar.ParseException;
+import adql.query.operand.function.string.LowerFunction;
+
+/**
+ * Example on how to support/unsupport some optional language features while
+ * parsing an ADQL query.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 08/2019
+ */
+public class D_DeclareOptionalFeatures {
+
+	public static void main(final String[] args) throws Throwable {
+
+		// Input query:
+		final String QUERY = "Select LOWER(name), ra || ' - ' || dec as \"Position\"\nFrom data;";
+		System.out.println("\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// Create a parser (supporting the notion of optional features):
+			ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+
+			/*
+			 * NOTES:
+			 *   If none is specified in parameter of the constructor,
+			 *   ADQLParser internally creates a default set of supported
+			 *   features.
+			 *
+			 *   This default may change in function of the ADQL grammar
+			 *   version. But generally, all features described in the version
+			 *   of an ADQL grammar are by default all supported by ADQLParser.
+			 *
+			 *   See ADQLParser.setDefaultFeatures() to know the exact content
+			 *   of the default features set created by ADQLParser.
+			 *
+			 *   To customize the list of supported features, you have 3
+			 *   possibilities:
+			 *
+			 *     1. Create an instance of FeatureSet and its support(...)
+			 *        and unsupport(...) functions to specify which features
+			 *        are supported or not.
+			 *        Then, give this FeatureSet in parameter when creating
+			 *        an ADQLParser.
+			 *
+			 *     2. As in 1., create a custom FeatureSet and set it to an
+			 *        existing ADQLParser instance using the function
+			 *        ADQLParser.setSupportedFeatures().
+			 *
+			 *     3. Get the FeatureSet of an already created ADQLParser with
+			 *        ADQLParser.getSupportedFeatures().
+			 *        Then, customize it as in 1. and 2. (with support(...) and
+			 *        unsupport(...) functions).
+			 *
+			 *   The functions FeatureSet.support(...) and
+			 *   FeatureSet.unsupport(...) take an instance of LanguageFeature
+			 *   in parameter. You DO NOT NEED to create a new one for each
+			 *   feature you want to support/unsupport. All ADQLObject
+			 *   extensions have a public static attribute named `FEATURE` of
+			 *   type LanguageFeature. So if you know the name of the class
+			 *   corresponding to your feature, you just have to use its
+			 *   attribute FEATURE (as illustrated below).
+			 */
+
+			// Create an empty set of language features:
+			FeatureSet features = new FeatureSet(false, false);
+
+			// Support all available features:
+			features.supportAll();
+
+			// Just get the LanguageFeature for LOWER (DO NOT CREATE IT):
+			final LanguageFeature lowerFeature = LowerFunction.FEATURE;
+
+			// Ensures LOWER is now supported:
+			if (!parser.getSupportedFeatures().isSupporting(lowerFeature))
+				throw new Error("This example can not work properly. LOWER seems to be NOT supported in the default FeatureSet of ADQLParser. To fix this example, pick a different optional features.");
+
+			// BUT for our example, now un-support it:
+			parser.getSupportedFeatures().unsupport(lowerFeature);
+
+			// Parse the query:
+			parser.parseQuery(QUERY);
+
+			System.err.println("\n((X)) This message should never be visible! ((X)");
+
+		} catch(UnresolvedIdentifiersException ex) {
+
+			System.out.println("\n((X))) INCORRECT QUERY! Cause: " + ex.getNbErrors() + " unsupported features! ((X))");
+			for(ParseException pe : ex) {
+				System.out.println("  - " + pe.getPosition() + " " + pe.getMessage());
+			}
+
+		} catch(ParseException ex) {
+
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getPosition() + " " + ex.getMessage());
+
+		}
+
+	}
+
+}
diff --git a/examples/vollt_examples/adql/parse/E_DeclareUDF.java b/examples/vollt_examples/adql/parse/E_DeclareUDF.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd6400dc5722c3927aee724179cf9bd5432c8e2d
--- /dev/null
+++ b/examples/vollt_examples/adql/parse/E_DeclareUDF.java
@@ -0,0 +1,110 @@
+package vollt_examples.adql.parse;
+
+import adql.db.FunctionDef;
+import adql.parser.ADQLParser;
+import adql.parser.feature.FeatureSet;
+import adql.parser.grammar.ParseException;
+import adql.query.ADQLQuery;
+
+/**
+ * Examples and explanations about how to declare UDF.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 08/2019
+ */
+public class E_DeclareUDF {
+
+	public static void main(final String[] args) {
+
+		// Input query:
+		final String QUERY = "Select MY_UDF(name)\nFrom data";
+
+		////////////////////////////////////////////////////////////////////////
+		//           CASE 1/3: Default = any undeclared UDF allowed           //
+		////////////////////////////////////////////////////////////////////////
+
+		System.out.println("\n##### DEFAULT = ANY UNDECLARED UDF ALLOWED #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// Create the parser:
+			ADQLParser parser = new ADQLParser();
+
+			// Parse the query:
+			ADQLQuery query = parser.parseQuery(QUERY);
+
+			System.out.println("\n((i)) Correct ADQL query ((i))");
+
+			System.out.println("\n((i)) As interpreted: ((i))\n    " + query.toADQL().replaceAll("\n", "\n    "));
+
+		}
+		// 3. EVENTUALLY DEAL WITH ERRORS:
+		catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getMessage());
+		}
+
+		////////////////////////////////////////////////////////////////////////
+		//                CASE 2/3: No undeclared UDF allowed                 //
+		////////////////////////////////////////////////////////////////////////
+
+		System.out.println("\n##### NO UNDECLARED UDF ALLOWED #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// Create the parser:
+			ADQLParser parser = new ADQLParser();
+
+			// FORBID ALL UNDECLARED UDF:
+			parser.getSupportedFeatures().allowAnyUdf(false);
+
+			// Parse the query:
+			ADQLQuery query = parser.parseQuery(QUERY);
+
+			System.out.println("\n((i)) Correct ADQL query ((i))");
+
+			System.out.println("\n((i)) As interpreted: ((i))\n    " + query.toADQL().replaceAll("\n", "\n    "));
+
+		}
+		// 3. EVENTUALLY DEAL WITH ERRORS:
+		catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getMessage());
+		}
+
+		////////////////////////////////////////////////////////////////////////
+		//                      CASE 3/3: Declare a UDF                       //
+		////////////////////////////////////////////////////////////////////////
+
+		System.out.println("\n##### DECLARE A UDF #####\n\n    " + QUERY.replaceAll("\n", "\n    "));
+
+		try {
+
+			// Create the parser:
+			ADQLParser parser = new ADQLParser();
+
+			// FORBID ALL UNDECLARED UDF:
+			FeatureSet features = parser.getSupportedFeatures();
+			features.allowAnyUdf(false);
+
+			// DECLARE A UDF:
+			// ...define this function:
+			FunctionDef myUdf = FunctionDef.parse("my_udf(param1 VARCHAR) -> VARCHAR", parser.getADQLVersion());
+			// ...now add it to the supported features:
+			if (!features.support(myUdf.toLanguageFeature()))
+				throw new Error("Impossible to support the UDF `" + myUdf + "`! This is the important point of this example file.");
+
+			// Parse the query:
+			ADQLQuery query = parser.parseQuery(QUERY);
+
+			System.out.println("\n((i)) Correct ADQL query ((i))");
+
+			System.out.println("\n((i)) As interpreted: ((i))\n    " + query.toADQL().replaceAll("\n", "\n    "));
+
+		}
+		// 3. EVENTUALLY DEAL WITH ERRORS:
+		catch(ParseException ex) {
+			System.out.println("\n((X)) INCORRECT QUERY! " + ex.getClass().getSimpleName() + " ((X))\n" + ex.getMessage());
+		}
+
+	}
+
+}