diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java
index 834d45abc5cf55a0005ad4b178941ac28469b586..31f1b16d00781a558af4be025b71fb0e56aecb29 100644
--- a/src/adql/db/DBChecker.java
+++ b/src/adql/db/DBChecker.java
@@ -20,6 +20,8 @@ package adql.db;
  *                            Astronomisches Rechen Institut (ARI)
  */
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -62,6 +64,7 @@ import adql.query.operand.function.geometry.PolygonFunction;
 import adql.query.operand.function.geometry.RegionFunction;
 import adql.search.ISearchHandler;
 import adql.search.SearchColumnHandler;
+import adql.search.SimpleReplaceHandler;
 import adql.search.SimpleSearchHandler;
 
 /**
@@ -780,7 +783,7 @@ public class DBChecker implements QueryChecker {
 	 * @since 1.3
 	 */
 	protected void checkUDFs(final ADQLQuery query, final UnresolvedIdentifiersException errors){
-		// Search all UDFs:
+		// 1. Search all UDFs:
 		ISearchHandler sHandler = new SearchUDFHandler();
 		sHandler.search(query);
 
@@ -789,7 +792,7 @@ public class DBChecker implements QueryChecker {
 			for(ADQLObject result : sHandler)
 				errors.addException(new UnresolvedFunction((UserDefinedFunction)result));
 		}
-		// Otherwise, try to resolve all of them:
+		// 2. Try to resolve all of them:
 		else{
 			ArrayList<UserDefinedFunction> toResolveLater = new ArrayList<UserDefinedFunction>();
 			UserDefinedFunction udf;
@@ -835,6 +838,9 @@ public class DBChecker implements QueryChecker {
 				else if (udf instanceof DefaultUDF)
 					((DefaultUDF)udf).setDefinition(allowedUdfs[match]);
 			}
+
+			// 3. Replace all the resolved DefaultUDF by an instance of the class associated with the set signature:
+			(new ReplaceDefaultUDFHandler(errors)).searchAndReplace(query);
 		}
 	}
 
@@ -1305,6 +1311,51 @@ public class DBChecker implements QueryChecker {
 		}
 	}
 
+	/**
+	 * <p>Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.</p>
+	 * 
+	 * <p><i><b>Important note:</b>
+	 * 	If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed.
+	 * </i></p>
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 1.3 (02/2015)
+	 * @since 1.3
+	 */
+	private static class ReplaceDefaultUDFHandler extends SimpleReplaceHandler {
+		private final UnresolvedIdentifiersException errors;
+
+		public ReplaceDefaultUDFHandler(final UnresolvedIdentifiersException errorsContainer){
+			errors = errorsContainer;
+		}
+
+		@Override
+		protected boolean match(ADQLObject obj){
+			return (obj.getClass().getName().equals(DefaultUDF.class.getName())) && (((DefaultUDF)obj).getDefinition() != null) && (((DefaultUDF)obj).getDefinition().getUDFClass() != null);
+			/* Note: detection of DefaultUDF is done on the exact class name rather than using "instanceof" in order to have only direct instances of DefaultUDF,
+			 * and not extensions of it. Indeed, DefaultUDFs are generally created automatically by the ADQLQueryFactory ; so, extensions of it can only be custom
+			 * UserDefinedFunctions. */
+		}
+
+		@Override
+		protected ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException{
+			try{
+				// get the associated UDF class:
+				Class<? extends UserDefinedFunction> udfClass = ((DefaultUDF)objToReplace).getDefinition().getUDFClass();
+				// get the constructor with a single parameter of type ADQLOperand[]:
+				Constructor<? extends UserDefinedFunction> constructor = udfClass.getConstructor(ADQLOperand[].class);
+				// create a new instance of this UDF class with the operands stored in the object to replace:
+				return constructor.newInstance((Object)(((DefaultUDF)objToReplace).getParameters())); /* note: without this class, each item of the given array will be considered as a single parameter. */
+			}catch(Exception ex){
+				// IF NO INSTANCE CAN BE CREATED...
+				// ...keep the error for further report:
+				errors.addException(new UnresolvedFunction("Impossible to represent the function \"" + ((DefaultUDF)objToReplace).getName() + "\": the following error occured while creating this representation: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\"", (DefaultUDF)objToReplace));
+				// ...keep the same object (i.e. no replacement):
+				return objToReplace;
+			}
+		}
+	}
+
 	/**
 	 * Let searching geometrical functions.
 	 * 
diff --git a/src/adql/db/FunctionDef.java b/src/adql/db/FunctionDef.java
index a8071eecb96545e846316d58c83a65de0e13eb43..82107d0a232a33d2e1d113c795304261f1bfda66 100644
--- a/src/adql/db/FunctionDef.java
+++ b/src/adql/db/FunctionDef.java
@@ -16,15 +16,19 @@ package adql.db;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2014 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2015 - Astronomisches Rechen Institut (ARI)
  */
 
+import java.lang.reflect.Constructor;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import adql.db.DBType.DBDatatype;
 import adql.parser.ParseException;
+import adql.query.operand.ADQLOperand;
 import adql.query.operand.function.ADQLFunction;
+import adql.query.operand.function.DefaultUDF;
+import adql.query.operand.function.UserDefinedFunction;
 
 /**
  * <p>Definition of any function that could be used in ADQL queries.</p>
@@ -45,7 +49,7 @@ import adql.query.operand.function.ADQLFunction;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 1.3 (10/2014)
+ * @version 1.3 (02/2015)
  * 
  * @since 1.3
  */
@@ -105,6 +109,13 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * <pre>{fctName}([xxx, ...])</pre> */
 	private final String compareForm;
 
+	/**
+	 * <p>Class of the {@link UserDefinedFunction} which must represent the UDF defined by this {@link FunctionDef} in the ADQL tree.</p>
+	 * <p>This class MUST have a constructor with a single parameter of type {@link ADQLOperand}[].</p>
+	 * <p>If this {@link FunctionDef} is defining an ordinary ADQL function, this attribute must be NULL. It is used only for user defined functions.</p> 
+	 */
+	private Class<? extends UserDefinedFunction> udfClass = null;
+
 	/**
 	 * <p>Definition of a function parameter.</p>
 	 * 
@@ -258,6 +269,60 @@ public class FunctionDef implements Comparable<FunctionDef> {
 			return params[indParam];
 	}
 
+	/**
+	 * <p>Get the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	This getter should return always NULL if the function defined here is not a user defined function.
+	 * 	<br/>
+	 * 	However, if this {@link FunctionDef} is defining a user defined function and this function returns NULL,
+	 * 	the library will create on the fly a {@link DefaultUDF} corresponding to this definition when needed.
+	 * 	Indeed this UDF class is useful only if the translation from ADQL (to SQL for instance) of the defined
+	 * 	function has a different signature (e.g. a different name) in the target language (e.g. SQL).
+	 * </i></p>
+	 * 
+	 * @return	The corresponding {@link UserDefinedFunction}. <i>MAY BE NULL</i>
+	 */
+	public final Class<? extends UserDefinedFunction> getUDFClass(){
+		return udfClass;
+	}
+
+	/**
+	 * <p>Set the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.</p>
+	 * 
+	 * <p><i>Note:
+	 * 	If this {@link FunctionDef} defines an ordinary ADQL function - and not a user defined function - no class should be set here.
+	 * 	<br/>
+	 * 	However, if it defines a user defined function, there is no obligation to set a UDF class. It is useful only if the translation
+	 * 	from ADQL (to SQL for instance) of the function has a different signature (e.g. a different name) in the target language (e.g. SQL).
+	 * 	If the signature is the same, there is no need to set a UDF class ; a {@link DefaultUDF} will be created on the fly by the library
+	 * 	when needed if it turns out that no UDF class is set.
+	 * </i></p>
+	 * 
+	 * @param udfClass	Class to use to represent in an ADQL tree the User Defined Function defined in this {@link FunctionDef}.
+	 * 
+	 * @throws IllegalArgumentException	If the given class does not provide any constructor with a single parameter of type ADQLOperand[].
+	 */
+	public final < T extends UserDefinedFunction > void setUDFClass(final Class<T> udfClass) throws IllegalArgumentException{
+		try{
+
+			// Ensure that, if a class is provided, it contains a constructor with a single parameter of type ADQLOperand[]:
+			if (udfClass != null){
+				Constructor<T> constructor = udfClass.getConstructor(ADQLOperand[].class);
+				if (constructor == null)
+					throw new IllegalArgumentException("The given class (" + udfClass.getName() + ") does not provide any constructor with a single parameter of type ADQLOperand[]!");
+			}
+
+			// Set the new UDF class:
+			this.udfClass = udfClass;
+
+		}catch(SecurityException e){
+			throw new IllegalArgumentException("A security problem occurred while trying to get constructor from the class " + udfClass.getName() + ": " + e.getMessage());
+		}catch(NoSuchMethodException e){
+			throw new IllegalArgumentException("The given class (" + udfClass.getName() + ") does not provide any constructor with a single parameter of type ADQLOperand[]!");
+		}
+	}
+
 	/**
 	 * <p>Let parsing the serialized form of a function definition.</p>
 	 * 
diff --git a/src/adql/query/operand/function/DefaultUDF.java b/src/adql/query/operand/function/DefaultUDF.java
index dd0c615843e0282855415d4c9b50c7fd4f9dda74..285eb4ef987cfa0e146ca1ad299af03460176cf8 100644
--- a/src/adql/query/operand/function/DefaultUDF.java
+++ b/src/adql/query/operand/function/DefaultUDF.java
@@ -16,7 +16,7 @@ package adql.query.operand.function;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -25,12 +25,14 @@ import adql.query.ADQLList;
 import adql.query.ADQLObject;
 import adql.query.ClauseADQL;
 import adql.query.operand.ADQLOperand;
+import adql.translator.ADQLTranslator;
+import adql.translator.TranslationException;
 
 /**
  * It represents any function which is not managed by ADQL.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.3 (10/2014)
+ * @version 1.3 (02/2015)
  */
 public final class DefaultUDF extends UserDefinedFunction {
 
@@ -48,7 +50,7 @@ public final class DefaultUDF extends UserDefinedFunction {
 	 * Creates a user function.
 	 * @param params	Parameters of the function.
 	 */
-	public DefaultUDF(final String name, ADQLOperand[] params) throws NullPointerException{
+	public DefaultUDF(final String name, final ADQLOperand[] params) throws NullPointerException{
 		functionName = name;
 		parameters = new ClauseADQL<ADQLOperand>();
 		if (params != null){
@@ -64,7 +66,7 @@ public final class DefaultUDF extends UserDefinedFunction {
 	 * @throws Exception	If there is an error during the copy.
 	 */
 	@SuppressWarnings("unchecked")
-	public DefaultUDF(DefaultUDF toCopy) throws Exception{
+	public DefaultUDF(final DefaultUDF toCopy) throws Exception{
 		functionName = toCopy.functionName;
 		parameters = (ADQLList<ADQLOperand>)(toCopy.parameters.getCopy());
 	}
@@ -91,6 +93,8 @@ public final class DefaultUDF extends UserDefinedFunction {
 	 * @param def	The definition applying to this parsed UDF, or NULL if none has been found.
 	 * 
 	 * @throws IllegalArgumentException	If the name in the given definition does not match the name of this parsed function.
+	 * 
+	 * @since 1.3
 	 */
 	public final void setDefinition(final FunctionDef def) throws IllegalArgumentException{
 		if (def != null && (def.name == null || !functionName.equalsIgnoreCase(def.name)))
@@ -155,4 +159,17 @@ public final class DefaultUDF extends UserDefinedFunction {
 		return parameters.set(index, replacer);
 	}
 
+	@Override
+	public String translate(final ADQLTranslator caller) throws TranslationException{
+		StringBuffer sql = new StringBuffer(functionName);
+		sql.append('(');
+		for(int i = 0; i < parameters.size(); i++){
+			if (i > 0)
+				sql.append(',').append(' ');
+			sql.append(caller.translate(parameters.get(i)));
+		}
+		sql.append(')');
+		return sql.toString();
+	}
+
 }
diff --git a/src/adql/query/operand/function/UserDefinedFunction.java b/src/adql/query/operand/function/UserDefinedFunction.java
index 78026e72d5429d4b6d76f07b42c3226ca241bbeb..f817832854b23f018e5a1af65756209481a61e61 100644
--- a/src/adql/query/operand/function/UserDefinedFunction.java
+++ b/src/adql/query/operand/function/UserDefinedFunction.java
@@ -1,7 +1,5 @@
 package adql.query.operand.function;
 
-import adql.query.operand.UnknownType;
-
 /*
  * This file is part of ADQLLibrary.
  * 
@@ -18,15 +16,19 @@ import adql.query.operand.UnknownType;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
+import adql.query.operand.UnknownType;
+import adql.translator.ADQLTranslator;
+import adql.translator.TranslationException;
+
 /**
  * Function defined by the user (i.e. PSQL functions).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.3 (10/2014)
+ * @version 1.3 (02/2015)
  * 
  * @see DefaultUDF
  */
@@ -46,4 +48,36 @@ public abstract class UserDefinedFunction extends ADQLFunction implements Unknow
 		expectedType = c;
 	}
 
+	/**
+	 * <p>Translate this User Defined Function into the language supported by the given translator.</p>
+	 * 
+	 * <p><b>VERY IMPORTANT:</b> This function <b>MUST NOT use</b> {@link ADQLTranslator#translate(UserDefinedFunction))} to translate itself.
+	 * The given {@link ADQLTranslator} <b>must be used ONLY</b> to translate UDF's operands.</p>
+	 * 
+	 * <p>Implementation example (extract of {@link DefaultUDF#translate(ADQLTranslator)}):</p>
+	 * <pre>
+	 * public String translate(final ADQLTranslator caller) throws TranslationException{
+	 * 	StringBuffer sql = new StringBuffer(functionName);
+	 * 	sql.append('(');
+	 * 	for(int i = 0; i < parameters.size(); i++){
+	 *		if (i > 0)
+	 *			sql.append(',').append(' ');
+	 * 		sql.append(caller.translate(parameters.get(i)));
+	 *	}
+	 *	sql.append(')');
+	 *	return sql.toString();
+	 * }
+	 * </pre>
+	 * 
+	 * 
+	 * @param caller	Translator to use in order to translate <b>ONLY</b> function parameters.
+	 * 
+	 * @return	The translation of this UDF into the language supported by the given translator.
+	 * 
+	 * @throws TranslationException	If one of the parameters can not be translated.
+	 * 
+	 * @since 1.3
+	 */
+	public abstract String translate(final ADQLTranslator caller) throws TranslationException;
+
 }
diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java
index 362e67c9e8843a4c47490a2a3c5c4fd0ea9e85f8..4451dcc743a77674355b87738f1fc6de455710fb 100644
--- a/src/adql/translator/JDBCTranslator.java
+++ b/src/adql/translator/JDBCTranslator.java
@@ -16,7 +16,7 @@ package adql.translator;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2014 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2015 - Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.ArrayList;
@@ -167,7 +167,7 @@ import adql.query.operand.function.geometry.RegionFunction;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (02/2015)
+ * @version 1.3 (02/2015)
  * @since 1.3
  * 
  * @see PostgreSQLTranslator
@@ -791,7 +791,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 
 	@Override
 	public String translate(UserDefinedFunction fct) throws TranslationException{
-		return getDefaultADQLFunction(fct);
+		return fct.translate(this);
 	}
 
 	/* *********************************** */
diff --git a/src/tap/config/DefaultServiceConnection.java b/src/tap/config/DefaultServiceConnection.java
index c7baae8101feac5bb19f1620c83e07739673d842..f04733aacbcacc2a124658dcdf5d97011593d81b 100644
--- a/src/tap/config/DefaultServiceConnection.java
+++ b/src/tap/config/DefaultServiceConnection.java
@@ -25,9 +25,11 @@ import static tap.config.TAPConfiguration.KEY_METADATA_FILE;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
 import static tap.config.TAPConfiguration.KEY_PROVIDER_NAME;
 import static tap.config.TAPConfiguration.KEY_SERVICE_DESCRIPTION;
+import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_MAX_FILE_SIZE;
 import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
+import static tap.config.TAPConfiguration.VALUE_ANY;
 import static tap.config.TAPConfiguration.VALUE_CSV;
 import static tap.config.TAPConfiguration.VALUE_DB;
 import static tap.config.TAPConfiguration.VALUE_JSON;
@@ -65,6 +67,8 @@ import uws.service.UserIdentifier;
 import uws.service.file.LocalUWSFileManager;
 import uws.service.file.UWSFileManager;
 import adql.db.FunctionDef;
+import adql.parser.ParseException;
+import adql.query.operand.function.UserDefinedFunction;
 
 public final class DefaultServiceConnection implements ServiceConnection {
 
@@ -146,6 +150,7 @@ public final class DefaultServiceConnection implements ServiceConnection {
 
 		// 9. CONFIGURE ADQL:
 		initADQLGeometries(tapConfig);
+		initUDFs(tapConfig);
 
 		// 10. MAKE THE SERVICE AVAILABLE:
 		setAvailable(true, "TAP service available.");
@@ -465,6 +470,10 @@ public final class DefaultServiceConnection implements ServiceConnection {
 		else if (propValue.equalsIgnoreCase(VALUE_NONE))
 			geometries = new ArrayList<String>(0);
 
+		// "ANY" => ALL FCT ALLOWED (= all of these functions are allowed)!
+		else if (propValue.equalsIgnoreCase(VALUE_ANY))
+			geometries = null;
+
 		// OTHERWISE, JUST THE ALLOWED ONE ARE LISTED:
 		else{
 			// split all the list items:
@@ -482,6 +491,9 @@ public final class DefaultServiceConnection implements ServiceConnection {
 					// "NONE" is not allowed inside a list => error!
 					else if (item.toUpperCase().equals(VALUE_NONE))
 						throw new TAPException("The special value \"" + VALUE_NONE + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that no value is allowed.");
+					// "ANY" is not allowed inside a list => error!
+					else if (item.toUpperCase().equals(VALUE_ANY))
+						throw new TAPException("The special value \"" + VALUE_ANY + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that any value is allowed.");
 					// unknown value => error!
 					else
 						throw new TAPException("Unknown ADQL geometrical function: \"" + item + "\"!");
@@ -494,6 +506,159 @@ public final class DefaultServiceConnection implements ServiceConnection {
 		}
 	}
 
+	private void initUDFs(final Properties tapConfig) throws TAPException{
+		// Get the property value:
+		String propValue = getProperty(tapConfig, KEY_UDFS);
+
+		// NO VALUE => NO UNKNOWN FCT ALLOWED!
+		if (propValue == null)
+			udfs = new ArrayList<FunctionDef>(0);
+
+		// "NONE" => NO UNKNOWN FCT ALLOWED (= none of the unknown functions are allowed)!
+		else if (propValue.equalsIgnoreCase(VALUE_NONE))
+			udfs = new ArrayList<FunctionDef>(0);
+
+		// "ANY" => ALL UNKNOWN FCT ALLOWED (= all of the unknown functions are allowed)!
+		else if (propValue.equalsIgnoreCase(VALUE_ANY))
+			udfs = null;
+
+		// OTHERWISE, JUST THE ALLOWED ONE ARE LISTED:
+		else{
+
+			char c;
+			int ind = 0;
+			short nbComma = 0;
+			boolean within_item = false, within_params = false, within_classpath = false;
+			StringBuffer buf = new StringBuffer();
+			String signature, classpath;
+			int[] posSignature = new int[]{-1,-1}, posClassPath = new int[]{-1,-1};
+
+			signature = null;
+			classpath = null;
+			buf.delete(0, buf.length());
+
+			while(ind < propValue.length()){
+				// Get the character:
+				c = propValue.charAt(ind++);
+				// If space => ignore
+				if (!within_params && Character.isWhitespace(c))
+					continue;
+				// If inside a parameters list, keep all characters until the list end (')'):
+				if (within_params){
+					if (c == ')')
+						within_params = false;
+					buf.append(c);
+				}
+				// If inside a classpath, keep all characters until the classpath end ('}'):
+				else if (within_classpath){
+					if (c == '}')
+						within_classpath = false;
+					buf.append(c);
+				}
+				// If inside an UDF declaration:
+				else if (within_item){
+					switch(c){
+						case '(': /* start of a parameters list */
+							within_params = true;
+							buf.append(c);
+							break;
+						case '{': /* start of a classpath */
+							within_classpath = true;
+							buf.append(c);
+							break;
+						case ',': /* separation between the signature and the classpath */
+							// count commas within this item:
+							if (++nbComma > 1)
+								// if more than 1, throw an error:
+								throw new TAPException("Wrong UDF declaration syntax: only two items (signature and classpath) can be given within brackets. (position in the property " + KEY_UDFS + ": " + ind + ")");
+							else{
+								// end of the signature and start of the class path:
+								signature = buf.toString();
+								buf.delete(0, buf.length());
+								posSignature[1] = ind;
+								posClassPath[0] = ind + 1;
+							}
+							break;
+						case ']': /* end of a UDF declaration */
+							within_item = false;
+							if (nbComma == 0){
+								signature = buf.toString();
+								posSignature[1] = ind;
+							}else{
+								classpath = (buf.length() == 0 ? null : buf.toString());
+								if (classpath != null)
+									posClassPath[1] = ind;
+							}
+							buf.delete(0, buf.length());
+
+							// no signature...
+							if (signature == null || signature.length() == 0){
+								// ...BUT a classpath => error
+								if (classpath != null)
+									throw new TAPException("Missing UDF declaration! (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")");
+								// ... => ignore this item
+								else
+									continue;
+							}
+
+							// add the new UDF in the list:
+							try{
+								// resolve the function signature:
+								FunctionDef def = FunctionDef.parse(signature);
+								// resolve the class path:
+								if (classpath != null){
+									if (isClassPath(classpath)){
+										Class<? extends UserDefinedFunction> fctClass = null;
+										try{
+											// fetch the class:
+											fctClass = fetchClass(classpath, KEY_UDFS, UserDefinedFunction.class);
+											// set the class inside the UDF definition:
+											def.setUDFClass(fctClass);
+										}catch(TAPException te){
+											throw new TAPException("Invalid class path for the UDF definition \"" + def + "\": " + te.getMessage() + " (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")", te);
+										}catch(IllegalArgumentException iae){
+											throw new TAPException("Invalid class path for the UDF definition \"" + def + "\": missing a constructor with a single parameter of type ADQLOperand[] " + (fctClass != null ? "in the class \"" + fctClass.getName() + "\"" : "") + "! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")");
+										}
+									}else
+										throw new TAPException("Invalid class path for the UDF definition \"" + def + "\": \"" + classpath + "\" is not a class path (or is not surrounding by {} as expected in this property file)! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")");
+								}
+								// add the UDF:
+								udfs.add(def);
+							}catch(ParseException pe){
+								throw new TAPException("Wrong UDF declaration syntax: " + pe.getMessage() + " (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")", pe);
+							}
+
+							// reset some variables:
+							nbComma = 0;
+							signature = null;
+							classpath = null;
+							break;
+						default: /* keep all other characters */
+							buf.append(c);
+							break;
+					}
+				}
+				// If outside of everything, just starting a UDF declaration or separate each declaration is allowed:
+				else{
+					switch(c){
+						case '[':
+							within_item = true;
+							posSignature[0] = ind + 1;
+							break;
+						case ',':
+							break;
+						default:
+							throw new TAPException("Wrong UDF declaration syntax: unexpected character at position " + ind + " in the property " + KEY_UDFS + ": \"" + c + "\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{classpath}]\".");
+					}
+				}
+			}
+
+			// If the parsing is not finished, throw an error:
+			if (within_item)
+				throw new TAPException("Wrong UDF declaration syntax: missing closing bracket at position " + propValue.length() + "!");
+		}
+	}
+
 	@Override
 	public String getProviderName(){
 		return providerName;
@@ -722,7 +887,7 @@ public final class DefaultServiceConnection implements ServiceConnection {
 
 	@Override
 	public Collection<FunctionDef> getUDFs(){
-		return udfs;	// FORBID ANY UNKNOWN FUNCTION
+		return udfs;
 	}
 
 }
diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java
index 860873e11bd0ca5d4be6f2927bb221b7816d36a9..aa60398382ca60282f30baa61913f4fedc2cd834 100644
--- a/src/tap/config/TAPConfiguration.java
+++ b/src/tap/config/TAPConfiguration.java
@@ -89,6 +89,8 @@ public final class TAPConfiguration {
 	/* ADQL RESTRICTIONS */
 	public final static String KEY_GEOMETRIES = "geometries";
 	public final static String VALUE_NONE = "NONE";
+	public final static String KEY_UDFS = "udfs";
+	public final static String VALUE_ANY = "ANY";
 
 	/**
 	 * <p>Read the asked property from the given Properties object.</p>
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 7573f5ea01c59bd02899991a8db8869a27ae0e77..173dc066d2669fee902bf04ae63e147cef47f5f4 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -470,6 +470,27 @@
 				</td>
 				<td><ul><li>ø <em>(default)</em></li><li>NONE</li><li>CONTAINS, intersects, Point, Box, CIRCLE</li></ul></td>
 			</tr>
+			<tr>
+				<td class="done">udfs</td>
+				<td></td>
+				<td>text</td>
+				<td>
+					<p>Comma-separated list of all allowed UDFs (User Defined Functions).</p>
+					<p>
+						Each item of the list must have the following syntax: <code>[fct_signature]</code> or <code>[fct_signature, classpath]</code>.
+						<i>fct_function</i> is the function signature. Its syntax is the same as in <a href="http://www.ivoa.net/documents/TAPRegExt/20120827/REC-TAPRegExt-1.0.html#langs">TAPRegExt</a>.
+						<i>classpath</i> is the path of a class extending UserDefinedFunction. An instance of this class will replace any reference of a UDF
+						written in an ADQL function with the associated signature. A class path must be specified if the function to represent has a signature
+						(and more particularly a name) different in ADQL and in SQL.
+					</p>
+					<p>
+						If the list is empty (no item), all unknown functions are forbidden. And if the special value <em>ANY</em> is given, any unknown function is allowed ;
+						consequently the unknown ADQL functions will be translated into SQL as they are in ADQL.
+					</p>
+					<p><em>By default, no unknown function is allowed.</em></p>
+				</td>
+				<td><ul><li>ø <em>(default)</em></li><li>ANY</li><li>[trim(txt String) -&gt; String], [random() -&gt; DOUBLE]</li><li>[newFct(x double)-&gt;double, {apackage.MyNewFunction}]</li></ul></td>
+			</tr>
 			
 		</table>
 	</body>
diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java
index c8e8b4b43dd1440f9371b6ae575837b560549084..3f9924a8616ad959deebbbbab8f7df1dac96b3f8 100644
--- a/test/adql/db/TestDBChecker.java
+++ b/test/adql/db/TestDBChecker.java
@@ -23,10 +23,16 @@ import adql.db.FunctionDef.FunctionParam;
 import adql.db.exception.UnresolvedIdentifiersException;
 import adql.parser.ADQLParser;
 import adql.parser.ParseException;
+import adql.query.ADQLObject;
 import adql.query.ADQLQuery;
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
+import adql.query.operand.StringConstant;
 import adql.query.operand.function.DefaultUDF;
+import adql.query.operand.function.UserDefinedFunction;
+import adql.search.SimpleSearchHandler;
+import adql.translator.ADQLTranslator;
+import adql.translator.TranslationException;
 
 public class TestDBChecker {
 
@@ -148,6 +154,52 @@ public class TestDBChecker {
 			assertEquals(1, ex.getNbErrors());
 			assertEquals("Unresolved function: \"toto('blabla')\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage());
 		}
+
+		// Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class:
+		udfs = new FunctionDef[]{new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("txt", new DBType(DBDatatype.VARCHAR))})};
+		udfs[0].setUDFClass(UDFToto.class);
+		parser = new ADQLParser(new DBChecker(tables, Arrays.asList(udfs)));
+		try{
+			ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;");
+			assertNotNull(query);
+			Iterator<ADQLObject> it = query.search(new SimpleSearchHandler(){
+				@Override
+				protected boolean match(ADQLObject obj){
+					return (obj instanceof UserDefinedFunction) && ((UserDefinedFunction)obj).getName().equals("toto");
+				}
+			});
+			assertTrue(it.hasNext());
+			assertEquals(UDFToto.class.getName(), it.next().getClass().getName());
+			assertFalse(it.hasNext());
+		}catch(Exception e){
+			e.printStackTrace();
+			fail("This query contains a DECLARED UDF with a valid UserDefinedFunction class: this test should have succeeded!");
+		}
+
+		// Test with a wrong parameter type:
+		try{
+			parser.parseQuery("SELECT toto(123) FROM foo;");
+			fail("This query contains an unknown UDF signature (the fct toto is declared with one parameter of type STRING...here it is a numeric): this test should have failed!");
+		}catch(Exception e){
+			assertTrue(e instanceof UnresolvedIdentifiersException);
+			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+			assertEquals(1, ex.getNbErrors());
+			assertEquals("Unresolved function: \"toto(123)\"! No UDF has been defined or found with the signature: toto(NUMERIC).", ex.getErrors().next().getMessage());
+		}
+
+		// Test with UDF class constructor throwing an exception:
+		udfs = new FunctionDef[]{new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("txt", new DBType(DBDatatype.VARCHAR))})};
+		udfs[0].setUDFClass(WrongUDFToto.class);
+		parser = new ADQLParser(new DBChecker(tables, Arrays.asList(udfs)));
+		try{
+			parser.parseQuery("SELECT toto('blabla') FROM foo;");
+			fail("The set UDF class constructor has throw an error: this test should have failed!");
+		}catch(Exception e){
+			assertTrue(e instanceof UnresolvedIdentifiersException);
+			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+			assertEquals(1, ex.getNbErrors());
+			assertEquals("Impossible to represent the function \"toto\": the following error occured while creating this representation: \"[Exception] Systematic error!\"", ex.getErrors().next().getMessage());
+		}
 	}
 
 	@Test
@@ -606,4 +658,83 @@ public class TestDBChecker {
 		}
 	}
 
+	private static class WrongUDFToto extends UDFToto {
+		public WrongUDFToto(final ADQLOperand[] params) throws Exception{
+			super(params);
+			throw new Exception("Systematic error!");
+		}
+	}
+
+	public static class UDFToto extends UserDefinedFunction {
+		protected StringConstant fakeParam;
+
+		public UDFToto(final ADQLOperand[] params) throws Exception{
+			if (params == null || params.length == 0)
+				throw new Exception("Missing parameter for the user defined function \"toto\"!");
+			else if (params.length > 1)
+				throw new Exception("Too many parameters for the function \"toto\"! Only one is required.");
+			else if (!(params[0] instanceof StringConstant))
+				throw new Exception("Wrong parameter type! The parameter of the UDF \"toto\" must be a string constant.");
+			fakeParam = (StringConstant)params[0];
+		}
+
+		@Override
+		public final boolean isNumeric(){
+			return false;
+		}
+
+		@Override
+		public final boolean isString(){
+			return true;
+		}
+
+		@Override
+		public final boolean isGeometry(){
+			return false;
+		}
+
+		@Override
+		public ADQLObject getCopy() throws Exception{
+			ADQLOperand[] params = new ADQLOperand[]{(StringConstant)fakeParam.getCopy()};
+			return new UDFToto(params);
+		}
+
+		@Override
+		public final String getName(){
+			return "toto";
+		}
+
+		@Override
+		public final ADQLOperand[] getParameters(){
+			return new ADQLOperand[]{fakeParam};
+		}
+
+		@Override
+		public final int getNbParameters(){
+			return 1;
+		}
+
+		@Override
+		public final ADQLOperand getParameter(int index) throws ArrayIndexOutOfBoundsException{
+			if (index != 0)
+				throw new ArrayIndexOutOfBoundsException("Incorrect parameter index: " + index + "! The function \"toto\" has only one parameter.");
+			return fakeParam;
+		}
+
+		@Override
+		public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception{
+			if (index != 0)
+				throw new ArrayIndexOutOfBoundsException("Incorrect parameter index: " + index + "! The function \"toto\" has only one parameter.");
+			else if (!(replacer instanceof StringConstant))
+				throw new Exception("Wrong parameter type! The parameter of the UDF \"toto\" must be a string constant.");
+			return (fakeParam = (StringConstant)replacer);
+		}
+
+		@Override
+		public String translate(final ADQLTranslator caller) throws TranslationException{
+			/* Note: Since this function is totally fake, this function will be replaced in SQL by its parameter (the string). */
+			return caller.translate(fakeParam);
+		}
+	}
+
 }
diff --git a/test/tap/config/TestDefaultServiceConnection.java b/test/tap/config/TestDefaultServiceConnection.java
index 4124ce7336ac6350276bef022fa9b730b550ffac..21e9692b11243379c328d9c8f0b58582e80e84c3 100644
--- a/test/tap/config/TestDefaultServiceConnection.java
+++ b/test/tap/config/TestDefaultServiceConnection.java
@@ -14,7 +14,9 @@ import static tap.config.TAPConfiguration.KEY_MAX_OUTPUT_LIMIT;
 import static tap.config.TAPConfiguration.KEY_METADATA;
 import static tap.config.TAPConfiguration.KEY_METADATA_FILE;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
+import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
+import static tap.config.TAPConfiguration.VALUE_ANY;
 import static tap.config.TAPConfiguration.VALUE_CSV;
 import static tap.config.TAPConfiguration.VALUE_DB;
 import static tap.config.TAPConfiguration.VALUE_JSON;
@@ -27,6 +29,7 @@ import static tap.config.TAPConfiguration.VALUE_XML;
 import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Properties;
 
@@ -44,6 +47,8 @@ import uws.job.user.JobOwner;
 import uws.service.UWSUrl;
 import uws.service.UserIdentifier;
 import uws.service.file.LocalUWSFileManager;
+import adql.db.FunctionDef;
+import adql.db.TestDBChecker.UDFToto;
 
 public class TestDefaultServiceConnection {
 
@@ -56,7 +61,12 @@ public class TestDefaultServiceConnection {
 			negativeMaxAsyncProp, notIntMaxAsyncProp, defaultOutputLimitProp,
 			maxOutputLimitProp, bothOutputLimitGoodProp,
 			bothOutputLimitBadProp, userIdentProp, notClassPathUserIdentProp,
-			geometriesProp, noneGeomProp, noneInsideGeomProp, unknownGeomProp;
+			geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp,
+			unknownGeomProp, anyUdfsProp, noneUdfsProp, udfsProp,
+			udfsWithClassPathProp, udfsListWithNONEorANYProp,
+			udfsWithWrongParamLengthProp, udfsWithMissingBracketsProp,
+			udfsWithMissingDefProp1, udfsWithMissingDefProp2,
+			emptyUdfItemProp1, emptyUdfItemProp2, udfWithMissingEndBracketProp;
 
 	@Before
 	public void setUp() throws Exception{
@@ -137,11 +147,50 @@ public class TestDefaultServiceConnection {
 		noneGeomProp = (Properties)validProp.clone();
 		noneGeomProp.setProperty(KEY_GEOMETRIES, VALUE_NONE);
 
+		anyGeomProp = (Properties)validProp.clone();
+		anyGeomProp.setProperty(KEY_GEOMETRIES, VALUE_ANY);
+
 		noneInsideGeomProp = (Properties)validProp.clone();
 		noneInsideGeomProp.setProperty(KEY_GEOMETRIES, "POINT, Box, none, circle");
 
 		unknownGeomProp = (Properties)validProp.clone();
 		unknownGeomProp.setProperty(KEY_GEOMETRIES, "POINT, Contains, foo, circle,Polygon");
+
+		anyUdfsProp = (Properties)validProp.clone();
+		anyUdfsProp.setProperty(KEY_UDFS, VALUE_ANY);
+
+		noneUdfsProp = (Properties)validProp.clone();
+		noneUdfsProp.setProperty(KEY_UDFS, VALUE_NONE);
+
+		udfsProp = (Properties)validProp.clone();
+		udfsProp.setProperty(KEY_UDFS, "[toto(a string)] ,	[  titi(b REAL) -> double 	]");
+
+		udfsWithClassPathProp = (Properties)validProp.clone();
+		udfsWithClassPathProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}]");
+
+		udfsListWithNONEorANYProp = (Properties)validProp.clone();
+		udfsListWithNONEorANYProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR],ANY");
+
+		udfsWithWrongParamLengthProp = (Properties)validProp.clone();
+		udfsWithWrongParamLengthProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, foo]");
+
+		udfsWithMissingBracketsProp = (Properties)validProp.clone();
+		udfsWithMissingBracketsProp.setProperty(KEY_UDFS, "toto(a string)->VARCHAR");
+
+		udfsWithMissingDefProp1 = (Properties)validProp.clone();
+		udfsWithMissingDefProp1.setProperty(KEY_UDFS, "[{adql.db.TestDBChecker$UDFToto}]");
+
+		udfsWithMissingDefProp2 = (Properties)validProp.clone();
+		udfsWithMissingDefProp2.setProperty(KEY_UDFS, "[,{adql.db.TestDBChecker$UDFToto}]");
+
+		emptyUdfItemProp1 = (Properties)validProp.clone();
+		emptyUdfItemProp1.setProperty(KEY_UDFS, "[ ]");
+
+		emptyUdfItemProp2 = (Properties)validProp.clone();
+		emptyUdfItemProp2.setProperty(KEY_UDFS, "[ ,	 ]");
+
+		udfWithMissingEndBracketProp = (Properties)validProp.clone();
+		udfWithMissingEndBracketProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR");
 	}
 
 	/**
@@ -184,6 +233,7 @@ public class TestDefaultServiceConnection {
 			assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]);
 			assertNull(connection.getUserIdentifier());
 			assertNull(connection.getGeometries());
+			assertEquals(0, connection.getUDFs().size());
 
 			// finally, save metadata in an XML file for the other tests:
 			writer = new PrintWriter(new File(XML_FILE));
@@ -213,8 +263,8 @@ public class TestDefaultServiceConnection {
 			assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]);
 			assertNull(connection.getUserIdentifier());
 			assertNull(connection.getGeometries());
+			assertEquals(0, connection.getUDFs().size());
 		}catch(Exception e){
-			e.printStackTrace();
 			fail("This MUST have succeeded because the property file is valid! \nCaught exception: " + getPertinentMessage(e));
 		}
 
@@ -223,8 +273,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(missingMetaProp);
 			fail("This MUST have failed because the property 'metadata' is missing!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).", e.getMessage());
 		}
 
 		// Missing metadata_file property:
@@ -232,8 +282,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(missingMetaFileProp);
 			fail("This MUST have failed because the property 'metadata_file' is missing!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "The property \"" + KEY_METADATA_FILE + "\" is missing! According to the property \"" + KEY_METADATA + "\", metadata must be fetched from an XML document. The local file path of it MUST be provided using the property \"" + KEY_METADATA_FILE + "\".");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("The property \"" + KEY_METADATA_FILE + "\" is missing! According to the property \"" + KEY_METADATA + "\", metadata must be fetched from an XML document. The local file path of it MUST be provided using the property \"" + KEY_METADATA_FILE + "\".", e.getMessage());
 		}
 
 		// Wrong metadata property:
@@ -241,8 +291,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(wrongMetaProp);
 			fail("This MUST have failed because the property 'metadata' has a wrong value!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Unsupported value for the property \"" + KEY_METADATA + "\": \"foo\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Unsupported value for the property \"" + KEY_METADATA + "\": \"foo\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).", e.getMessage());
 		}
 
 		// Wrong metadata_file property:
@@ -250,8 +300,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(wrongMetaFileProp);
 			fail("This MUST have failed because the property 'metadata_file' has a wrong value!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "A grave error occurred while reading/parsing the TableSet XML document: \"foo\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("A grave error occurred while reading/parsing the TableSet XML document: \"foo\"!", e.getMessage());
 		}
 
 		// No File Manager:
@@ -259,8 +309,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(noFmProp);
 			fail("This MUST have failed because no File Manager is specified!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "The property \"" + KEY_FILE_MANAGER + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_LOCAL + " or a class path between {...}.");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("The property \"" + KEY_FILE_MANAGER + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_LOCAL + " or a class path between {...}.", e.getMessage());
 		}
 
 		// File Manager = Class Path:
@@ -288,8 +338,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(incorrectFmProp);
 			fail("This MUST have failed because an incorrect File Manager value has been provided!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Unknown value for the property \"" + KEY_FILE_MANAGER + "\": \"foo\". Only two possible values: " + VALUE_LOCAL + " or a class path between {...}.");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Unknown value for the property \"" + KEY_FILE_MANAGER + "\": \"foo\". Only two possible values: " + VALUE_LOCAL + " or a class path between {...}.", e.getMessage());
 		}
 
 		// Valid output formats list:
@@ -313,8 +363,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(badSVFormat1Prop);
 			fail("This MUST have failed because an incorrect SV output format value has been provided!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Missing separator char/string for the SV output format: \"sv\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Missing separator char/string for the SV output format: \"sv\"!", e.getMessage());
 		}
 
 		// Bad SV(...) format 2 = "sv()":
@@ -322,8 +372,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(badSVFormat2Prop);
 			fail("This MUST have failed because an incorrect SV output format value has been provided!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Missing separator char/string for the SV output format: \"sv()\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Missing separator char/string for the SV output format: \"sv()\"!", e.getMessage());
 		}
 
 		// Unknown output format:
@@ -331,8 +381,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(unknownFormatProp);
 			fail("This MUST have failed because an incorrect output format value has been provided!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Unknown output format: foo");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Unknown output format: foo", e.getMessage());
 		}
 
 		// Valid value for max_async_jobs:
@@ -356,8 +406,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(notIntMaxAsyncProp);
 			fail("This MUST have failed because a not integer value has been provided for \"" + KEY_MAX_ASYNC_JOBS + "\"!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Integer expected for the property \"" + KEY_MAX_ASYNC_JOBS + "\", instead of: \"foo\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Integer expected for the property \"" + KEY_MAX_ASYNC_JOBS + "\", instead of: \"foo\"!", e.getMessage());
 		}
 
 		// Test with no output limit specified:
@@ -409,8 +459,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(bothOutputLimitBadProp);
 			fail("This MUST have failed because the default output limit is greater than the maximum one!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "The default output limit (here: 1000) MUST be less or equal to the maximum output limit (here: 100)!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("The default output limit (here: 1000) MUST be less or equal to the maximum output limit (here: 100)!", e.getMessage());
 		}
 
 		// Valid user identifier:
@@ -428,8 +478,8 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(notClassPathUserIdentProp);
 			fail("This MUST have failed because the user_identifier value is not a class path!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Class path expected for the property \"" + KEY_USER_IDENTIFIER + "\", instead of: \"foo\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Class path expected for the property \"" + KEY_USER_IDENTIFIER + "\", instead of: \"foo\"!", e.getMessage());
 		}
 
 		// Valid geometry list:
@@ -454,13 +504,21 @@ public class TestDefaultServiceConnection {
 			fail("This MUST have succeeded because the given list of geometries is correct (reduced to only NONE)! \nCaught exception: " + getPertinentMessage(e));
 		}
 
+		// "ANY" as geometry list:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(anyGeomProp);
+			assertNull(connection.getGeometries());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of geometries is correct (reduced to only ANY)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
 		// "NONE" inside a geometry list:
 		try{
 			new DefaultServiceConnection(noneInsideGeomProp);
 			fail("This MUST have failed because the given geometry list contains at least 2 items, whose one is NONE!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "The special value \"" + VALUE_NONE + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that no value is allowed.");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("The special value \"" + VALUE_NONE + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that no value is allowed.", e.getMessage());
 		}
 
 		// Unknown geometrical function:
@@ -468,8 +526,121 @@ public class TestDefaultServiceConnection {
 			new DefaultServiceConnection(unknownGeomProp);
 			fail("This MUST have failed because the given geometry list contains at least 1 unknown ADQL geometrical function!");
 		}catch(Exception e){
-			assertEquals(e.getClass(), TAPException.class);
-			assertEquals(e.getMessage(), "Unknown ADQL geometrical function: \"foo\"!");
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Unknown ADQL geometrical function: \"foo\"!", e.getMessage());
+		}
+
+		// "ANY" as UDFs list:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(anyUdfsProp);
+			assertNull(connection.getUDFs());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs is correct (reduced to only ANY)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// "NONE" as UDFs list:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(noneUdfsProp);
+			assertNotNull(connection.getUDFs());
+			assertEquals(0, connection.getUDFs().size());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs is correct (reduced to only NONE)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Valid list of UDFs:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(udfsProp);
+			assertNotNull(connection.getUDFs());
+			assertEquals(2, connection.getUDFs().size());
+			Iterator<FunctionDef> it = connection.getUDFs().iterator();
+			assertEquals("toto(a VARCHAR)", it.next().toString());
+			assertEquals("titi(b REAL) -> DOUBLE", it.next().toString());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs contains valid items! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Valid list of UDFs containing one UDF with a classpath:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(udfsWithClassPathProp);
+			assertNotNull(connection.getUDFs());
+			assertEquals(1, connection.getUDFs().size());
+			FunctionDef def = connection.getUDFs().iterator().next();
+			assertEquals("toto(a VARCHAR) -> VARCHAR", def.toString());
+			assertEquals(UDFToto.class, def.getUDFClass());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs contains valid items! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// "NONE" inside a UDFs list:
+		try{
+			new DefaultServiceConnection(udfsListWithNONEorANYProp);
+			fail("This MUST have failed because the given UDFs list contains at least 2 items, whose one is ANY!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Wrong UDF declaration syntax: unexpected character at position 27 in the property " + KEY_UDFS + ": \"A\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{classpath}]\".", e.getMessage());
+		}
+
+		// UDF with no brackets:
+		try{
+			new DefaultServiceConnection(udfsWithMissingBracketsProp);
+			fail("This MUST have failed because one UDFs list item has no brackets!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Wrong UDF declaration syntax: unexpected character at position 1 in the property " + KEY_UDFS + ": \"t\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{classpath}]\".", e.getMessage());
+		}
+
+		// UDFs whose one item have more parts than supported:
+		try{
+			new DefaultServiceConnection(udfsWithWrongParamLengthProp);
+			fail("This MUST have failed because one UDFs list item has too many parameters!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Wrong UDF declaration syntax: only two items (signature and classpath) can be given within brackets. (position in the property " + KEY_UDFS + ": 58)", e.getMessage());
+		}
+
+		// UDF with missing definition part (or wrong since there is no comma):
+		try{
+			new DefaultServiceConnection(udfsWithMissingDefProp1);
+			fail("This MUST have failed because one UDFs list item has a wrong signature part (it has been forgotten)!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Wrong UDF declaration syntax: Wrong function definition syntax! Expected syntax: \"<regular_identifier>(<parameters>?) <return_type>?\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <return_type>=\" -> <type_name>\", <parameters>=\"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"foo()\", \"foo() -> VARCHAR\", \"foo(param INTEGER)\", \"foo(param1 INTEGER, param2 DOUBLE) -> DOUBLE\" (position in the property " + KEY_UDFS + ": 2-33)", e.getMessage());
+		}
+
+		// UDF with missing definition part (or wrong since there is no comma):
+		try{
+			new DefaultServiceConnection(udfsWithMissingDefProp2);
+			fail("This MUST have failed because one UDFs list item has no signature part!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Missing UDF declaration! (position in the property " + KEY_UDFS + ": 2-2)", e.getMessage());
+		}
+
+		// Empty UDF item (without comma):
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(emptyUdfItemProp1);
+			assertNotNull(connection.getUDFs());
+			assertEquals(0, connection.getUDFs().size());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs contains one empty UDF (which should be merely ignored)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Empty UDF item (with comma):
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(emptyUdfItemProp2);
+			assertNotNull(connection.getUDFs());
+			assertEquals(0, connection.getUDFs().size());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of UDFs contains one empty UDF (which should be merely ignored)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// UDF item without its closing bracket:
+		try{
+			new DefaultServiceConnection(udfWithMissingEndBracketProp);
+			fail("This MUST have failed because one UDFs list item has no closing bracket!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Wrong UDF declaration syntax: missing closing bracket at position 24!", e.getMessage());
 		}
 	}