diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 12212b154960a9245413e38ccada439236805fe3..fd65069243f83dbb90a2a99998c37d3c8f085445 100644
--- a/src/tap/ADQLExecutor.java
+++ b/src/tap/ADQLExecutor.java
@@ -16,7 +16,7 @@ package tap;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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)
  */
 
@@ -104,7 +104,7 @@ import adql.query.ADQLQuery;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (12/2014)
+ * @version 2.0 (04/2015)
  */
 public class ADQLExecutor {
 
@@ -223,6 +223,8 @@ public class ADQLExecutor {
 
 		try{
 			return start();
+		}catch(IOException ioe){
+			throw new UWSException(ioe);
 		}catch(TAPException te){
 			throw new UWSException(te.getHttpErrorCode(), te);
 		}
@@ -260,11 +262,12 @@ public class ADQLExecutor {
 	 * @return	The resulting execution report.
 	 * 
 	 * @throws TAPException			If any error occurs while executing the ADQL query.
+	 * @throws IOException			If any error occurs while writing the result in the given {@link HttpServletResponse}.
 	 * @throws InterruptedException	If the job has been interrupted (by the user or a time-out).
 	 * 
 	 * @see #start()
 	 */
-	public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, InterruptedException{
+	public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, IOException, InterruptedException{
 		if (this.thread != null || this.report != null)
 			throw new TAPException("This ADQLExecutor has already been executed!");
 
@@ -305,9 +308,14 @@ public class ADQLExecutor {
 	 * 
 	 * @throws TAPException			If any error occurs while executing the ADQL query.
 	 * @throws UWSException			If any error occurs while executing the ADQL query.
+	 * @throws IOException			If an error happens while writing the result in the specified {@link HttpServletResponse}.
+	 *                    			<i>That kind of error can be thrown only in synchronous mode.
+	 *                    			In asynchronous, the error is stored as job error report and is never propagated.</i>
 	 * @throws InterruptedException	If the job has been interrupted (by the user or a time-out).
 	 */
-	protected final TAPExecutionReport start() throws TAPException, UWSException, InterruptedException{
+	protected final TAPExecutionReport start() throws TAPException, UWSException, IOException, InterruptedException{
+		logger.logTAP(LogLevel.INFO, report, "START_EXEC", (report.synchronous ? "Synchronous" : "Asynchronous") + " execution of an ADQL query STARTED.", null);
+
 		// Save the start time (for reporting usage):
 		long start = System.currentTimeMillis();
 
@@ -356,16 +364,19 @@ public class ADQLExecutor {
 
 			// 4. WRITE RESULT:
 			startStep(ExecutionProgression.WRITING_RESULT);
-			if (response != null && response.isCommitted())
-				getLogger().logTAP(LogLevel.WARNING, report, "WRITING_RESULT", "HTTP request canceled/timeout! The result can not have been returned to the user.", null);
-			else
-				writeResult(queryResult);
+			writeResult(queryResult);
 			endStep();
 
 			// Report the COMPLETED status:
 			tapParams.remove(TAPJob.PARAM_PROGRESSION);
 			report.success = true;
 
+			// Set the total duration in the report:
+			report.setTotalDuration(System.currentTimeMillis() - start);
+
+			// Log and report the end of this execution:
+			logger.logTAP(LogLevel.INFO, report, "END_EXEC", "ADQL query execution finished.", null);
+
 			return report;
 		}finally{
 			// Close the result if any:
@@ -373,7 +384,7 @@ public class ADQLExecutor {
 				try{
 					queryResult.close();
 				}catch(DataReadException dre){
-					logger.logTAP(LogLevel.ERROR, report, "END_EXEC", "Can not close the database query result!", dre);
+					logger.logTAP(LogLevel.WARNING, report, "END_EXEC", "Can not close the database query result!", dre);
 				}
 			}
 
@@ -381,7 +392,7 @@ public class ADQLExecutor {
 			try{
 				dropUploadedTables();
 			}catch(TAPException e){
-				logger.logTAP(LogLevel.ERROR, report, "END_EXEC", "Can not drop the uploaded tables from the database!", e);
+				logger.logTAP(LogLevel.WARNING, report, "END_EXEC", "Can not drop the uploaded tables from the database!", e);
 			}
 
 			// Free the connection (so that giving it back to a pool, if any, otherwise, just free resources):
@@ -389,12 +400,6 @@ public class ADQLExecutor {
 				service.getFactory().freeConnection(dbConn);
 				dbConn = null;
 			}
-
-			// Set the total duration in the report:
-			report.setTotalDuration(System.currentTimeMillis() - start);
-
-			// Log and report the end of this execution:
-			logger.logTAP(LogLevel.INFO, report, "END_EXEC", "ADQL query execution finished.", null);
 		}
 	}
 
@@ -549,7 +554,7 @@ public class ADQLExecutor {
 	 */
 	protected TableIterator executeADQL(final ADQLQuery adql) throws InterruptedException, TAPException{
 		// Log the start of execution:
-		logger.logTAP(LogLevel.INFO, report, "EXECUTING", "Executing ADQL: " + adql.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
+		logger.logTAP(LogLevel.INFO, report, "START_DB_EXECUTION", "ADQL query: " + adql.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
 
 		// Set the fetch size, if any:
 		if (service.getFetchSize() != null && service.getFetchSize().length >= 1){
@@ -564,9 +569,10 @@ public class ADQLExecutor {
 
 		// Log the success or failure:
 		if (result == null)
-			logger.logTAP(LogLevel.INFO, report, "END_QUERY", "Query execution aborted after " + (System.currentTimeMillis() - startStep) + "ms!", null);
+			logger.logTAP(LogLevel.INFO, report, "END_DB_EXECUTION", "Query execution aborted after " + (System.currentTimeMillis() - startStep) + "ms!", null);
 		else
-			logger.logTAP(LogLevel.INFO, report, "END_QUERY", "Query successfully executed in " + (System.currentTimeMillis() - startStep) + "ms!", null);
+			logger.logTAP(LogLevel.INFO, report, "END_DB_EXECUTION", "Query successfully executed in " + (System.currentTimeMillis() - startStep) + "ms!", null);
+
 		return result;
 	}
 
@@ -582,12 +588,15 @@ public class ADQLExecutor {
 	 * @param queryResult	The result of the query execution in database.
 	 * 
 	 * @throws InterruptedException	If the thread has been interrupted.
+	 * @throws IOException			If an error happens while writing the result in the {@link HttpServletResponse}.
+	 *                    			<i>That kind of error can be thrown only in synchronous mode.
+	 *                    			In asynchronous, the error is stored as job error report and is never propagated.</i>
 	 * @throws TAPException			If an error occurs while getting the appropriate formatter or while formatting or writing (synchronous execution) the result.
 	 * @throws UWSException			If an error occurs while getting the output stream or while writing (asynchronous execution) the result.
 	 * 
 	 * @see #writeResult(TableIterator, OutputFormat, OutputStream)
 	 */
-	protected final void writeResult(final TableIterator queryResult) throws InterruptedException, TAPException, UWSException{
+	protected final void writeResult(final TableIterator queryResult) throws InterruptedException, IOException, TAPException, UWSException{
 		// Log the start of the writing:
 		logger.logTAP(LogLevel.INFO, report, "WRITING_RESULT", "Writing the query result", null);
 
@@ -596,19 +605,20 @@ public class ADQLExecutor {
 
 		// CASE SYNCHRONOUS:
 		if (response != null){
-			try{
-				// Set the HTTP content type to the MIME type of the result format:
-				response.setContentType(formatter.getMimeType());
+			long start = -1;
 
-				// Write the formatted result in the HTTP response output:
-				writeResult(queryResult, formatter, response.getOutputStream());
+			// Set the HTTP content type to the MIME type of the result format:
+			response.setContentType(formatter.getMimeType());
 
-			}catch(IOException ioe){
-				throw new TAPException("Impossible to get the HTTP response, so the result of the job " + report.jobID + " can not be written!", ioe, UWSException.INTERNAL_SERVER_ERROR);
-			}
+			// Write the formatted result in the HTTP response output:
+			start = System.currentTimeMillis();
+			writeResult(queryResult, formatter, response.getOutputStream());
+
+			logger.logTAP(LogLevel.INFO, report, "RESULT_WRITTEN", "Result formatted (in " + formatter.getMimeType() + " ; " + (report.nbRows < 0 ? "?" : report.nbRows) + " rows ; " + ((report.resultingColumns == null) ? "?" : report.resultingColumns.length) + " columns) in " + ((start <= 0) ? "?" : (System.currentTimeMillis() - start)) + "ms!", null);
 		}
 		// CASE ASYNCHRONOUS:
 		else{
+			long start = -1, end = -1;
 			try{
 				// Create a UWS Result object to store the result
 				// (the result will be stored in a file and this object is the association between the job and the result file):
@@ -619,7 +629,9 @@ public class ADQLExecutor {
 				result.setMimeType(formatter.getMimeType());
 
 				// Write the formatted result in the file output:
+				start = System.currentTimeMillis();
 				writeResult(queryResult, formatter, jobThread.getResultOutput(result));
+				end = System.currentTimeMillis();
 
 				// Set the size (in bytes) of the result in the result description:
 				result.setSize(jobThread.getResultSize(result));
@@ -627,8 +639,10 @@ public class ADQLExecutor {
 				// Add the result description and link in the job description:
 				jobThread.publishResult(result);
 
+				logger.logTAP(LogLevel.INFO, report, "RESULT_WRITTEN", "Result formatted (in " + formatter.getMimeType() + " ; " + (report.nbRows < 0 ? "?" : report.nbRows) + " rows ; " + ((report.resultingColumns == null) ? "?" : report.resultingColumns.length) + " columns) in " + ((start <= 0 || end <= 0) ? "?" : (end - start)) + "ms!", null);
+
 			}catch(IOException ioe){
-				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to access the file into the result of the job " + report.jobID + " must be written!");
+				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to write in the file into the result of the job " + report.jobID + " must be written!");
 			}
 		}
 	}
@@ -648,9 +662,10 @@ public class ADQLExecutor {
 	 * @param output		The stream in which the result must be written.
 	 * 
 	 * @throws InterruptedException	If the thread has been interrupted.
+	 * @throws IOException			If there is an error while writing the result in the given stream.
 	 * @throws TAPException			If there is an error while formatting the result.
 	 */
-	protected void writeResult(TableIterator queryResult, OutputFormat formatter, OutputStream output) throws InterruptedException, TAPException{
+	protected void writeResult(TableIterator queryResult, OutputFormat formatter, OutputStream output) throws InterruptedException, IOException, TAPException{
 		formatter.writeResult(queryResult, output, report, thread);
 	}
 
diff --git a/src/tap/TAPExecutionReport.java b/src/tap/TAPExecutionReport.java
index 45ce99b893f9d2e214bc0404b64b789c55d15a7f..fe2733f07ac4fe4fc6e2bbb3be59de678e503fb5 100644
--- a/src/tap/TAPExecutionReport.java
+++ b/src/tap/TAPExecutionReport.java
@@ -16,7 +16,7 @@ package tap;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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)
  */
 
@@ -31,7 +31,7 @@ import adql.db.DBColumn;
  * <p>This report is completely filled by {@link ADQLExecutor}, and aims to be used/read only at the end of the job or when it is definitely finished.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2014)
+ * @version 2.0 (04/2015)
  */
 public class TAPExecutionReport {
 
@@ -47,6 +47,10 @@ public class TAPExecutionReport {
 	/** List of all resulting columns. <i>Empty array, if not yet known.</i> */
 	public DBColumn[] resultingColumns = new DBColumn[0];
 
+	/** Total number of written rows.
+	 * @since 2.0 */
+	public long nbRows = -1;
+
 	/** Duration of all execution steps. <i>For the moment only 4 steps (in the order): uploading, parsing, executing and writing.</i> */
 	protected final long[] durations = new long[]{-1,-1,-1,-1};
 
diff --git a/src/tap/TAPJob.java b/src/tap/TAPJob.java
index 597d24eeee1fc0cb71960f7a3745eff4190a07df..aa89491428ac40e8d058d38b58fac92a7b6102a1 100644
--- a/src/tap/TAPJob.java
+++ b/src/tap/TAPJob.java
@@ -16,7 +16,7 @@ package tap;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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)
  */
 
@@ -45,7 +45,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (12/2014)
+ * @version 2.0 (04/2015)
  */
 public class TAPJob extends UWSJob {
 	private static final long serialVersionUID = 1L;
@@ -253,7 +253,7 @@ public class TAPJob extends UWSJob {
 	 * @throws UWSException	If this job has never been restored and is not running.
 	 */
 	public final void setExecReport(final TAPExecutionReport execReport) throws UWSException{
-		if (getRestorationDate() == null && !isRunning())
+		if (getRestorationDate() == null && (thread == null || thread.isFinished()))
 			throw new UWSException("Impossible to set an execution report if the job is not in the EXECUTING phase ! Here, the job \"" + jobId + "\" is in the phase " + getPhase());
 		this.execReport = execReport;
 	}
diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java
index 78411c71d280a48c0f1315a491c40bc3a17272c3..7b65a01cb2eda3ce1b27d2a073c1808844c73d7e 100644
--- a/src/tap/TAPSyncJob.java
+++ b/src/tap/TAPSyncJob.java
@@ -20,6 +20,7 @@ package tap;
  *                       Astronomisches Rechen Institut (ARI)
  */
 
+import java.io.IOException;
 import java.util.Date;
 
 import javax.servlet.http.HttpServletResponse;
@@ -162,16 +163,17 @@ public class TAPSyncJob {
 	 * @return	<i>true</i> if the execution was successful, <i>false</i> otherwise.
 	 * 
 	 * @throws IllegalStateException	If this synchronous job has already been started before.
+	 * @throws IOException				If any error occurs while writing the query result in the given {@link HttpServletResponse}.
 	 * @throws TAPException				If any error occurs while executing the ADQL query.
 	 * 
 	 * @see SyncThread
 	 */
-	public synchronized boolean start(final HttpServletResponse response) throws IllegalStateException, TAPException{
+	public synchronized boolean start(final HttpServletResponse response) throws IllegalStateException, IOException, TAPException{
 		if (startedAt != null)
-			throw new IllegalStateException("Impossible to restart a synchronous TAP query !");
+			throw new IllegalStateException("Impossible to restart a synchronous TAP query!");
 
 		// Log the start of this sync job:
-		service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_START", "Synchronous job " + ID + " is starting!", null);
+		service.getLogger().logTAP(LogLevel.INFO, this, "START", "Synchronous job " + ID + " is starting!", null);
 
 		// Create the object having the knowledge about how to execute an ADQL query:
 		ADQLExecutor executor = service.getFactory().createADQLExecutor();
@@ -179,7 +181,7 @@ public class TAPSyncJob {
 			executor.initDBConnection(ID);
 		}catch(TAPException te){
 			service.getLogger().logDB(LogLevel.ERROR, null, "CONNECTION_LACK", "No more database connection available for the moment!", te);
-			service.getLogger().logTAP(LogLevel.ERROR, this, "END_EXEC", "Synchronous job " + ID + " execution aborted: no database connection available!", null);
+			service.getLogger().logTAP(LogLevel.ERROR, this, "END", "Synchronous job " + ID + " execution aborted: no database connection available!", null);
 			throw new TAPException("TAP service too busy! No connection available for the moment. You should try later or create an asynchronous query (which will be executed when enough resources will be available again).", UWSException.SERVICE_UNAVAILABLE);
 		}
 
@@ -211,9 +213,9 @@ public class TAPSyncJob {
 		if (timeout && error != null && error instanceof InterruptedException){
 			// Log the timeout:
 			if (thread.isAlive())
-				service.getLogger().logTAP(LogLevel.WARNING, this, "SYNC_TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ", but the thread can not be interrupted!", null);
+				service.getLogger().logTAP(LogLevel.WARNING, this, "TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ", but the thread can not be interrupted!", null);
 			else
-				service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ".", null);
+				service.getLogger().logTAP(LogLevel.INFO, this, "TIME_OUT", "Time out (after " + tapParams.getExecutionDuration() + "ms) for the synchonous job " + ID + ".", null);
 
 			// Report the timeout to the user:
 			throw new TAPException("Time out! The execution of this synchronous TAP query was limited to " + tapParams.getExecutionDuration() + "ms. You should try again but in asynchronous execution.", UWSException.ACCEPTED_BUT_NOT_COMPLETE);
@@ -223,21 +225,28 @@ public class TAPSyncJob {
 			// INTERRUPTION:
 			if (error instanceof InterruptedException){
 				// log the unexpected interruption (unexpected because not caused by a timeout):
-				service.getLogger().logTAP(LogLevel.WARNING, this, "SYNC_END", "The execution of the synchronous job " + ID + " has been unexpectedly interrupted!", null);
+				service.getLogger().logTAP(LogLevel.ERROR, this, "END", "The execution of the synchronous job " + ID + " has been unexpectedly interrupted!", error);
 				// report the unexpected interruption to the user:
 				throw new TAPException("The execution of this synchronous job " + ID + " has been unexpectedly aborted!", UWSException.ACCEPTED_BUT_NOT_COMPLETE);
 			}
+			// REQUEST ABORTION:
+			else if (error instanceof IOException){
+				// log the unexpected interruption (unexpected because not caused by a timeout):
+				service.getLogger().logTAP(LogLevel.INFO, this, "END", "Abortion of the synchronous job " + ID + "! Cause: connection with the HTTP client unexpectedly closed.", null);
+				// throw the error until the TAP instance to notify it about the abortion:
+				throw (IOException)error;
+			}
 			// TAP EXCEPTION:
 			else if (error instanceof TAPException){
 				// log the error:
-				service.getLogger().logTAP(LogLevel.ERROR, this, "SYNC_END", "An error occured while executing the query of the synchronous job " + ID + ".", null);
+				service.getLogger().logTAP(LogLevel.ERROR, this, "END", "The following error interrupted the execution of the synchronous job " + ID + ".", error);
 				// report the error to the user:
 				throw (TAPException)error;
 			}
 			// ANY OTHER EXCEPTION:
 			else{
 				// log the error:
-				service.getLogger().logTAP(LogLevel.FATAL, this, "SYNC_END", "An unexpected error has stopped the execution of the synchronous job " + ID + ".", error);
+				service.getLogger().logTAP(LogLevel.FATAL, this, "END", "The following GRAVE error interrupted the execution of the synchronous job " + ID + ".", error);
 				// report the error to the user:
 				if (error instanceof Error)
 					throw (Error)error;
@@ -245,7 +254,7 @@ public class TAPSyncJob {
 					throw new TAPException(error);
 			}
 		}else
-			service.getLogger().logTAP(LogLevel.INFO, this, "SYNC_END", "The synchronous job " + ID + " successfully ended.", null);
+			service.getLogger().logTAP(LogLevel.INFO, this, "END", "Success of the synchronous job " + ID + ".", null);
 
 		return thread.isSuccess();
 	}
@@ -259,7 +268,7 @@ public class TAPSyncJob {
 	 * </p>
 	 * 
 	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
-	 * @version 2.0 (09/2014)
+	 * @version 2.0 (04/2015)
 	 */
 	protected class SyncThread extends Thread {
 
@@ -342,15 +351,15 @@ public class TAPSyncJob {
 				exception = e;
 
 				// Log the end of the job:
-				if (e instanceof InterruptedException)
+				if (e instanceof InterruptedException || e instanceof IOException)
 					// Abortion:
 					executor.getLogger().logThread(LogLevel.INFO, this, "END", "Synchronous thread \"" + ID + "\" cancelled.", null);
-				else if (e instanceof Error)
-					// GRAVE error:
-					executor.getLogger().logThread(LogLevel.FATAL, this, "END", "Synchronous thread \"" + ID + "\" ended with a FATAL error.", exception);
-				else
+				else if (e instanceof TAPException)
 					// Error:
 					executor.getLogger().logThread(LogLevel.ERROR, this, "END", "Synchronous thread \"" + ID + "\" ended with an error.", null);
+				else
+					// GRAVE error:
+					executor.getLogger().logThread(LogLevel.FATAL, this, "END", "Synchronous thread \"" + ID + "\" ended with a FATAL error.", null);
 			}
 		}
 
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index 52d047570c40b58c70ba7e7adf51330e8d87df43..405b30479df2070c8fd217272509b2b14b9eab9a 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -435,27 +435,27 @@ public class JDBCConnection implements DBConnection {
 			// 3. Execute the SQL query:
 			result = stmt.executeQuery(sql);
 			if (logger != null)
-				logger.logDB(LogLevel.INFO, this, "EXECUTE", "Executing translated query: " + sql.replaceAll("(\t|\r?\n)+", " "), null);
+				logger.logDB(LogLevel.INFO, this, "EXECUTE", "SQL query: " + sql.replaceAll("(\t|\r?\n)+", " "), null);
 
 			// 4. Return the result through a TableIterator object:
 			if (logger != null)
-				logger.logDB(LogLevel.INFO, this, "RESULT", "Returning result", null);
+				logger.logDB(LogLevel.INFO, this, "RESULT", "Returning result (" + (supportsFetchSize ? "fetch size = " + fetchSize : "all in once") + ").", null);
 			return createTableIterator(result, adqlQuery.getResultingColumns());
 
 		}catch(SQLException se){
 			close(result);
 			if (logger != null)
-				logger.logDB(LogLevel.ERROR, this, "EXECUTE", "Unexpected error while EXECUTING SQL query!", se);
+				logger.logDB(LogLevel.ERROR, this, "EXECUTE", "Unexpected error while EXECUTING SQL query!", null);
 			throw new DBException("Unexpected error while executing a SQL query: " + se.getMessage(), se);
 		}catch(TranslationException te){
 			close(result);
 			if (logger != null)
-				logger.logDB(LogLevel.ERROR, this, "TRANSLATE", "Unexpected error while TRANSLATING ADQL into SQL!", te);
+				logger.logDB(LogLevel.ERROR, this, "TRANSLATE", "Unexpected error while TRANSLATING ADQL into SQL!", null);
 			throw new DBException("Unexpected error while translating ADQL into SQL: " + te.getMessage(), te);
 		}catch(DataReadException dre){
 			close(result);
 			if (logger != null)
-				logger.logDB(LogLevel.ERROR, this, "RESULT", "Unexpected error while reading the query result!", dre);
+				logger.logDB(LogLevel.ERROR, this, "RESULT", "Unexpected error while reading the query result!", null);
 			throw new DBException("Impossible to read the query result, because: " + dre.getMessage(), dre);
 		}
 	}
diff --git a/src/tap/error/DefaultTAPErrorWriter.java b/src/tap/error/DefaultTAPErrorWriter.java
index d95cd717d65acd96c534421ef338c4880a1743fc..22a542c32ab2c1710c1472e4a1352c2a72276c89 100644
--- a/src/tap/error/DefaultTAPErrorWriter.java
+++ b/src/tap/error/DefaultTAPErrorWriter.java
@@ -16,13 +16,15 @@ package tap.error;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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 java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.sql.SQLException;
@@ -73,7 +75,7 @@ import uws.service.error.ServiceErrorWriter;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (12/2014)
+ * @version 2.0 (04/2015)
  */
 public class DefaultTAPErrorWriter implements ServiceErrorWriter {
 
@@ -155,10 +157,11 @@ public class DefaultTAPErrorWriter implements ServiceErrorWriter {
 	}
 
 	@Override
-	public void writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException{
+	public boolean writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action){
 		if (t == null || response == null)
-			return;
+			return true;
 
+		boolean written = false;
 		// If expected error, just write it in VOTable:
 		if (t instanceof UWSException || t instanceof TAPException){
 			// get the error type:
@@ -166,41 +169,68 @@ public class DefaultTAPErrorWriter implements ServiceErrorWriter {
 			// get the HTTP error code:
 			int httpErrorCode = (t instanceof UWSException) ? ((UWSException)t).getHttpErrorCode() : ((TAPException)t).getHttpErrorCode();
 			// write the VOTable error:
-			writeError(t.getMessage(), type, httpErrorCode, response, request, reqID, user, action);
+			written = writeError(t.getMessage(), type, httpErrorCode, response, request, reqID, user, action);
 		}
 		// Otherwise, log it and write a message to the user:
 		else
 			// write a message to the user:
-			writeError("INTERNAL SERVER ERROR! Sorry, this error is grave and unexpected. No explanation can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following REQ_ID).", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
+			written = writeError("INTERNAL SERVER ERROR! Sorry, this error is grave and unexpected. No explanation can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following REQ_ID).", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
+		return written;
 	}
 
 	@Override
-	public void writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException{
+	public boolean writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action){
 		if (message == null || response == null)
-			return;
+			return true;
 
-		// Erase anything written previously in the HTTP response:
-		response.reset();
+		try{
+			// Erase anything written previously in the HTTP response:
+			response.reset();
 
-		// Set the HTTP status:
-		response.setStatus((httpErrorCode <= 0) ? 500 : httpErrorCode);
+			// Set the HTTP status:
+			response.setStatus((httpErrorCode <= 0) ? 500 : httpErrorCode);
 
-		// Set the MIME type of the answer (XML for a VOTable document):
-		response.setContentType("application/xml");
+			// Set the MIME type of the answer (XML for a VOTable document):
+			response.setContentType("application/xml");
 
-		// List any additional information useful to report to the user:
-		Map<String,String> addInfos = new LinkedHashMap<String,String>();
-		if (reqID != null)
-			addInfos.put("REQ_ID", reqID);
-		if (type != null)
-			addInfos.put("ERROR_TYPE", type.toString());
-		if (user != null)
-			addInfos.put("USER", user.getID() + ((user.getPseudo() == null) ? "" : " (" + user.getPseudo() + ")"));
-		if (action != null)
-			addInfos.put("ACTION", action);
+		}catch(IllegalStateException ise){
+			/*   If it is not possible any more to reset the response header and body,
+			 * the error is anyway written in order to corrupt the HTTP response.
+			 *   Thus, it will be obvious that an error occurred and the result is
+			 * incomplete and/or wrong.*/
+		}
 
-		// Format the error in VOTable and write the document in the given HTTP response:
-		getFormatter().writeError(message, addInfos, response.getWriter());
+		try{
+			// List any additional information useful to report to the user:
+			Map<String,String> addInfos = new LinkedHashMap<String,String>();
+			if (reqID != null)
+				addInfos.put("REQ_ID", reqID);
+			if (type != null)
+				addInfos.put("ERROR_TYPE", type.toString());
+			if (user != null)
+				addInfos.put("USER", user.getID() + ((user.getPseudo() == null) ? "" : " (" + user.getPseudo() + ")"));
+			if (action != null)
+				addInfos.put("ACTION", action);
+
+			// Format the error in VOTable and write the document in the given HTTP response:
+			PrintWriter writer;
+			try{
+				writer = response.getWriter();
+			}catch(IllegalStateException ise){
+				/*   This exception may occur just because either the writer or
+				 * the output-stream can be used (because already got before).
+				 *   So, we just have to get the output-stream if getting the writer
+				 * throws an error.*/
+				writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(response.getOutputStream())));
+			}
+			getFormatter().writeError(message, addInfos, writer);
+
+			return true;
+		}catch(IllegalStateException ise){
+			return false;
+		}catch(IOException ioe){
+			return false;
+		}
 	}
 
 	@Override
@@ -272,7 +302,7 @@ public class DefaultTAPErrorWriter implements ServiceErrorWriter {
 			if (causes.length() > 0)
 				addInfos.put("CAUSES", "\n" + nbCauses + causes.toString());
 
-			// Add the stack trace of the original exception ONLY IF NOT A TAP OR A UWS EXCEPTION (only unexpected error should be detailed to the users):
+			// Add the stack trace of the original exception ONLY IF NOT A TAP NOR A UWS EXCEPTION (only unexpected error should be detailed to the users):
 			if (!(lastCause instanceof TAPException && lastCause instanceof UWSException)){
 				ByteArrayOutputStream stackTrace = new ByteArrayOutputStream();
 				lastCause.printStackTrace(new PrintStream(stackTrace));
diff --git a/src/tap/formatter/FITSFormat.java b/src/tap/formatter/FITSFormat.java
index 42bf9fcc81d35e1623f938172145e74a9af19140..20c7d928c84661d522ec3b17643db904840ce382 100644
--- a/src/tap/formatter/FITSFormat.java
+++ b/src/tap/formatter/FITSFormat.java
@@ -16,7 +16,7 @@ package tap.formatter;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2014 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2014-2015 - Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -31,20 +31,16 @@ import uk.ac.starlink.fits.FitsTableWriter;
 import uk.ac.starlink.table.ColumnInfo;
 import uk.ac.starlink.table.StarTable;
 import uk.ac.starlink.table.StoragePolicy;
-import uws.service.log.UWSLog.LogLevel;
 
 /**
  * Format any given query (table) result into FITS.
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (10/2014)
+ * @version 2.0 (04/2015)
  * @since 2.0
  */
 public class FITSFormat implements OutputFormat {
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
 	protected final ServiceConnection service;
 
@@ -56,23 +52,10 @@ public class FITSFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public FITSFormat(final ServiceConnection service) throws NullPointerException{
-		this(service, true);
-	}
-
-	/**
-	 * Creates a FITS formatter.
-	 * 
-	 * @param service				The service to use (for the log and to have some information about the service (particularly: name, description).
-	 * @param logFormatReport		<code>true</code> to append a format report (start and end date/time) in the log output, <code>false</code> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public FITSFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL !");
 
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 	}
 
 	@Override
@@ -96,30 +79,23 @@ public class FITSFormat implements OutputFormat {
 	}
 
 	@Override
-	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
-		try{
-			long start = System.currentTimeMillis();
-
-			// Extract the columns' metadata:
-			ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread);
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{
+		// Extract the columns' metadata:
+		ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread);
 
-			// Turns the result set into a table:
-			LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec());
+		// Turns the result set into a table:
+		LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec());
 
-			// Copy the table on disk (or in memory if the table is short):
-			StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table);
+		// Copy the table on disk (or in memory if the table is short):
+		StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table);
 
-			/* Format the table in FITS (2 passes are needed for that, hence the copy on disk),
-			 * and write it in the given output stream: */
-			new FitsTableWriter().writeStarTable(copyTable, output);
+		/* Format the table in FITS (2 passes are needed for that, hence the copy on disk),
+		 * and write it in the given output stream: */
+		new FitsTableWriter().writeStarTable(copyTable, output);
 
-			output.flush();
+		execReport.nbRows = table.getNbReadRows();
 
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in FITS ; " + table.getNbReadRows() + " rows ; " + table.getColumnCount() + " columns) in " + (System.currentTimeMillis() - start) + "ms!", null);
-		}catch(IOException ioe){
-			throw new TAPException("Error while writing a query result in FITS!", ioe);
-		}
+		output.flush();
 	}
 
 }
diff --git a/src/tap/formatter/HTMLFormat.java b/src/tap/formatter/HTMLFormat.java
index 64054f077f4fb7d5074ad2a2c7bc26e4ce4d17ae..df179fa5763b1e75c0692b9e15619f208de790a8 100644
--- a/src/tap/formatter/HTMLFormat.java
+++ b/src/tap/formatter/HTMLFormat.java
@@ -19,9 +19,10 @@ package tap.formatter;
  * Copyright 2014 - Astronomisches Rechen Institut (ARI)
  */
 
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
+import java.io.OutputStreamWriter;
 
 import tap.ServiceConnection;
 import tap.TAPException;
@@ -29,7 +30,6 @@ import tap.TAPExecutionReport;
 import tap.data.TableIterator;
 import uk.ac.starlink.votable.VOSerializer;
 import uws.ISO8601Format;
-import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 
 /**
@@ -41,9 +41,6 @@ import adql.db.DBColumn;
  */
 public class HTMLFormat implements OutputFormat {
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
 	protected final ServiceConnection service;
 
@@ -55,23 +52,10 @@ public class HTMLFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public HTMLFormat(final ServiceConnection service) throws NullPointerException{
-		this(service, true);
-	}
-
-	/**
-	 * Creates an HTML formatter
-	 * 
-	 * @param service			Description of the TAP service.
-	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public HTMLFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL!");
 
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 	}
 
 	@Override
@@ -95,30 +79,24 @@ public class HTMLFormat implements OutputFormat {
 	}
 
 	@Override
-	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
-		try{
-			final long startTime = System.currentTimeMillis();
-
-			// Prepare the output stream:
-			final PrintWriter writer = new PrintWriter(output);
-			writer.println("<table>");
-
-			// Write header:
-			DBColumn[] columns = writeHeader(result, writer, execReport, thread);
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{
+		// Prepare the output stream:
+		final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
+		writer.write("<table>");
+		writer.newLine();
 
-			// Write data:
-			int nbRows = writeData(result, columns, writer, execReport, thread);
+		// Write header:
+		DBColumn[] columns = writeHeader(result, writer, execReport, thread);
 
-			writer.println("</table>");
-			writer.flush();
+		if (thread.isInterrupted())
+			throw new InterruptedException();
 
-			// Report stats about the result writing:
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in HTML ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + "ms!", null);
+		// Write data:
+		writeData(result, columns, writer, execReport, thread);
 
-		}catch(Exception ex){
-			service.getLogger().logTAP(LogLevel.ERROR, execReport, "FORMAT", "Error while formatting in HTML!", ex);
-		}
+		writer.write("</table>");
+		writer.newLine();
+		writer.flush();
 	}
 
 	/**
@@ -135,14 +113,15 @@ public class HTMLFormat implements OutputFormat {
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected DBColumn[] writeHeader(TableIterator result, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+	protected DBColumn[] writeHeader(TableIterator result, BufferedWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
 		// Prepend a description of this result:
-		writer.print("<caption>TAP result");
+		writer.write("<caption>TAP result");
 		if (service.getProviderName() != null)
-			writer.print(" from " + VOSerializer.formatText(service.getProviderName()));
-		writer.print(" on " + ISO8601Format.format(System.currentTimeMillis()));
-		writer.print("<br/><em>" + VOSerializer.formatText(execReport.parameters.getQuery()) + "</em>");
-		writer.println("</caption>");
+			writer.write(" from " + VOSerializer.formatText(service.getProviderName()));
+		writer.write(" on " + ISO8601Format.format(System.currentTimeMillis()));
+		writer.write("<br/><em>" + VOSerializer.formatText(execReport.parameters.getQuery()) + "</em>");
+		writer.write("</caption>");
+		writer.newLine();
 
 		// Get the columns meta:
 		DBColumn[] selectedColumns = execReport.resultingColumns;
@@ -150,19 +129,22 @@ public class HTMLFormat implements OutputFormat {
 		// If meta are not known, no header will be written:
 		int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length;
 		if (nbColumns > 0){
-			writer.println("<thead>");
-			writer.print("<tr>");
+			writer.write("<thead>");
+			writer.newLine();
+			writer.write("<tr>");
 
 			// Write all columns' name:
 			for(int i = 0; i < nbColumns; i++){
-				writer.print("<th>");
-				writer.print(VOSerializer.formatText(selectedColumns[i].getADQLName()));
-				writer.print("</th>");
+				writer.write("<th>");
+				writer.write(VOSerializer.formatText(selectedColumns[i].getADQLName()));
+				writer.write("</th>");
 			}
 
 			// Go to a new line (in order to prepare the data writing):
-			writer.println("</tr>");
-			writer.println("</thead>");
+			writer.write("</tr>");
+			writer.newLine();
+			writer.write("</thead>");
+			writer.newLine();
 			writer.flush();
 		}
 
@@ -179,50 +161,55 @@ public class HTMLFormat implements OutputFormat {
 	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
 	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
 	 * 
-	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
-	 * 
 	 * @throws IOException				If there is an error while writing something in the output stream.
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected int writeData(TableIterator result, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
-		int nbRows = 0;
+	protected void writeData(TableIterator result, DBColumn[] selectedColumns, BufferedWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+		execReport.nbRows = 0;
 
-		writer.println("<tbody>");
+		writer.write("<tbody>");
+		writer.newLine();
 
 		while(result.nextRow()){
+			// Stop right now the formatting if the job has been aborted/canceled/interrupted:
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+
 			// Deal with OVERFLOW, if needed:
-			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()){ // that's to say: OVERFLOW !
-				writer.println("<tr class=\"OVERFLOW\"><td colspan=\"" + selectedColumns.length + "\"><em><strong>OVERFLOW</strong> (more rows were available but have been truncated by the TAP service)</em></td></tr>");
+			if (execReport.parameters.getMaxRec() > 0 && execReport.nbRows >= execReport.parameters.getMaxRec()){ // that's to say: OVERFLOW !
+				writer.write("<tr class=\"OVERFLOW\"><td colspan=\"" + selectedColumns.length + "\"><em><strong>OVERFLOW</strong> (more rows were available but have been truncated by the TAP service)</em></td></tr>");
+				writer.newLine();
 				break;
 			}
 
-			writer.print("<tr>");
+			writer.write("<tr>");
 
 			while(result.hasNextCol()){
-				writer.print("<td>");
+				writer.write("<td>");
 
 				// Write the column value:
 				Object colVal = result.nextCol();
 				if (colVal != null)
-					writer.print(VOSerializer.formatText(colVal.toString()));
+					writer.write(VOSerializer.formatText(colVal.toString()));
 
-				writer.print("</td>");
+				writer.write("</td>");
 
 				if (thread.isInterrupted())
 					throw new InterruptedException();
 			}
-			writer.println("</tr>");
-			nbRows++;
+			writer.write("</tr>");
+			writer.newLine();
+			execReport.nbRows++;
 
-			if (thread.isInterrupted())
-				throw new InterruptedException();
+			// flush the writer every 30 lines:
+			if (execReport.nbRows % 30 == 0)
+				writer.flush();
 		}
 
-		writer.println("</tbody>");
+		writer.write("</tbody>");
+		writer.newLine();
 		writer.flush();
-
-		return nbRows;
 	}
 
 }
diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java
index 1596827b50b8459c191c808c29a9c51c0c2f1091..439008dacb884f27c9b137fd82a2d5e178fcfcc1 100644
--- a/src/tap/formatter/JSONFormat.java
+++ b/src/tap/formatter/JSONFormat.java
@@ -16,13 +16,14 @@ package tap.formatter;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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 java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
+import java.io.OutputStreamWriter;
 
 import org.json.JSONException;
 import org.json.JSONWriter;
@@ -33,7 +34,6 @@ import tap.TAPExecutionReport;
 import tap.data.TableIterator;
 import tap.metadata.TAPColumn;
 import tap.metadata.VotType;
-import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
@@ -42,44 +42,25 @@ import adql.db.DBType.DBDatatype;
  * Format any given query (table) result into JSON.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (10/2014)
+ * @version 2.0 (04/2015)
  */
 public class JSONFormat implements OutputFormat {
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
 	protected final ServiceConnection service;
 
 	/**
-	 * <p>Build a JSON formatter.</p>
-	 * 
-	 * <p><i>note: The built formatter will not write a log entry each time a result is written.
-	 * However if you want this behavior you must you {@link #JSONFormat(ServiceConnection, boolean)}.</i></p>
+	 * Build a JSON formatter.
 	 * 
 	 * @param service	Description of the TAP service.
 	 * 
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public JSONFormat(final ServiceConnection service) throws NullPointerException{
-		this(service, true);
-	}
-
-	/**
-	 * Build a JSON formatter.
-	 * 
-	 * @param service			Description of the TAP service.
-	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public JSONFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL!");
 
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 	}
 
 	@Override
@@ -103,12 +84,11 @@ public class JSONFormat implements OutputFormat {
 	}
 
 	@Override
-	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{
 		try{
-			long start = System.currentTimeMillis();
 
 			// Prepare the output stream for JSON:
-			PrintWriter writer = new PrintWriter(output);
+			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
 			JSONWriter out = new JSONWriter(writer);
 
 			// {
@@ -122,24 +102,21 @@ public class JSONFormat implements OutputFormat {
 
 			writer.flush();
 
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+
 			// "data": [...]
 			out.key("data");
 
 			// Write the data part:
-			int nbRows = writeData(result, columns, out, execReport, thread);
+			writeData(result, columns, out, execReport, thread);
 
 			// }
 			out.endObject();
 			writer.flush();
 
-			// Report stats about the result writing:
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in JSON ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + "ms!", null);
-
 		}catch(JSONException je){
-			throw new TAPException("Error while writing a query result in JSON!", je);
-		}catch(IOException ioe){
-			throw new TAPException("Error while writing a query result in JSON!", ioe);
+			throw new TAPException(je.getMessage(), je);
 		}
 	}
 
@@ -185,9 +162,6 @@ public class JSONFormat implements OutputFormat {
 				// Write the field/column metadata in the JSON output:
 				writeFieldMeta(tapCol, out);
 				indField++;
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
 			}
 		}
 
@@ -273,44 +247,38 @@ public class JSONFormat implements OutputFormat {
 	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
 	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
 	 * 
-	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
-	 * 
 	 * @throws IOException				If there is an error while writing something in the output stream.
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws JSONException			If there is an error while formatting something in JSON.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected int writeData(TableIterator result, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
+	protected void writeData(TableIterator result, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
 		// [
 		out.array();
 
-		int nbRows = 0;
+		execReport.nbRows = 0;
 		while(result.nextRow()){
+			// Stop right now the formatting if the job has been aborted/canceled/interrupted:
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+
 			// Deal with OVERFLOW, if needed:
-			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
+			if (execReport.parameters.getMaxRec() > 0 && execReport.nbRows >= execReport.parameters.getMaxRec())
 				break;
 
 			// [
 			out.array();
 			int indCol = 0;
-			while(result.hasNextCol()){
+			while(result.hasNextCol())
 				// ...
 				writeFieldValue(result.nextCol(), selectedColumns[indCol++], out);
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
-			}
 			// ]
 			out.endArray();
-			nbRows++;
-
-			if (thread.isInterrupted())
-				throw new InterruptedException();
+			execReport.nbRows++;
 		}
 
 		// ]
 		out.endArray();
-		return nbRows;
 	}
 
 	/**
diff --git a/src/tap/formatter/OutputFormat.java b/src/tap/formatter/OutputFormat.java
index a881a2acf2e1dd26a3ae9e232df05612901572b6..f450c4be68bfb563865e5d4607d186ac92e6d8b9 100644
--- a/src/tap/formatter/OutputFormat.java
+++ b/src/tap/formatter/OutputFormat.java
@@ -16,10 +16,11 @@ package tap.formatter;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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 java.io.IOException;
 import java.io.OutputStream;
 
 import tap.TAPException;
@@ -31,7 +32,7 @@ import tap.data.TableIterator;
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * 
- * @version 2.0 (07/2014)
+ * @version 2.0 (03/2015)
  */
 public interface OutputFormat {
 
@@ -73,8 +74,10 @@ public interface OutputFormat {
 	 * @param execReport	The report of the execution of the TAP query whose the result must be now written.
 	 * @param thread		The thread which has asked the result writing.
 	 * 
-	 * @throws TAPException		If there is an error while formatting/writing the query result.
+	 * @throws TAPException			If there is an error while formatting the query result.
+	 * @throws IOException			If any error occurs while writing into the given stream.
+	 * @throws InterruptedException	If the query has been interrupted/aborted.
 	 */
-	public void writeResult(final TableIterator result, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException;
+	public void writeResult(final TableIterator result, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, IOException, InterruptedException;
 
 }
diff --git a/src/tap/formatter/SVFormat.java b/src/tap/formatter/SVFormat.java
index ba0879e6b4db4277865411e8a1af977f0d1d9099..89adf09a25d4b1790db73e97c3da73aece7286d9 100644
--- a/src/tap/formatter/SVFormat.java
+++ b/src/tap/formatter/SVFormat.java
@@ -16,32 +16,29 @@ package tap.formatter;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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 java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
+import java.io.OutputStreamWriter;
 
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
 import tap.data.TableIterator;
-import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 
 /**
  * Format any given query (table) result into CSV or TSV (or with custom separator).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (01/2015)
+ * @version 2.0 (04/2015)
  */
 public class SVFormat implements OutputFormat {
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** Column separator for CSV format. */
 	public static final char COMMA_SEPARATOR = ',';
 	/** Column separator for sCSV format. */
@@ -88,7 +85,7 @@ public class SVFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings) throws NullPointerException{
-		this(service, colSeparator, delimitStrings, null, null, true);
+		this(service, colSeparator, delimitStrings, null, null);
 	}
 
 	/**
@@ -105,25 +102,7 @@ public class SVFormat implements OutputFormat {
 	 * @since 2.0
 	 */
 	public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings, final String mime, final String shortMime) throws NullPointerException{
-		this(service, colSeparator, delimitStrings, mime, shortMime, true);
-	}
-
-	/**
-	 * Build a SVFormat.
-	 * 
-	 * @param service			Description of the TAP service.
-	 * @param colSeparator		Column separator to use.
-	 * @param delimitStrings	<i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise.
-	 * @param mime				The MIME type to associate with this format. <i>note: this MIME type is then used by a user to specify the result format he wants.</i>
-	 * @param shortMime			The alias of the MIME type to associate with this format. <i>note: this short MIME type is then used by a user to specify the result format he wants.</i>
-	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 * 
-	 * @since 2.0
-	 */
-	public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings, final String mime, final String shortMime, final boolean logFormatReport) throws NullPointerException{
-		this(service, "" + colSeparator, delimitStrings, mime, shortMime, logFormatReport);
+		this(service, "" + colSeparator, delimitStrings, mime, shortMime);
 	}
 
 	/**
@@ -148,7 +127,7 @@ public class SVFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public SVFormat(final ServiceConnection service, String colSeparator, boolean delimitStrings) throws NullPointerException{
-		this(service, colSeparator, delimitStrings, null, null, true);
+		this(service, colSeparator, delimitStrings, null, null);
 	}
 
 	/**
@@ -165,24 +144,6 @@ public class SVFormat implements OutputFormat {
 	 * @since 2.0
 	 */
 	public SVFormat(final ServiceConnection service, String colSeparator, boolean delimitStrings, final String mime, final String shortMime) throws NullPointerException{
-		this(service, colSeparator, delimitStrings, mime, shortMime, true);
-	}
-
-	/**
-	 * Build a SVFormat.
-	 * 
-	 * @param service			Description of the TAP service.
-	 * @param colSeparator		Column separator to use.
-	 * @param delimitStrings	<i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise.
-	 * @param mime				The MIME type to associate with this format. <i>note: this MIME type is then used by a user to specify the result format he wants.</i>
-	 * @param shortMime			The alias of the MIME type to associate with this format. <i>note: this short MIME type is then used by a user to specify the result format he wants.</i>
-	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 * 
-	 * @since 2.0
-	 */
-	public SVFormat(final ServiceConnection service, String colSeparator, boolean delimitStrings, final String mime, final String shortMime, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL!");
 
@@ -191,7 +152,6 @@ public class SVFormat implements OutputFormat {
 		mimeType = (mime == null || mime.trim().length() <= 0) ? guessMimeType(separator) : mime;
 		shortMimeType = (shortMime == null || shortMime.trim().length() <= 0) ? guessShortMimeType(separator) : shortMime;
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 	}
 
 	/**
@@ -279,28 +239,20 @@ public class SVFormat implements OutputFormat {
 	}
 
 	@Override
-	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
-		try{
-			final long startTime = System.currentTimeMillis();
-
-			// Prepare the output stream:
-			final PrintWriter writer = new PrintWriter(output);
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{
+		// Prepare the output stream:
+		final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
 
-			// Write header:
-			DBColumn[] columns = writeHeader(result, writer, execReport, thread);
+		// Write header:
+		DBColumn[] columns = writeHeader(result, writer, execReport, thread);
 
-			// Write data:
-			int nbRows = writeData(result, columns, writer, execReport, thread);
-
-			writer.flush();
+		if (thread.isInterrupted())
+			throw new InterruptedException();
 
-			// Report stats about the result writing:
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in SV[" + delimitStr + "] ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + "ms!", null);
+		// Write data:
+		writeData(result, columns, writer, execReport, thread);
 
-		}catch(Exception ex){
-			service.getLogger().logTAP(LogLevel.ERROR, execReport, "FORMAT", "Error while formatting in (T/C)SV (delimiter: " + delimitStr + ")!", ex);
-		}
+		writer.flush();
 	}
 
 	/**
@@ -317,7 +269,7 @@ public class SVFormat implements OutputFormat {
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected DBColumn[] writeHeader(TableIterator result, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+	protected DBColumn[] writeHeader(TableIterator result, BufferedWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
 		// Get the columns meta:
 		DBColumn[] selectedColumns = execReport.resultingColumns;
 
@@ -326,13 +278,13 @@ public class SVFormat implements OutputFormat {
 		if (nbColumns > 0){
 			// Write all columns' name:
 			for(int i = 0; i < nbColumns - 1; i++){
-				writer.print(selectedColumns[i].getADQLName());
-				writer.print(separator);
+				writer.write(selectedColumns[i].getADQLName());
+				writer.write(separator);
 			}
-			writer.print(selectedColumns[nbColumns - 1].getADQLName());
+			writer.write(selectedColumns[nbColumns - 1].getADQLName());
 
 			// Go to a new line (in order to prepare the data writing):
-			writer.println();
+			writer.newLine();
 			writer.flush();
 		}
 
@@ -349,18 +301,20 @@ public class SVFormat implements OutputFormat {
 	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
 	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
 	 * 
-	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
-	 * 
 	 * @throws IOException				If there is an error while writing something in the output stream.
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected int writeData(TableIterator result, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
-		int nbRows = 0;
+	protected void writeData(TableIterator result, DBColumn[] selectedColumns, BufferedWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+		execReport.nbRows = 0;
 
 		while(result.nextRow()){
+			// Stop right now the formatting if the job has been aborted/canceled/interrupted:
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+
 			// Deal with OVERFLOW, if needed:
-			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
+			if (execReport.parameters.getMaxRec() > 0 && execReport.nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
 				break;
 
 			int indCol = 0;
@@ -370,20 +324,17 @@ public class SVFormat implements OutputFormat {
 
 				// Append the column separator:
 				if (result.hasNextCol())
-					writer.print(separator);
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
+					writer.write(separator);
 			}
-			writer.println();
-			nbRows++;
-
-			if (thread.isInterrupted())
-				throw new InterruptedException();
+			writer.newLine();
+			
+			execReport.nbRows++;
+			
+			// flush the writer every 30 lines:
+			if (execReport.nbRows % 30 == 0)
+				writer.flush();
 		}
 		writer.flush();
-
-		return nbRows;
 	}
 
 	/**
@@ -401,14 +352,14 @@ public class SVFormat implements OutputFormat {
 	 * @throws IOException		If there is an error while writing the given field value in the given stream.
 	 * @throws TAPException		If there is any other error (by default: never happen).
 	 */
-	protected void writeFieldValue(final Object value, final DBColumn column, final PrintWriter writer) throws IOException, TAPException{
+	protected void writeFieldValue(final Object value, final DBColumn column, final BufferedWriter writer) throws IOException, TAPException{
 		if (value != null){
 			if ((delimitStr && value instanceof String) || value.toString().contains(separator)){
-				writer.print('"');
-				writer.print(value.toString().replaceAll("\"", "'"));
-				writer.print('"');
+				writer.write('"');
+				writer.write(value.toString().replaceAll("\"", "'"));
+				writer.write('"');
 			}else
-				writer.print(value.toString());
+				writer.write(value.toString());
 		}
 	}
 }
diff --git a/src/tap/formatter/TextFormat.java b/src/tap/formatter/TextFormat.java
index ddd864a1366e90b83382593b08833033c817a9a3..9849d1518909488f2a31f063c2dc40af1dc13f43 100644
--- a/src/tap/formatter/TextFormat.java
+++ b/src/tap/formatter/TextFormat.java
@@ -20,14 +20,15 @@ package tap.formatter;
  *                       Astronomisches Rechen Institut (ARI)
  */
 
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
 import tap.data.TableIterator;
-import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 import cds.util.AsciiTable;
 
@@ -36,7 +37,7 @@ import cds.util.AsciiTable;
  * (columns' width are adjusted so that all columns are well aligned and of the same width).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (03/2015)
+ * @version 2.0 (04/2015)
  */
 public class TextFormat implements OutputFormat {
 
@@ -45,9 +46,6 @@ public class TextFormat implements OutputFormat {
 	 * @since 2.0 */
 	protected static final char COL_SEP = '\u25c6';
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
 	protected final ServiceConnection service;
 
@@ -59,23 +57,10 @@ public class TextFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public TextFormat(final ServiceConnection service) throws NullPointerException{
-		this(service, true);
-	}
-
-	/**
-	 * Build a {@link TextFormat}.
-	 * 
-	 * @param service			Description of the TAP service.
-	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public TextFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL!");
 
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 	}
 
 	@Override
@@ -99,41 +84,44 @@ public class TextFormat implements OutputFormat {
 	}
 
 	@Override
-	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
-		try{
-			// Prepare the formatting of the whole output:
-			AsciiTable asciiTable = new AsciiTable(COL_SEP);
-
-			final long startTime = System.currentTimeMillis();
-
-			// Write header:
-			String headerLine = getHeader(result, execReport, thread);
-			asciiTable.addHeaderLine(headerLine);
-			asciiTable.endHeaderLine();
-
-			// Write data into the AsciiTable object:
-			int nbRows = writeData(result, asciiTable, execReport, thread);
-
-			// Finally write the formatted ASCII table (header + data) in the output stream:
-			String[] lines = asciiTable.displayAligned(new int[]{AsciiTable.LEFT}, '|');
-			for(String l : lines){
-				output.write(l.getBytes());
-				output.write('\n');
-			}
-
-			// Add a line in case of an OVERFLOW:
-			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
-				output.write("\nOVERFLOW (more rows were available but have been truncated by the TAP service)".getBytes());
-
-			output.flush();
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{
+		// Prepare the formatting of the whole output:
+		AsciiTable asciiTable = new AsciiTable(COL_SEP);
+
+		// Write header:
+		String headerLine = getHeader(result, execReport, thread);
+		asciiTable.addHeaderLine(headerLine);
+		asciiTable.endHeaderLine();
+
+		if (thread.isInterrupted())
+			throw new InterruptedException();
+
+		// Write data into the AsciiTable object:
+		boolean overflow = writeData(result, asciiTable, execReport, thread);
+
+		// Finally write the formatted ASCII table (header + data) in the output stream:
+		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
+		String[] lines = asciiTable.displayAligned(new int[]{AsciiTable.LEFT}, '|');
+		execReport.nbRows = 0;
+		for(String l : lines){
+			// stop right now the formatting if the job has been aborted/canceled/interrupted:
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+			// write the line:
+			writer.write(l);
+			writer.newLine();
+			// update the counter of written lines:
+			execReport.nbRows++;
+			// flush the writer every 30 lines:
+			if (execReport.nbRows % 30 == 0)
+				writer.flush();
+		}
 
-			// Report stats about the result writing:
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in text ; " + nbRows + " rows ; " + ((execReport != null && execReport.resultingColumns != null) ? "?" : execReport.resultingColumns.length) + " columns) in " + (System.currentTimeMillis() - startTime) + "ms!", null);
+		// Add a line in case of an OVERFLOW:
+		if (overflow)
+			writer.write("\nOVERFLOW (more rows were available but have been truncated by the TAP service)");
 
-		}catch(Exception ex){
-			service.getLogger().logTAP(LogLevel.ERROR, execReport, "FORMAT", "Error while formatting in text/plain!", ex);
-		}
+		writer.flush();
 	}
 
 	/**
@@ -176,14 +164,14 @@ public class TextFormat implements OutputFormat {
 	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
 	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
 	 * 
-	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
+	 * @return	<i>true</i> if an overflow (i.e. nbDBRows > MAXREC) is detected, <i>false</i> otherwise.
 	 * 
-	 * @throws IOException				If there is an error while writing something in the output stream.
 	 * @throws InterruptedException		If the thread has been interrupted.
 	 * @throws TAPException				If any other error occurs.
 	 */
-	protected int writeData(final TableIterator queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException{
-		int nbRows = 0;
+	protected boolean writeData(final TableIterator queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{
+		execReport.nbRows = 0;
+		boolean overflow = false;
 
 		// Get the list of columns:
 		DBColumn[] selectedColumns = execReport.resultingColumns;
@@ -191,9 +179,15 @@ public class TextFormat implements OutputFormat {
 
 		StringBuffer line = new StringBuffer();
 		while(queryResult.nextRow()){
+			// Stop right now the formatting if the job has been aborted/canceled/interrupted:
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+
 			// Deal with OVERFLOW, if needed:
-			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
+			if (execReport.parameters.getMaxRec() > 0 && execReport.nbRows >= execReport.parameters.getMaxRec()){
+				overflow = true;
 				break;
+			}
 
 			// Clear the line buffer:
 			line.delete(0, line.length());
@@ -212,18 +206,18 @@ public class TextFormat implements OutputFormat {
 			// Append the line/row in the ASCII table:
 			asciiTable.addLine(line.toString());
 
-			nbRows++;
+			execReport.nbRows++;
 		}
 
-		return nbRows;
+		return overflow;
 	}
 
 	/**
 	 * Writes the given field value in the given buffer.
 	 * 
-	 * @param value				The value to write.
-	 * @param tapCol			The corresponding column metadata.
-	 * @param line				The buffer in which the field value must be written.
+	 * @param value		The value to write.
+	 * @param tapCol	The corresponding column metadata.
+	 * @param line		The buffer in which the field value must be written.
 	 */
 	protected void writeFieldValue(final Object value, final DBColumn tapCol, final StringBuffer line){
 		if (value != null){
diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java
index 2e166d6ea8107f57cb68569b615c1fa9e6e6ff0a..994c36713e83b7b59fe43f551b1e3edae732d195 100644
--- a/src/tap/formatter/VOTableFormat.java
+++ b/src/tap/formatter/VOTableFormat.java
@@ -46,7 +46,6 @@ import uk.ac.starlink.table.StarTable;
 import uk.ac.starlink.votable.DataFormat;
 import uk.ac.starlink.votable.VOSerializer;
 import uk.ac.starlink.votable.VOTableVersion;
-import uws.service.log.UWSLog.LogLevel;
 import adql.db.DBColumn;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
@@ -84,13 +83,10 @@ import adql.db.DBType.DBDatatype;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  */
 public class VOTableFormat implements OutputFormat {
 
-	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
-	private boolean logFormatReport;
-
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
 	protected final ServiceConnection service;
 
@@ -119,7 +115,7 @@ public class VOTableFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service) throws NullPointerException{
-		this(service, true);
+		this(service, null, null);
 	}
 
 	/**
@@ -141,7 +137,7 @@ public class VOTableFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException{
-		this(service, votFormat, null, true);
+		this(service, votFormat, null);
 	}
 
 	/**
@@ -164,52 +160,10 @@ public class VOTableFormat implements OutputFormat {
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException{
-		this(service, votFormat, votVersion, true);
-	}
-
-	/**
-	 * <p>Creates a VOTable formatter.</p>
-	 * 
-	 * <p><i>Note:
-	 * 	The MIME type is automatically set to "application/x-votable+xml" = "votable".
-	 * 	It is however possible to change this default value thanks to {@link #setMimeType(String, String)}.
-	 * </i></p>
-	 * 
-	 * @param service				The service to use (for the log and to have some information about the service (particularly: name, description).
-	 * @param logFormatReport		<code>true</code> to append a format report (start and end date/time) in the log output, <code>false</code> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public VOTableFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
-		this(service, null, null, logFormatReport);
-	}
-
-	/**
-	 * <p>Creates a VOTable formatter.</p>
-	 * 
-	 * <i>Note: The MIME type is automatically set in function of the given VOTable serialization:</i>
-	 * <ul>
-	 * 	<li><i><b>none or unknown</b>: equivalent to BINARY</i></li>
-	 * 	<li><i><b>BINARY</b>:          "application/x-votable+xml" = "votable"</i></li>
-	 * 	<li><i><b>BINARY2</b>:         "application/x-votable+xml;serialization=BINARY2" = "votable/b2"</i></li>
-	 * 	<li><i><b>TABLEDATA</b>:       "application/x-votable+xml;serialization=TABLEDATA" = "votable/td"</i></li>
-	 * 	<li><i><b>FITS</b>:            "application/x-votable+xml;serialization=FITS" = "votable/fits"</i></li>
-	 * </ul>
-	 * <p><i>It is however possible to change these default values thanks to {@link #setMimeType(String, String)}.</i></p> 
-	 * 
-	 * @param service				The service to use (for the log and to have some information about the service (particularly: name, description).
-	 * @param votFormat				Serialization of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS).
-	 * @param votVersion			Version of the resulting VOTable.
-	 * @param logFormatReport		<code>true</code> to append a format report (start and end date/time) in the log output, <code>false</code> otherwise.
-	 * 
-	 * @throws NullPointerException	If the given service connection is <code>null</code>.
-	 */
-	public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL!");
 
 		this.service = service;
-		this.logFormatReport = logFormatReport;
 
 		// Set the VOTable serialization and version:
 		this.votFormat = (votFormat == null) ? DataFormat.BINARY : votFormat;
@@ -372,48 +326,40 @@ public class VOTableFormat implements OutputFormat {
 	}
 
 	@Override
-	public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{
-		try{
-			long start = System.currentTimeMillis();
+	public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, IOException, InterruptedException{
+		ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread);
 
-			ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread);
+		/* Turns the result set into a table. */
+		LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec());
 
-			/* Turns the result set into a table. */
-			LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec());
+		/* Prepares the object that will do the serialization work. */
+		VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
+		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output));
 
-			/* Prepares the object that will do the serialization work. */
-			VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
-			BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output));
+		/* Write header. */
+		writeHeader(votVersion, execReport, out);
 
-			/* Write header. */
-			writeHeader(votVersion, execReport, out);
+		if (thread.isInterrupted())
+			throw new InterruptedException();
 
-			/* Write table element. */
-			voser.writeInlineTableElement(out);
-			out.flush();
-
-			/* Check for overflow and write INFO if required. */
-			if (table.lastSequenceOverflowed()){
-				out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>");
-				out.newLine();
-			}
+		/* Write table element. */
+		voser.writeInlineTableElement(out);
+		execReport.nbRows = table.getNbReadRows();
+		out.flush();
 
-			/* Write footer. */
-			out.write("</RESOURCE>");
-			out.newLine();
-			out.write("</VOTABLE>");
+		/* Check for overflow and write INFO if required. */
+		if (table.lastSequenceOverflowed()){
+			out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>");
 			out.newLine();
+		}
 
-			out.flush();
+		/* Write footer. */
+		out.write("</RESOURCE>");
+		out.newLine();
+		out.write("</VOTABLE>");
+		out.newLine();
 
-			if (logFormatReport)
-				service.getLogger().logTAP(LogLevel.INFO, execReport, "FORMAT", "Result formatted (in VOTable ; " + table.getNbReadRows() + " rows ; " + table.getColumnCount() + " columns) in " + (System.currentTimeMillis() - start) + "ms!", null);
-		}catch(IOException ioe){
-			if (ioe.getCause() != null && ioe.getCause() instanceof DataReadException)
-				throw (DataReadException)ioe.getCause();
-			else
-				throw new TAPException("Error while writing a query result in VOTable!", ioe);
-		}
+		out.flush();
 	}
 
 	/**
@@ -460,7 +406,7 @@ public class VOTableFormat implements OutputFormat {
 		 * 	2/ a GROUP item with the STC expression of the coordinate system. 
 		 */
 
-		//out.flush();  // TODO DEBUG flush() => commit
+		out.flush();
 	}
 
 	/**
@@ -502,9 +448,6 @@ public class VOTableFormat implements OutputFormat {
 				colInfos[indField] = getColumnInfo(tapCol);
 
 				indField++;
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
 			}
 
 			return colInfos;
diff --git a/src/tap/log/DefaultTAPLog.java b/src/tap/log/DefaultTAPLog.java
index 789bdf3032f47333f4feb0f3a5c7bc327f4ec36e..8374bed467ee155b3715f48c8509d12d035b56aa 100644
--- a/src/tap/log/DefaultTAPLog.java
+++ b/src/tap/log/DefaultTAPLog.java
@@ -37,7 +37,7 @@ import uws.service.log.DefaultUWSLog;
  * Default implementation of the {@link TAPLog} interface which lets logging any message about a TAP service.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  * 
  * @see DefaultUWSLog
  */
@@ -127,7 +127,7 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 			return;
 
 		// log the main given error:
-		log(level, "DB", event, (connection != null ? connection.getID() : null), message, error);
+		log(level, "DB", event, (connection != null ? connection.getID() : null), message, null, error);
 
 		/* Some SQL exceptions (like BatchUpdateException) have a next exception which provides more information.
 		 * Here, the stack trace of the next exception is also logged:
@@ -155,23 +155,23 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 		try{
 			if (event != null && obj != null){
 				if (event.equals("SYNC_INIT"))
-					msgAppend = "QUERY=" + ((TAPParameters)obj).getQuery().replaceAll("(\t|\r?\n)+", " ");
-				else if (obj instanceof TAPSyncJob)
-					jobId = ((TAPSyncJob)obj).getID();
-				else if (obj instanceof TAPExecutionReport){
+					msgAppend = "QUERY=" + ((TAPParameters)obj).getQuery();
+				else if (obj instanceof TAPSyncJob){
+					log(level, "JOB", event, ((TAPSyncJob)obj).getID(), message, null, error);
+					return;
+				}else if (obj instanceof TAPExecutionReport){
 					TAPExecutionReport report = (TAPExecutionReport)obj;
 					jobId = report.jobID;
 					msgAppend = (report.synchronous ? "SYNC" : "ASYNC") + ",duration=" + report.getTotalDuration() + "ms (upload=" + report.getUploadDuration() + ",parse=" + report.getParsingDuration() + ",exec=" + report.getExecutionDuration() + ",format[" + report.parameters.getFormat() + "]=" + report.getFormattingDuration() + ")";
-				}
+				}else if (event.equalsIgnoreCase("WRITING_ERROR"))
+					jobId = obj.toString();
 			}
-			if (msgAppend != null)
-				msgAppend = "\t" + msgAppend;
 		}catch(Throwable t){
 			error("Error while preparing a log message in logTAP(...)! The message will be logger but without additional information such as the job ID.", t);
 		}
 
 		// Log the message:
-		log(level, "TAP", event, jobId, message + (msgAppend != null ? msgAppend : ""), error);
+		log(level, "TAP", event, jobId, message, msgAppend, error);
 	}
 
 }
diff --git a/src/tap/log/TAPLog.java b/src/tap/log/TAPLog.java
index 777ec1e3e56ba8423d279d65b5980e1e70f4ec5e..288eac73ad82bbc1b2e2283dea5592d1e4a61039 100644
--- a/src/tap/log/TAPLog.java
+++ b/src/tap/log/TAPLog.java
@@ -30,7 +30,7 @@ import uws.service.log.UWSLog;
  * Let log any kind of message about a TAP service.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  */
 public interface TAPLog extends UWSLog {
 
@@ -78,18 +78,21 @@ public interface TAPLog extends UWSLog {
 	 * 
 	 * <p>List of all events sent by the library (case sensitive):</p>
 	 * <ul>
+	 * 	<li>IDENT_USER (with a NULL "obj")</li>
 	 * 	<li>SYNC_INIT (with "obj" as an instance of {@link TAPParameters})</li>
 	 * 	<li>ASYNC_INIT (with a NULL "obj")</li>
-	 * 	<li>SYNC_START (with "obj" as an instance of {@link TAPSyncJob})</li>
+	 * 	<li>START (with "obj" as an instance of {@link TAPSyncJob})</li>
 	 * 	<li>UPLOADING (with "obj" as an instance of {@link TAPExecutionReport})</li>
 	 * 	<li>PARSING (with "obj" as an instance of {@link TAPExecutionReport})</li>
-	 * 	<li>EXECUTING (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>START_DB_EXECUTION (with "obj" as an instance of {@link TAPExecutionReport})</li>
 	 * 	<li>WRITING_RESULT (with "obj" as an instance of {@link TAPExecutionReport})</li>
-	 * 	<li>FORMAT (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>RESULT_WRITTEN (with "obj" as an instance of {@link TAPExecutionReport})</li>
 	 * 	<li>START_STEP (with "obj" as an instance of {@link TAPExecutionReport})</li>
 	 * 	<li>END_EXEC (with "obj" as an instance of {@link TAPExecutionReport})</li>
-	 * 	<li>END_QUERY (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>END_DB_EXECUTION (with "obj" as an instance of {@link TAPExecutionReport})</li>
 	 * 	<li>DROP_UPLOAD (with "obj" as an instance of {@link TAPExecutionReport})</li>
+	 * 	<li>TIME_OUT (with "obj" as an instance of {@link TAPSyncJob})</li>
+	 * 	<li>END (with "obj" as an instance of {@link TAPSyncJob})</li>
 	 * </ul>
 	 * 
 	 * @param level		Level of the log (info, warning, error, ...). <i>SHOULD NOT be NULL, but if NULL anyway, the level SHOULD be considered as INFO</i>
diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java
index 2d4ec39401a8daa8597fc43f45dbb596b6e36260..77dd7320c6e4d12896f9ff1ea1b2c9d7a3bba15f 100644
--- a/src/tap/metadata/TAPMetadata.java
+++ b/src/tap/metadata/TAPMetadata.java
@@ -16,7 +16,7 @@ package tap.metadata;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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)
  */
 
@@ -38,6 +38,8 @@ import tap.resource.Capabilities;
 import tap.resource.TAPResource;
 import tap.resource.VOSIResource;
 import uk.ac.starlink.votable.VOSerializer;
+import uws.ClientAbortException;
+import uws.UWSToolBox;
 import adql.db.DBTable;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
@@ -62,7 +64,7 @@ import adql.db.DBType.DBDatatype;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (03/2015)
  */
 public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource {
 
@@ -483,7 +485,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 
 		writer.println("</vosi:tableset>");
 
-		writer.flush();
+		UWSToolBox.flush(writer);
 	}
 
 	/**
@@ -507,7 +509,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * @param s			The schema to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given schema must be written.
 	 * 
-	 * @throws IOException	If there is any error while writing the XML in the given writer.
+	 * @throws IOException	If the connection with the HTTP client has been either canceled or closed for another reason.
 	 * 
 	 * @see #writeTable(TAPTable, PrintWriter)
 	 */
@@ -520,10 +522,28 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 		writeAtt(prefix, "description", s.getDescription(), true, writer);
 		writeAtt(prefix, "utype", s.getUtype(), true, writer);
 
-		for(TAPTable t : s)
-			writeTable(t, writer);
+		int nbColumns = 0;
+		for(TAPTable t : s){
+
+			// write each table:
+			nbColumns += writeTable(t, writer);
+
+			// flush the PrintWriter buffer when at least 30 tables have been read:
+			/* Note: the buffer may have already been flushed before automatically,
+			 *       but this manual flush is also checking whether any error has occurred while writing the previous characters.
+			 *       If so, a ClientAbortException (extension of IOException) is thrown in order to interrupt the writing of the
+			 *       metadata and thus, in order to spare server resources (and particularly memory if the metadata set is large). */
+			if (nbColumns / 30 > 1){
+				UWSToolBox.flush(writer);
+				nbColumns = 0;
+			}
+
+		}
 
 		writer.println("\t</schema>");
+
+		if (nbColumns > 0)
+			UWSToolBox.flush(writer);
 	}
 
 	/**
@@ -541,16 +561,22 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * &lt;/table&gt;
 	 * </pre>
 	 * 
-	 * <p><i>Note:
+	 * <p><i>Note 1:
 	 * 	When NULL an attribute or a field is not written. Here this rule concerns: description and utype.
 	 * </i></p>
 	 * 
+	 * <p><i>Note 2:
+	 * 	The PrintWriter buffer is flushed all the 10 columns. At that moment the writer is checked for errors.
+	 * 	If the error flag is set, a {@link ClientAbortException} is thrown in order to stop the metadata writing.
+	 * 	This is particularly useful if the metadata data is pretty large.
+	 * </i></p>
+	 * 
 	 * @param t			The table to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given table must be written.
 	 * 
-	 * @throws IOException	If there is any error while writing the XML in the given writer.
+	 * @return	The total number of written columns.
 	 */
-	private void writeTable(TAPTable t, PrintWriter writer) throws IOException{
+	private int writeTable(TAPTable t, PrintWriter writer){
 		final String prefix = "\t\t\t";
 
 		writer.print("\t\t<table");
@@ -568,15 +594,20 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 		writeAtt(prefix, "description", t.getDescription(), true, writer);
 		writeAtt(prefix, "utype", t.getUtype(), true, writer);
 
+		int nbCol = 0;
 		Iterator<TAPColumn> itCols = t.getColumns();
-		while(itCols.hasNext())
+		while(itCols.hasNext()){
 			writeColumn(itCols.next(), writer);
+			nbCol++;
+		}
 
 		Iterator<TAPForeignKey> itFK = t.getForeignKeys();
 		while(itFK.hasNext())
 			writeForeignKey(itFK.next(), writer);
 
 		writer.println("\t\t</table>");
+
+		return nbCol;
 	}
 
 	/**
@@ -602,10 +633,8 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * 
 	 * @param c			The column to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given column must be written.
-	 * 
-	 * @throws IOException	If there is any error while writing the XML in the given writer.
 	 */
-	private void writeColumn(TAPColumn c, PrintWriter writer) throws IOException{
+	private void writeColumn(TAPColumn c, PrintWriter writer){
 		final String prefix = "\t\t\t\t";
 
 		writer.print("\t\t\t<column");
@@ -665,10 +694,8 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * 
 	 * @param fk		The foreign key to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given foreign key must be written.
-	 * 
-	 * @throws IOException	If there is any error while writing the XML in the given writer.
 	 */
-	private void writeForeignKey(TAPForeignKey fk, PrintWriter writer) throws IOException{
+	private void writeForeignKey(TAPForeignKey fk, PrintWriter writer){
 		final String prefix = "\t\t\t\t";
 
 		writer.println("\t\t\t<foreignKey>");
@@ -699,10 +726,8 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * @param isOptionalAttr	<i>true</i> if the attribute to write is optional (in this case, if the value is NULL or an empty string, the whole attribute item won't be written), 
 	 *                      	<i>false</i> otherwise (here, if the value is NULL or an empty string, the XML item will be written with an empty string as value). 
 	 * @param writer			Output in which the XML node must be written.
-	 * 
-	 * @throws IOException	If there is a problem while writing the XML node inside the given writer.
 	 */
-	private void writeAtt(String prefix, String attributeName, String attributeValue, boolean isOptionalAttr, PrintWriter writer) throws IOException{
+	private void writeAtt(String prefix, String attributeName, String attributeValue, boolean isOptionalAttr, PrintWriter writer){
 		if (attributeValue != null && attributeValue.trim().length() > 0){
 			StringBuffer xml = new StringBuffer(prefix);
 			xml.append('<').append(attributeName).append('>').append(VOSerializer.formatText(attributeValue)).append("</").append(attributeName).append('>');
diff --git a/src/tap/parameters/TAPParameters.java b/src/tap/parameters/TAPParameters.java
index 8ab7466093020564add6f92b1bb2cc146bcfb717..2f295785e29e8926bbca624b2a5c9de7e8f5cdb3 100644
--- a/src/tap/parameters/TAPParameters.java
+++ b/src/tap/parameters/TAPParameters.java
@@ -268,13 +268,13 @@ public class TAPParameters extends UWSParameters {
 		// Check that required parameters are not NON-NULL:
 		String requestParam = getRequest();
 		if (requestParam == null)
-			throw new TAPException("The parameter \"" + TAPJob.PARAM_REQUEST + "\" must be provided and its value must be equal to \"" + TAPJob.REQUEST_DO_QUERY + "\" or \"" + TAPJob.REQUEST_GET_CAPABILITIES + "\" !", UWSException.BAD_REQUEST);
+			throw new TAPException("The parameter \"" + TAPJob.PARAM_REQUEST + "\" must be provided and its value must be equal to \"" + TAPJob.REQUEST_DO_QUERY + "\" or \"" + TAPJob.REQUEST_GET_CAPABILITIES + "\"!", UWSException.BAD_REQUEST);
 
 		if (requestParam.equals(TAPJob.REQUEST_DO_QUERY)){
 			if (get(TAPJob.PARAM_LANGUAGE) == null)
-				throw new TAPException("The parameter \"" + TAPJob.PARAM_LANGUAGE + "\" must be provided if " + TAPJob.PARAM_REQUEST + "=" + TAPJob.REQUEST_DO_QUERY + " !", UWSException.BAD_REQUEST);
+				throw new TAPException("The parameter \"" + TAPJob.PARAM_LANGUAGE + "\" must be provided if " + TAPJob.PARAM_REQUEST + "=" + TAPJob.REQUEST_DO_QUERY + "!", UWSException.BAD_REQUEST);
 			else if (get(TAPJob.PARAM_QUERY) == null)
-				throw new TAPException("The parameter \"" + TAPJob.PARAM_QUERY + "\" must be provided if " + TAPJob.PARAM_REQUEST + "=" + TAPJob.REQUEST_DO_QUERY + " !", UWSException.BAD_REQUEST);
+				throw new TAPException("The parameter \"" + TAPJob.PARAM_QUERY + "\" must be provided if " + TAPJob.PARAM_REQUEST + "=" + TAPJob.REQUEST_DO_QUERY + "!", UWSException.BAD_REQUEST);
 		}
 	}
 }
diff --git a/src/tap/resource/ASync.java b/src/tap/resource/ASync.java
index 8cc6784515ebc58447f96eaf4b63ebe5162eff88..7aef6e2dd72462ce1249c9c10f4bdc90825998fd 100644
--- a/src/tap/resource/ASync.java
+++ b/src/tap/resource/ASync.java
@@ -16,7 +16,7 @@ package tap.resource;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  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)
  */
 
@@ -71,7 +71,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </ul>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  * 
  * @see UWSService
  */
@@ -191,6 +191,7 @@ public class ASync implements TAPResource {
 			return uws.executeRequest(request, response);
 
 		}catch(UWSException ue){
+			service.getLogger().logTAP(LogLevel.FATAL, null, null, "Error while executing the /async resource.", ue);
 			throw new TAPException(ue);
 		}
 	}
diff --git a/src/tap/resource/HomePage.java b/src/tap/resource/HomePage.java
index 3de9588ec04fc7c90ae2831d19beeb2ba243091e..280ab99b0132c8a1328fb4ad1ecf80fca8c07ffd 100644
--- a/src/tap/resource/HomePage.java
+++ b/src/tap/resource/HomePage.java
@@ -19,12 +19,14 @@ package tap.resource;
  * Copyright 2015 - Astronomisches Rechen Institut (ARI)
  */
 
-import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URISyntaxException;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
@@ -32,6 +34,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import tap.TAPException;
+import uws.ClientAbortException;
+import uws.UWSToolBox;
 import uws.service.log.UWSLog.LogLevel;
 
 /**
@@ -58,7 +62,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </ol>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  * @since 2.0
  */
 public class HomePage implements TAPResource {
@@ -91,7 +95,6 @@ public class HomePage implements TAPResource {
 
 	@Override
 	public boolean executeResource(final HttpServletRequest request, final HttpServletResponse response) throws IOException, TAPException{
-		PrintWriter writer = response.getWriter();
 		boolean written = false;
 
 		// Display the specified home page, if any is specified:
@@ -102,19 +105,29 @@ public class HomePage implements TAPResource {
 				uri = new URI(tap.homePageURI);
 				/* CASE: FILE IN WebContent */
 				if (uri.getScheme() == null){
-					if (request.getServletContext().getResource(tap.homePageURI) != null){
-						request.getRequestDispatcher(tap.homePageURI).forward(request, response);
-						written = true;
-					}else
-						tap.getLogger().logTAP(LogLevel.ERROR, null, "HOME_PAGE", "Can not write the specified home page content (" + tap.homePageURI + "): Web application file not found!", null);
+					try{
+						if (request.getServletContext().getResource(tap.homePageURI) != null){
+							request.getRequestDispatcher(tap.homePageURI).forward(request, response);
+							written = true;
+						}else
+							logError("Web application file not found", null);
+					}catch(MalformedURLException mue){
+						logError("incorrect URL syntax", mue);
+					}
 				}
 				/* CASE: LOCAL FILE */
 				else if (uri.getScheme().equalsIgnoreCase("file")){
 					// Set the content type:
 					response.setContentType(tap.homePageMimeType);
 
+					// set character encoding:
+					response.setCharacterEncoding("UTF-8");
+
+					// Get the character writer:
+					PrintWriter writer = response.getWriter();
+
 					// Get an input toward the custom home page:
-					BufferedInputStream input = null;
+					BufferedReader input = null;
 					try{
 						File f = new File(uri.getPath());
 						if (f.exists() && !f.isDirectory() && f.canRead()){
@@ -122,23 +135,50 @@ public class HomePage implements TAPResource {
 							response.setContentLength((int)f.length());
 
 							// get the input stream:
-							input = new BufferedInputStream(new FileInputStream(f));
+							input = new BufferedReader(new FileReader(f));
 
 							// Copy the content of the input into the given writer:
-							byte[] buffer = new byte[2048];
-							int nbReads = 0;
-							while((nbReads = input.read(buffer)) > 0)
-								writer.print(new String(buffer, 0, nbReads));
+							char[] buffer = new char[2048];
+							int nbReads = 0, nbBufferWritten = 0;
+							while((nbReads = input.read(buffer)) > 0){
+								writer.write(buffer, 0, nbReads);
+								if ((++nbBufferWritten) % 4 == 0){ // the minimum and default buffer size of an HttpServletResponse is 8kiB => 4*2048
+									UWSToolBox.flush(writer);
+									nbBufferWritten = 0;
+								}
+							}
+							UWSToolBox.flush(writer);
 
 							// copy successful:
 							written = true;
 						}else
-							tap.getLogger().logTAP(LogLevel.ERROR, null, "HOME_PAGE", "Can not write the specified home page content (" + tap.homePageURI + "): File not found or not readable (" + f.exists() + !f.isDirectory() + f.canRead() + ")!", null);
+							logError("file not found or not readable (" + f.exists() + !f.isDirectory() + f.canRead() + ")", null);
+
+					}catch(ClientAbortException cae){
+						/*   This exception is an extension of IOException thrown only by some functions of UWSToolBox.
+						 * It aims to notify about an IO error while trying to write the content of an HttpServletResponse.
+						 * Such exception just means that the connection with the HTTP client has been closed/aborted.
+						 * Consequently, no error nor result can be written any more in the HTTP response.
+						 *   This error, is just propagated to the TAP instance, so that stopping any current process
+						 * for this request and so that being logged without any attempt of writing the error in the HTTP response.
+						 */
+						throw cae;
+
+					}catch(IOException ioe){
+						/*   This IOException can be thrown only by InputStream.read(...) (because PrintWriter.print(...)
+						 * silently fallbacks in case of error).
+						 *   So this error must not be propagated but caught and logged right now. Thus the default home page
+						 * can be displayed after the error has been logged. */
+						logError("the following error occurred while reading the specified local file", ioe);
 
 					}finally{
 						if (input != null)
 							input.close();
 					}
+
+					// Stop trying to write the home page if the HTTP request has been aborted/closed:
+					/*if (requestAborted)
+						throw new IOException("HTTP request aborted or connection with the HTTP client closed for another reason!");*/
 				}
 				/* CASE: HTTP/HTTPS/FTP/... */
 				else{
@@ -146,13 +186,33 @@ public class HomePage implements TAPResource {
 					written = true;
 				}
 
+			}catch(IOException ioe){
+				/*   This IOException can be caught here only if caused by a HTTP client abortion or by a closing of the HTTPrequest.
+				 * So, it must be propagated until the TAP instance, where it will be merely logged as INFO. No response/error can be 
+				 * returned in the HTTP response. */
+				throw ioe;
+
+			}catch(IllegalStateException ise){
+				/*   This exception is caused by an attempt to reset the HTTP response buffer while a part of its
+				 * content has already been submitted to the HTTP client.
+				 *   It must be propagated to the TAP instance so that being logged as a FATAL error. */
+				throw ise;
+
 			}catch(Exception e){
-				tap.getLogger().logTAP(LogLevel.ERROR, null, "HOME_PAGE", "Can not write the specified home page content (" + tap.homePageURI + "): " + e.getMessage(), e);
+				/*   The other errors are just logged, but not reported to the HTTP client,
+				 * and then the default home page is displayed. */
+				if (e instanceof URISyntaxException)
+					logError("the given URI has a wrong and unexpected syntax", e);
+				else
+					logError(null, e);
 			}
 		}
 
 		// DEFAULT: list all available resources:
 		if (!written){
+			// Get the output stream:
+			PrintWriter writer = response.getWriter();
+
 			// Set the content type: HTML document
 			response.setContentType("text/html");
 
@@ -162,10 +222,32 @@ public class HomePage implements TAPResource {
 				writer.println("<li><a href=\"" + tap.tapBaseURL + "/" + res.getName() + "\">" + res.getName() + "</a></li>");
 			writer.println("</ul></body></html>");
 
+			writer.flush();
+
 			written = true;
 		}
 
 		return written;
 	}
 
+	/**
+	 * <p>Log the given error as a TAP log message with the {@link LogLevel} ERROR, and the event "HOME_PAGE".</p>
+	 * 
+	 * <p>
+	 * 	The logged message starts with: <code>Can not write the specified home page content ({tap.homePageURI})</code>.
+	 * 	After the specified error message, the following is appended: <code>! => The default home page will be displayed.</code>.
+	 * </p>
+	 * 
+	 * <p>
+	 * 	If the message parameter is missing, the {@link Throwable} message will be taken instead.
+	 * 	And if this latter is also missing, none will be written.
+	 * </p>
+	 *
+	 * @param message	Error message to log.
+	 * @param error		The exception at the origin of the error.
+	 */
+	protected void logError(final String message, final Throwable error){
+		tap.getLogger().logTAP(LogLevel.ERROR, null, "HOME_PAGE", "Can not write the specified home page content (" + tap.homePageURI + ") " + (message == null ? (error == null ? "" : ": " + error.getMessage()) : ": " + message) + "! => The default home page will be displayed.", error);
+	}
+
 }
diff --git a/src/tap/resource/Sync.java b/src/tap/resource/Sync.java
index 72550b1e1302f349dce2dcc660d75a1a484627e0..9b93392fe39ed677bb292b9c2df473a0b425d5db 100644
--- a/src/tap/resource/Sync.java
+++ b/src/tap/resource/Sync.java
@@ -33,7 +33,6 @@ import tap.TAPJob;
 import tap.TAPSyncJob;
 import tap.parameters.TAPParameters;
 import uws.UWSException;
-import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>Synchronous resource of a TAP service.</p>
@@ -107,16 +106,10 @@ public class Sync implements TAPResource {
 			throw new TAPException("Can not execute a query: this TAP service is not available! " + service.getAvailability(), UWSException.SERVICE_UNAVAILABLE);
 
 		// Execute synchronously the given job:
-		try{
-			TAPSyncJob syncJob = new TAPSyncJob(service, params);
-			syncJob.start(response);
-			return true;
-		}catch(TAPException te){
-			throw te;
-		}catch(Exception t){
-			service.getLogger().logTAP(LogLevel.FATAL, params, "SYNC_INIT", "Unexpected error while executing the given ADQL query!", t);
-			throw new TAPException("Unexpected error while executing the given ADQL query!");
-		}
+		TAPSyncJob syncJob = new TAPSyncJob(service, params);
+		syncJob.start(response);
+
+		return true;
 
 	}
 
diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java
index 775f705659a5401bda357e357af13536f915b315..57f15187e5e663bb4a3a9110a9238b7461fd08e6 100644
--- a/src/tap/resource/TAP.java
+++ b/src/tap/resource/TAP.java
@@ -40,7 +40,6 @@ import tap.metadata.TAPMetadata;
 import uk.ac.starlink.votable.VOSerializer;
 import uws.UWSException;
 import uws.UWSToolBox;
-import uws.job.ErrorType;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
 import uws.service.UWSService;
@@ -54,7 +53,7 @@ import adql.db.FunctionDef;
  * <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (04/2015)
  */
 public class TAP implements VOSIResource {
 
@@ -796,7 +795,7 @@ public class TAP implements VOSIResource {
 			try{
 				request.setAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS, getUWS().getRequestParser().parse(request));
 			}catch(UWSException ue){
-				getLogger().log(LogLevel.ERROR, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue);
+				getLogger().log(LogLevel.WARNING, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue);
 			}
 		}
 
@@ -822,6 +821,7 @@ public class TAP implements VOSIResource {
 			try{
 				user = UWSToolBox.getUser(request, service.getUserIdentifier());
 			}catch(UWSException ue){
+				getLogger().logTAP(LogLevel.ERROR, null, "IDENT_USER", "Can not identify the HTTP request user!", ue);
 				throw new TAPException(ue);
 			}
 
@@ -846,23 +846,56 @@ public class TAP implements VOSIResource {
 
 			// Log the successful execution of the action, only if the asked resource is not UWS (because UWS is already logging the received request):
 			if (!resourceName.equalsIgnoreCase(ASync.RESOURCE_NAME))
-				getLogger().logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + resourceName + "\" successfully executed.", null);
+				getLogger().logHttp(LogLevel.INFO, response, reqID, user, "Action \"" + resourceName + "\" successfully executed.", null);
+
+		}catch(IOException ioe){
+			/*
+			 *   Any IOException thrown while writing the HTTP response is generally caused by a client abortion (intentional or timeout)
+			 * or by a connection closed with the client for another reason.
+			 *   Consequently, a such error should not be considered as a real error from the server or the library: the request is
+			 * canceled, and so the response is not expected. It is anyway not possible any more to send it (header and/or body) totally
+			 * or partially.
+			 *   Nothing can solve this error. So the "error" is just reported as a simple information and theoretically the action
+			 * executed when this error has been thrown is already stopped.
+			 */
+			getLogger().logHttp(LogLevel.INFO, response, reqID, user, "HTTP request aborted or connection with the client closed => the TAP resource \"" + resourceName + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", null);
+
+		}catch(TAPException te){
+			/*
+			 *   Any known/"expected" TAP exception is logged but also returned to the HTTP client in an XML error document.
+			 *   Since the error is known, it is supposed to have already been logged with a full stack trace. Thus, there
+			 * is no need to log again its stack trace...just its message is logged. 
+			 */
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(te, response, request, reqID, user, resourceName);
+			// Log the error:
+			getLogger().logHttp(LogLevel.ERROR, response, reqID, user, "TAP resource \"" + resourceName + "\" execution FAILED with the error: \"" + te.getMessage() + "\"!", null);
+
+		}catch(IllegalStateException ise){
+			/*
+			 *   Any IllegalStateException that reaches this point, is supposed coming from a HttpServletResponse operation which
+			 * has to reset the response buffer (e.g. resetBuffer(), sendRedirect(), sendError()).
+			 *   If this exception happens, the library tried to rewrite the HTTP response body with a message or a result,
+			 * while this body has already been partially sent to the client. It is then no longer possible to change its content.
+			 *   Consequently, the error is logged as FATAL and a message will be appended at the end of the already submitted response
+			 * to alert the HTTP client that an error occurs and the response should not be considered as complete and reliable. 
+			 */
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(ise, response, request, reqID, user, resourceName);
+			// Log the error:
+			getLogger().logHttp(LogLevel.FATAL, response, reqID, user, "HTTP response already partially committed => the TAP resource \"" + resourceName + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", (ise.getCause() != null) ? ise.getCause() : ise);
 
 		}catch(Throwable t){
-			// CLIENT ABORTION: (note: should work with Apache/Tomcat and JBoss)
-			if (t.getClass().getName().endsWith("ClientAbortException")){
-				// Log the client abortion:
-				getLogger().logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + response.getStatus() + " - HTTP request aborted by the client => the TAP resource \"" + resourceName + "\" has stopped!", t);
-				// Notify the client abortion in a TAP error:
-				errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, response, request, reqID, user, resourceName);
-			}
-			// ANY OTHER ERROR:
-			else{
-				// Write the error in the response and return the appropriate HTTP status code:
-				errorWriter.writeError(t, response, request, reqID, user, resourceName);
-				// Log the error:
-				getLogger().logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\"!", t);
-			}
+			/*
+			 *   Any other error is considered as unexpected if it reaches this point. Consequently, it has not yet been logged.
+			 * So its stack trace will be fully logged, and an appropriate message will be returned to the HTTP client. The
+			 * returned XML document should contain not too technical information which would be useless for the user.
+			 */
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(t, response, request, reqID, user, resourceName);
+			// Log the error:
+			getLogger().logHttp(LogLevel.FATAL, response, reqID, user, "TAP resource \"" + resourceName + "\" execution FAILED with a GRAVE error!", t);
+
 		}finally{
 			// Notify the queue of the asynchronous jobs that a new connection may be available:
 			if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME))
diff --git a/src/uws/ClientAbortException.java b/src/uws/ClientAbortException.java
new file mode 100644
index 0000000000000000000000000000000000000000..df8a9cdaea7382fb01fc8473943abe23e0fe037b
--- /dev/null
+++ b/src/uws/ClientAbortException.java
@@ -0,0 +1,56 @@
+package uws;
+
+/*
+ * This file is part of UWSLibrary.
+ * 
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2015 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.io.IOException;
+
+/**
+ * <p>Exception which occurs when the connection between the HTTP client and a servlet has been unexpectedly closed.</p>
+ * 
+ * <p>
+ * 	In such situation Tomcat and JBoss throw a class extending {@link IOException} and also named ClientAbortException.
+ * 	Jetty just throw a simple {@link IOException} with an appropriate message. And so, other servlet
+ * 	containers may throw a similar exception when a client-server connection is closed. This implementation
+ * 	of ClientAbortException provided by the library aims to signal this error in a unified way, with a single
+ * 	{@link IOException}, whatever is the underlying servlet container.
+ * </p>
+ * 
+ * <p><i>Note:
+ * 	Instead of this exception any IOException thrown by an {@link java.io.OutputStream} or a {@link java.io.PrintWriter}
+ * 	which has been provided by an {@link javax.servlet.http.HttpServletResponse} should be considered as an abortion of
+ * 	the HTTP client.
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 4.1 (04/2015)
+ * @since 4.1
+ */
+public class ClientAbortException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	public ClientAbortException(){
+		super();
+	}
+
+	public ClientAbortException(final IOException ioe){
+		super(ioe);
+	}
+
+}
diff --git a/src/uws/UWSToolBox.java b/src/uws/UWSToolBox.java
index de7e3f304c86957f2fad901d8cd9b5faa0241929..2658240d2eaea587eab1c99c6e756d9058ccf38f 100644
--- a/src/uws/UWSToolBox.java
+++ b/src/uws/UWSToolBox.java
@@ -54,7 +54,7 @@ import uws.service.request.UploadFile;
  * Some useful functions for the managing of a UWS service.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (02/2015)
+ * @version 4.1 (03/2015)
  */
 public class UWSToolBox {
 
@@ -463,6 +463,30 @@ public class UWSToolBox {
 	/* *************************** */
 	/* RESPONSE MANAGEMENT METHODS */
 	/* *************************** */
+
+	/**
+	 * <p>Flush the buffer of the given {@link PrintWriter}.</p>
+	 * 
+	 * <p>
+	 * 	This function aims to be used if the given {@link PrintWriter} has been provided by an {@link HttpServletResponse}.
+	 * 	In such case, a call to its flush() function may generate a silent error which could only mean that
+	 * 	the connection with the HTTP client has been closed.
+	 * </p>
+	 * 
+	 * @param writer	The writer to flush.
+	 * 
+	 * @throws ClientAbortException		If the connection with the HTTP client is closed.
+	 * 
+	 * @see PrintWriter#flush()
+	 * 
+	 * @since 4.1
+	 */
+	public static final void flush(final PrintWriter writer) throws ClientAbortException{
+		writer.flush();
+		if (writer.checkError())
+			throw new ClientAbortException();
+	}
+
 	/**
 	 * Copies the content of the given input stream in the given HTTP response.
 	 * 
diff --git a/src/uws/job/JobThread.java b/src/uws/job/JobThread.java
index 5f2bef35e71f1d9f15d1301bbbc8de1dd7fda62d..141350190d4c2294e8627c9446ac18c8b50f7f91 100644
--- a/src/uws/job/JobThread.java
+++ b/src/uws/job/JobThread.java
@@ -16,7 +16,7 @@ package uws.job;
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  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)
  */
 
@@ -60,7 +60,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </ul>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (09/2014)
+ * @version 4.1 (04/2015)
  * 
  * @see UWSJob#start()
  * @see UWSJob#abort()
@@ -75,6 +75,11 @@ public abstract class JobThread extends Thread {
 	/** The last error which has occurred during the execution of this thread. */
 	protected UWSException lastError = null;
 
+	/** Indicate whether the exception stored in the attribute {@link #lastError} should be considered as a grave error or not.
+	 * By default, {@link #lastError} is a "normal" error.
+	 * @since 4.1 */
+	protected boolean fatalError = false;
+
 	/** Indicates whether the {@link UWSJob#jobWork()} has been called and finished, or not. */
 	protected boolean finished = false;
 
@@ -424,44 +429,60 @@ public abstract class JobThread extends Thread {
 		logger.logThread(LogLevel.INFO, this, "START", "Thread \"" + getName() + "\" started.", null);
 
 		try{
-			try{
-				// Execute the task:
-				jobWork();
-
-				// Change the phase to COMPLETED:
-				finished = true;
-				complete();
-			}catch(InterruptedException ex){
-				// Abort:
-				finished = true;
-				if (!job.stopping)
+			// Execute the task:
+			jobWork();
+
+			// Change the phase to COMPLETED:
+			finished = true;
+			complete();
+			logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getName() + "\" successfully ended.", null);
+
+		}catch(InterruptedException ex){
+			/* CASE: ABORTION
+			 *   In case of abortion, the thread just stops normally, just logging an INFO saying that the thread has been cancelled.
+			 * Since it is not an abnormal behavior there is no reason to keep a trace of the interrupted exception. */
+			finished = true;
+			// Abort:
+			if (!job.stopping){
+				try{
 					job.abort();
-				// Log the abortion:
-				logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getName() + "\" cancelled.", null);
+				}catch(UWSException ue){
+					/* Should not happen since the reason of a such exception would be that the thread can not be stopped...
+					 * ...but we are already in the thread and it is stopping. */
+					logger.logJob(LogLevel.WARNING, job, "ABORT", "Can not put the job in its ABORTED phase!", ue);
+				}
 			}
-			return;
+			// Log the abortion:
+			logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getName() + "\" cancelled.", null);
 
 		}catch(UWSException ue){
-			// Save the error:
+			/* CASE: ERROR for a known reason
+			 *   A such error is just a "normal" error, in the sense its cause is known and in a way supported or expected in
+			 * a special configuration or parameters. Thus, the error is kept and will logged with a stack trace afterwards.*/
 			lastError = ue;
 
 		}catch(Throwable t){
-			// Build the error:
+			/* DEFAULT: FATAL error
+			 *   Any other error is considered as FATAL because it was not expected or supported at a given point.
+			 * It is generally a bug or a forgiven thing in the code of the library. As for "normal" errors, this error
+			 * is kept and will logged with stack trace afterwards. */
+			fatalError = true;
 			if (t instanceof Error)
 				lastError = new UWSException(UWSException.INTERNAL_SERVER_ERROR, t, "A FATAL DEEP ERROR OCCURED WHILE EXECUTING THIS QUERY! This error is reported in the service logs.", ErrorType.FATAL);
 			else if (t.getMessage() == null || t.getMessage().trim().isEmpty())
-				lastError = new UWSException(UWSException.INTERNAL_SERVER_ERROR, t.getClass().getName(), ErrorType.FATAL);
+				lastError = new UWSException(UWSException.INTERNAL_SERVER_ERROR, t, t.getClass().getName(), ErrorType.FATAL);
 			else
 				lastError = new UWSException(UWSException.INTERNAL_SERVER_ERROR, t, ErrorType.FATAL);
 
 		}finally{
 			finished = true;
 
-			// Publish the error if any has occurred:
+			/* PUBLISH THE ERROR if any has occurred */
 			if (lastError != null){
 				// Log the error:
-				LogLevel logLevel = (lastError.getCause() != null && lastError.getCause() instanceof Error) ? LogLevel.FATAL : LogLevel.ERROR;
-				logger.logThread(logLevel, this, "END", "Thread \"" + getName() + "\" ended with an error.", lastError);
+				LogLevel logLevel = fatalError ? LogLevel.FATAL : LogLevel.ERROR;
+				logger.logJob(logLevel, job, "END", "The following " + (fatalError ? "GRAVE" : "") + " error interrupted the execution of the job " + job.getJobId() + ".", lastError);
+				logger.logThread(logLevel, this, "END", "Thread \"" + getName() + "\" ended with an error.", null);
 				// Set the error into the job:
 				try{
 					setError(lastError);
@@ -472,11 +493,9 @@ public abstract class JobThread extends Thread {
 					}catch(UWSException ue2){
 						logger.logThread(logLevel, this, "SET_ERROR", "[2nd and last Attempt] Problem in JobThread.setError(ErrorSummary), while setting the execution error of the job " + job.getJobId() + ". This error can not be reported to the user, but it will be reported in the log in the JOB context.", ue2);
 						// Note: no need of a level 3: if the second attempt fails, it means the job is in a wrong phase and no error summary can never be set ; further attempt won't change anything!
-						logger.logJob(logLevel, job, "EXECUTING", "An error has interrupted the execution of the job \"" + job.getJobId() + "\". Here is its summary: " + lastError.getMessage(), lastError);
 					}
 				}
-			}else
-				logger.logThread(LogLevel.INFO, this, "END", "Thread \"" + getName() + "\" successfully ended.", null);
+			}
 		}
 	}
 }
diff --git a/src/uws/service/UWSService.java b/src/uws/service/UWSService.java
index 4c16c49a738b939634893da887c0795a0922f441..4283ff5ddf54268883757f4543ce0b54f1a34d4f 100644
--- a/src/uws/service/UWSService.java
+++ b/src/uws/service/UWSService.java
@@ -35,7 +35,6 @@ import javax.servlet.http.HttpServletResponse;
 import uws.AcceptHeader;
 import uws.UWSException;
 import uws.UWSToolBox;
-import uws.job.ErrorType;
 import uws.job.ExecutionPhase;
 import uws.job.JobList;
 import uws.job.JobThread;
@@ -186,7 +185,7 @@ import uws.service.request.RequestParser;
  * 
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (02/2015)
+ * @version 4.1 (04/2015)
  */
 public class UWSService implements UWS {
 
@@ -1145,21 +1144,55 @@ public class UWSService implements UWS {
 			response.flushBuffer();
 
 			// Log the successful execution of the action:
-			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
+			logger.logHttp(LogLevel.INFO, response, reqID, user, "UWS action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
+
+		}catch(IOException ioe){
+			/*
+			 *   Any IOException thrown while writing the HTTP response is generally caused by a client abortion (intentional or timeout)
+			 * or by a connection closed with the client for another reason.
+			 *   Consequently, a such error should not be considered as a real error from the server or the library: the request is
+			 * canceled, and so the response is not expected. It is anyway not possible any more to send it (header and/or body) totally
+			 * or partially.
+			 *   Nothing can solve this error. So the "error" is just reported as a simple information and theoretically the action
+			 * executed when this error has been thrown is already stopped.
+			 */
+			logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP request aborted or connection with the client closed => the UWS action \"" + action.getName() + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", null);
 
 		}catch(UWSException ex){
+			/*
+			 *   Any known/"expected" UWS exception is logged but also returned to the HTTP client in an error document.
+			 *   Since the error is known, it is supposed to have already been logged with a full stack trace. Thus, there
+			 * is no need to log again its stack trace...just its message is logged. 
+			 *   Besides, this error may also be just a redirection and not a true error. In such case, the error message
+			 * is not logged.
+			 */
 			// If redirection, flag the action as executed with success:
 			if (ex.getHttpErrorCode() == UWSException.SEE_OTHER)
 				actionApplied = true;
 			sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
-		}catch(Exception ex){
-			if (ex.getClass().getName().endsWith("ClientAbortException")){
-				// Log the client abortion:
-				logger.logHttp(LogLevel.INFO, request, reqID, "HTTP " + UWSException.ACCEPTED_BUT_NOT_COMPLETE + " - Action \"" + action.getName() + "\" aborted by the client! [Client abort => " + ex.getClass().getName() + "]", ex);
-				// Notify the client abortion in a TAP error:
-				errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, response, request, reqID, user, action.getName());
-			}else
-				sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
+
+		}catch(IllegalStateException ise){
+			/*
+			 *   Any IllegalStateException that reaches this point, is supposed coming from a HttpServletResponse operation which
+			 * has to reset the response buffer (e.g. resetBuffer(), sendRedirect(), sendError()).
+			 *   If this exception happens, the library tried to rewrite the HTTP response body with a message or a result,
+			 * while this body has already been partially sent to the client. It is then no longer possible to change its content.
+			 *   Consequently, the error is logged as FATAL and a message will be appended at the end of the already submitted response
+			 * to alert the HTTP client that an error occurs and the response should not be considered as complete and reliable. 
+			 */
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(ise, response, request, reqID, user, ((action != null) ? action.getName() : null));
+			// Log the error:
+			getLogger().logHttp(LogLevel.FATAL, response, reqID, user, "HTTP response already partially committed => the UWS action \"" + action.getName() + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", (ise.getCause() != null) ? ise.getCause() : ise);
+
+		}catch(Throwable t){
+			/*
+			 *   Any other error is considered as unexpected if it reaches this point. Consequently, it has not yet been logged.
+			 * So its stack trace will be fully logged, and an appropriate message will be returned to the HTTP client. The
+			 * returned document should contain not too technical information which would be useless for the user.
+			 */
+			sendError(t, request, reqID, user, ((action != null) ? action.getName() : null), response);
+
 		}finally{
 			executedAction = action;
 			// Free resources about uploaded files ; only unused files will be deleted:
@@ -1215,7 +1248,7 @@ public class UWSService implements UWS {
 			// Apply the redirection:
 			redirect(error.getMessage(), request, user, uwsAction, response);
 		}else
-			sendError((Exception)error, request, reqID, user, uwsAction, response);
+			sendError((Throwable)error, request, reqID, user, uwsAction, response);
 	}
 
 	/**
@@ -1231,21 +1264,21 @@ public class UWSService implements UWS {
 	 * @param request		The request which has caused the given error <i>(not used by default)</i>.
 	 * @param reqID			ID of the request.
 	 * @param user			The user which executes the given request.
-	 * @param uwsAction	The UWS action corresponding to the given request.
+	 * @param uwsAction		The UWS action corresponding to the given request.
 	 * @param response		The response in which the error must be published.
 	 * 
 	 * @throws IOException	If there is an error when calling {@link HttpServletResponse#sendError(int, String)}.
 	 * 
 	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)}
 	 */
-	public final void sendError(Exception error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
+	public final void sendError(Throwable error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
 		// Write the error in the response and return the appropriate HTTP status code:
 		errorWriter.writeError(error, response, request, reqID, user, uwsAction);
 		// Log the error:
-		if (uwsAction == null)
-			logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Unknown UWS action!", error);
+		if (error instanceof UWSException)
+			logger.logHttp(LogLevel.ERROR, response, reqID, user, "UWS action \"" + uwsAction + "\" FAILED with the error: \"" + error.getMessage() + "\"!", null);
 		else
-			logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\"!", error);
+			logger.logHttp(LogLevel.FATAL, response, reqID, user, "UWS action \"" + uwsAction + "\" execution FAILED with a GRAVE error!", error);
 	}
 
 }
diff --git a/src/uws/service/UWSServlet.java b/src/uws/service/UWSServlet.java
index 17c748178ff7410db9aa161c91759babcab571e1..9a47f3a6b5ad09722897e4bc58f3cd089e117db8 100644
--- a/src/uws/service/UWSServlet.java
+++ b/src/uws/service/UWSServlet.java
@@ -44,7 +44,6 @@ import uws.UWSException;
 import uws.UWSExceptionFactory;
 import uws.UWSToolBox;
 import uws.job.ErrorSummary;
-import uws.job.ErrorType;
 import uws.job.JobList;
 import uws.job.JobThread;
 import uws.job.Result;
@@ -132,7 +131,7 @@ import uws.service.request.UploadFile;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (02/2015)
+ * @version 4.1 (04/2015)
  */
 public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory {
 	private static final long serialVersionUID = 1L;
@@ -323,7 +322,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 		try{
 			req.setAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS, requestParser.parse(req));
 		}catch(UWSException ue){
-			logger.log(LogLevel.ERROR, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue);
+			logger.log(LogLevel.WARNING, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue);
 		}
 
 		// Log the reception of the request:
@@ -432,18 +431,52 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 			resp.flushBuffer();
 
 			// Log the successful execution of the action:
-			logger.logHttp(LogLevel.INFO, resp, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + uwsAction + "\" successfully executed.", null);
+			logger.logHttp(LogLevel.INFO, resp, reqID, user, "UWS action \"" + uwsAction + "\" successfully executed.", null);
+
+		}catch(IOException ioe){
+			/*
+			 *   Any IOException thrown while writing the HTTP response is generally caused by a client abortion (intentional or timeout)
+			 * or by a connection closed with the client for another reason.
+			 *   Consequently, a such error should not be considered as a real error from the server or the library: the request is
+			 * canceled, and so the response is not expected. It is anyway not possible any more to send it (header and/or body) totally
+			 * or partially.
+			 *   Nothing can solve this error. So the "error" is just reported as a simple information and theoretically the action
+			 * executed when this error has been thrown is already stopped.
+			 */
+			logger.logHttp(LogLevel.INFO, resp, reqID, user, "HTTP request aborted or connection with the client closed => the UWS action \"" + uwsAction + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", null);
+
+		}catch(UWSException ex){
+			/*
+			 *   Any known/"expected" UWS exception is logged but also returned to the HTTP client in an error document.
+			 *   Since the error is known, it is supposed to have already been logged with a full stack trace. Thus, there
+			 * is no need to log again its stack trace...just its message is logged. 
+			 *   Besides, this error may also be just a redirection and not a true error. In such case, the error message
+			 * is not logged.
+			 */
+			sendError(ex, req, reqID, user, uwsAction, resp);
+
+		}catch(IllegalStateException ise){
+			/*
+			 *   Any IllegalStateException that reaches this point, is supposed coming from a HttpServletResponse operation which
+			 * has to reset the response buffer (e.g. resetBuffer(), sendRedirect(), sendError()).
+			 *   If this exception happens, the library tried to rewrite the HTTP response body with a message or a result,
+			 * while this body has already been partially sent to the client. It is then no longer possible to change its content.
+			 *   Consequently, the error is logged as FATAL and a message will be appended at the end of the already submitted response
+			 * to alert the HTTP client that an error occurs and the response should not be considered as complete and reliable. 
+			 */
+			// Write the error in the response and return the appropriate HTTP status code:
+			errorWriter.writeError(ise, resp, req, reqID, user, uwsAction);
+			// Log the error:
+			getLogger().logHttp(LogLevel.FATAL, resp, reqID, user, "HTTP response already partially committed => the UWS action \"" + uwsAction + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", (ise.getCause() != null) ? ise.getCause() : ise);
 
-		}catch(UWSException ue){
-			sendError(ue, req, reqID, user, uwsAction, resp);
 		}catch(Throwable t){
-			if (t.getClass().getName().endsWith("ClientAbortException")){
-				// Log the client abortion:
-				logger.logHttp(LogLevel.INFO, req, reqID, "HTTP " + UWSException.ACCEPTED_BUT_NOT_COMPLETE + " - Action \"" + uwsAction + "\" aborted by the client! [Client abort => " + t.getClass().getName() + "]", t);
-				// Notify the client abortion in a TAP error:
-				errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, resp, req, reqID, user, uwsAction);
-			}else
-				sendError(t, req, reqID, user, uwsAction, resp);
+			/*
+			 *   Any other error is considered as unexpected if it reaches this point. Consequently, it has not yet been logged.
+			 * So its stack trace will be fully logged, and an appropriate message will be returned to the HTTP client. The
+			 * returned document should contain not too technical information which would be useless for the user.
+			 */
+			sendError(t, req, reqID, user, uwsAction, resp);
+
 		}finally{
 			// Free resources about uploaded files ; only unused files will be deleted:
 			UWSToolBox.deleteUploads(req);
@@ -461,7 +494,7 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 			serialization = serializer.getUWS(this);
 		}catch(Exception e){
 			if (!(e instanceof UWSException)){
-				getLogger().logUWS(LogLevel.WARNING, requestUrl, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
+				getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
 				throw new UWSException(UWSException.NO_CONTENT, e, "No home page available for this UWS service!");
 			}else
 				throw (UWSException)e;
@@ -794,15 +827,10 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
 	 * @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)}
 	 */
 	public final void sendError(Throwable error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException{
-		try{
-			// Write the error in the response and return the appropriate HTTP status code:
-			errorWriter.writeError(error, response, request, reqID, user, uwsAction);
-			// Log the error:
-			logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
-		}catch(IOException ioe){
-			logger.logHttp(LogLevel.FATAL, response, reqID, user, "Can not write the response!", ioe);
-			throw new ServletException("Can not write the error response: \"" + error.getMessage() + "\"! You should notify the administrator of the service (FATAL-" + reqID + ").");
-		}
+		// Write the error in the response and return the appropriate HTTP status code:
+		errorWriter.writeError(error, response, request, reqID, user, uwsAction);
+		// Log the error:
+		logger.logHttp(LogLevel.ERROR, response, reqID, user, "Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
 	}
 
 	/* ************** */
diff --git a/src/uws/service/actions/ShowHomePage.java b/src/uws/service/actions/ShowHomePage.java
index c713c9222d3f135b66da07b9c052cd0cc46cd899..7d4205d7dcb8f85201ef03e9aa06dd74dab330ea 100644
--- a/src/uws/service/actions/ShowHomePage.java
+++ b/src/uws/service/actions/ShowHomePage.java
@@ -16,7 +16,7 @@ package uws.service.actions;
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  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)
  */
 
@@ -44,7 +44,7 @@ import uws.service.log.UWSLog.LogLevel;
  * <p>This action displays the UWS home page.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (08/2014)
+ * @version 4.1 (04/2015)
  */
 public class ShowHomePage extends UWSAction {
 	private static final long serialVersionUID = 1L;
@@ -106,7 +106,7 @@ public class ShowHomePage extends UWSAction {
 				serialization = serializer.getUWS(uws);
 			}catch(Exception e){
 				if (!(e instanceof UWSException)){
-					getLogger().logUWS(LogLevel.WARNING, urlInterpreter, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
+					getLogger().logUWS(LogLevel.ERROR, urlInterpreter, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e);
 					throw new UWSException(UWSException.NO_CONTENT, e, "No home page available for this UWS service!");
 				}else
 					throw (UWSException)e;
diff --git a/src/uws/service/error/DefaultUWSErrorWriter.java b/src/uws/service/error/DefaultUWSErrorWriter.java
index 74366d32d4f489cfe085670c21559ca9e0a3a4ec..90196133a67a3341ac49b4f50602309b01beb846 100644
--- a/src/uws/service/error/DefaultUWSErrorWriter.java
+++ b/src/uws/service/error/DefaultUWSErrorWriter.java
@@ -16,12 +16,14 @@ package uws.service.error;
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  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 java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -67,7 +69,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (09/2014)
+ * @version 4.1 (04/2015)
  */
 public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 
@@ -90,31 +92,40 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 	}
 
 	@Override
-	public void writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action) throws IOException{
+	public boolean writeError(Throwable t, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action){
 		if (t == null || response == null)
-			return;
+			return true;
 
+		boolean written = false;
 		// If expected error, just write it:
 		if (t instanceof UWSException){
 			UWSException ue = (UWSException)t;
-			writeError(ue.getMessage(), ue.getUWSErrorType(), ue.getHttpErrorCode(), response, request, reqID, user, action);
+			written = writeError(ue.getMessage(), ue.getUWSErrorType(), ue.getHttpErrorCode(), response, request, reqID, user, action);
 		}
 		// Otherwise, log it and write a message to the user:
 		else{
 			// log the error as GRAVE/FATAL (because unexpected/unmanaged):
 			logger.logUWS(LogLevel.FATAL, null, null, "[REQUEST N°" + reqID + "] " + t.getMessage(), t);
 			// write a message to the user:
-			writeError("INTERNAL SERVER ERROR! Sorry, this error is unexpected and no explanation can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following 'Request ID').", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
+			written = writeError("INTERNAL SERVER ERROR! Sorry, this error is unexpected and no explanation can be provided for the moment. Details about this error have been reported in the service log files ; you should try again your request later or notify the administrator(s) by yourself (with the following 'Request ID').", ErrorType.FATAL, UWSException.INTERNAL_SERVER_ERROR, response, request, reqID, user, action);
 		}
+		return written;
 	}
 
 	@Override
-	public void writeError(String message, ErrorType type, int httpErrorCode, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action) throws IOException{
+	public boolean writeError(String message, ErrorType type, int httpErrorCode, HttpServletResponse response, HttpServletRequest request, String reqID, JobOwner user, String action){
 		if (message == null || response == null)
-			return;
+			return true;
 
-		// Just format and write the error message:
-		formatError(message, type, httpErrorCode, reqID, action, user, response, (request != null) ? request.getHeader("Accept") : null);
+		try{
+			// Just format and write the error message:
+			formatError(message, type, httpErrorCode, reqID, action, user, response, (request != null) ? request.getHeader("Accept") : null);
+			return true;
+		}catch(IllegalStateException ise){
+			return false;
+		}catch(IOException ioe){
+			return false;
+		}
 	}
 
 	@Override
@@ -169,11 +180,6 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 	 * @see #formatJSONError(Throwable, boolean, ErrorType, int, String, JobOwner, HttpServletResponse)
 	 */
 	protected void formatError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response, final String acceptHeader) throws IOException{
-		// Reset the whole response to ensure the output stream is free:
-		if (response.isCommitted())
-			return;
-		response.reset();
-
 		String format = chooseFormat(acceptHeader);
 		if (format != null && (format.equalsIgnoreCase("application/json") || format.equalsIgnoreCase("text/json") || format.equalsIgnoreCase("json")))
 			formatJSONError(message, type, httpErrorCode, reqID, action, user, response);
@@ -196,14 +202,33 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 	 * @throws IOException		If there is an error while writing the given exception.
 	 */
 	protected void formatHTMLError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
-		// Erase anything written previously in the HTTP response:
-		response.reset();
+		try{
+			// Erase anything written previously in the HTTP response:
+			response.reset();
+
+			// Set the HTTP status:
+			response.setStatus(httpErrorCode);
 
-		// Set the HTTP status code and the content type of the response:
-		response.setStatus(httpErrorCode);
-		response.setContentType(UWSSerializer.MIME_TYPE_HTML);
+			// Set the MIME type of the answer (XML for a VOTable document):
+			response.setContentType(UWSSerializer.MIME_TYPE_HTML);
 
-		PrintWriter out = response.getWriter();
+		}catch(IllegalStateException ise){
+			/*   If it is not possible any more to reset the response header and body,
+			 * the error is anyway written in order to corrupt the HTTP response.
+			 *   Thus, it will be obvious that an error occurred and the result is
+			 * incomplete and/or wrong.*/
+		}
+
+		PrintWriter out;
+		try{
+			out = response.getWriter();
+		}catch(IllegalStateException ise){
+			/*   This exception may occur just because either the writer or
+			 * the output-stream can be used (because already got before).
+			 *   So, we just have to get the output-stream if getting the writer
+			 * throws an error.*/
+			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(response.getOutputStream())));
+		}
 
 		// Header:
 		out.println("<html>\n\t<head>");
@@ -238,7 +263,8 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 		out.println("\t\t</ul>");
 
 		out.println("\t</body>\n</html>");
-		out.close();
+
+		out.flush();
 	}
 
 	/**
@@ -256,14 +282,34 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 	 * @throws IOException		If there is an error while writing the given exception.
 	 */
 	protected void formatJSONError(final String message, final ErrorType type, final int httpErrorCode, final String reqID, final String action, final JobOwner user, final HttpServletResponse response) throws IOException{
-		// Erase anything written previously in the HTTP response:
-		response.reset();
+		try{
+			// Erase anything written previously in the HTTP response:
+			response.reset();
 
-		// Set the HTTP status code and the content type of the response:
-		response.setStatus(httpErrorCode);
-		response.setContentType(UWSSerializer.MIME_TYPE_JSON);
+			// Set the HTTP status:
+			response.setStatus(httpErrorCode);
+
+			// Set the MIME type of the answer (JSON):
+			response.setContentType(UWSSerializer.MIME_TYPE_JSON);
+
+		}catch(IllegalStateException ise){
+			/*   If it is not possible any more to reset the response header and body,
+			 * the error is anyway written in order to corrupt the HTTP response.
+			 *   Thus, it will be obvious that an error occurred and the result is
+			 * incomplete and/or wrong.*/
+		}
+
+		PrintWriter out;
+		try{
+			out = response.getWriter();
+		}catch(IllegalStateException ise){
+			/*   This exception may occur just because either the writer or
+			 * the output-stream can be used (because already got before).
+			 *   So, we just have to get the output-stream if getting the writer
+			 * throws an error.*/
+			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(response.getOutputStream())));
+		}
 
-		PrintWriter out = response.getWriter();
 		try{
 			JSONWriter json = new JSONWriter(out);
 
@@ -277,12 +323,12 @@ public class DefaultUWSErrorWriter implements ServiceErrorWriter {
 			json.key("message").value(message);
 
 			json.endObject();
+
+			out.flush();
+
 		}catch(JSONException je){
 			logger.logUWS(LogLevel.ERROR, null, "FORMAT_ERROR", "Impossible to format/write an error in JSON!", je);
 			throw new IOException("Error while formatting the error in JSON!", je);
-		}finally{
-			out.flush();
-			out.close();
 		}
 	}
 
diff --git a/src/uws/service/error/ServiceErrorWriter.java b/src/uws/service/error/ServiceErrorWriter.java
index 965e17113eec4c216ab33a4ac758b04515ba1012..a1cea7d0c9182030c5ea59dd053610d789997cc9 100644
--- a/src/uws/service/error/ServiceErrorWriter.java
+++ b/src/uws/service/error/ServiceErrorWriter.java
@@ -16,7 +16,7 @@ package uws.service.error;
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  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)
  */
 
@@ -35,7 +35,7 @@ import uws.job.user.JobOwner;
  * Let's writing/formatting any Exception/Throwable in an {@link HttpServletResponse} or in an error summary.
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (09/2014)
+ * @version 4.1 (04/2015)
  */
 public interface ServiceErrorWriter {
 
@@ -47,6 +47,12 @@ public interface ServiceErrorWriter {
 	 * 	No error may be thrown.
 	 * </i></p>
 	 * 
+	 * <p><i><b>IMPORTANT:</b>
+	 * 	If any {@link IOException} occurs while writing the error in the given {@link HttpServletResponse} output stream,
+	 * 	this function should stop and return <code>false</code>. In such case, the error which was supposed to be written
+	 * 	may be logged.
+	 * </i></p>
+	 * 
 	 * @param t					Exception to write/format.
 	 * @param response			Response in which the given exception must be written.
 	 * @param request			Request at the origin of the error (MAY BE NULL).
@@ -54,9 +60,10 @@ public interface ServiceErrorWriter {
 	 * @param user				User which sends the given request (which generates the error) (MAY BE NULL).
 	 * @param action			Type/Name of the action which generates the error (MAY BE NULL).
 	 * 
-	 * @throws IOException		If there is an error while writing the response.
+	 * @return	<i>true</i> if the given error message has been successfully written in the given {@link HttpServletResponse},
+	 *        	<i>false</i> otherwise.
 	 */
-	public void writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException;
+	public boolean writeError(final Throwable t, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action);
 
 	/**
 	 * <p>Write the described error in the given response.</p>
@@ -66,6 +73,12 @@ public interface ServiceErrorWriter {
 	 * 	No error may be thrown.
 	 * </i></p>
 	 * 
+	 * <p><i><b>IMPORTANT:</b>
+	 * 	If any {@link IOException} occurs while writing the error in the given {@link HttpServletResponse} output stream,
+	 * 	this function should stop and return <code>false</code>. In such case, the error which was supposed to be written
+	 * 	may be logged.
+	 * </i></p>
+	 * 
 	 * @param message			Message to display.
 	 * @param type				Type of the error: FATAL or TRANSIENT.
 	 * @param httpErrorCode		HTTP error code (i.e. 404, 500).
@@ -75,9 +88,10 @@ public interface ServiceErrorWriter {
 	 * @param user				User which sends the HTTP request.
 	 * @param action			Action corresponding to the given request.
 	 * 
-	 * @throws IOException		If there is an error while writing the response.
+	 * @return	<i>true</i> if the given error message has been successfully written in the given {@link HttpServletResponse},
+	 *        	<i>false</i> otherwise.
 	 */
-	public void writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action) throws IOException;
+	public boolean writeError(final String message, final ErrorType type, final int httpErrorCode, final HttpServletResponse response, final HttpServletRequest request, final String reqID, final JobOwner user, final String action);
 
 	/**
 	 * <p>Write the given error in the given output stream.</p>
diff --git a/src/uws/service/log/DefaultUWSLog.java b/src/uws/service/log/DefaultUWSLog.java
index f9ea297cf4d409912195e8cbdec6e80d4851ad69..dd03f8ff49bb236c88f8326f13b7e15ffc51befe 100644
--- a/src/uws/service/log/DefaultUWSLog.java
+++ b/src/uws/service/log/DefaultUWSLog.java
@@ -43,7 +43,7 @@ import uws.service.file.UWSFileManager;
  * <p>Default implementation of {@link UWSLog} interface which lets logging any message about a UWS.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.1 (02/2015)
+ * @version 4.1 (04/2015)
  */
 public class DefaultUWSLog implements UWSLog {
 
@@ -220,6 +220,27 @@ public class DefaultUWSLog implements UWSLog {
 	/* GENERAL LOGGING METHODS */
 	/* *********************** */
 
+	/**
+	 * <p>Normalize a log message.</p>
+	 * 
+	 * <p>
+	 * 	Since a log entry will a tab-separated concatenation of information, additional tabulations or new-lines
+	 * 	would corrupt a log entry. This function replaces such characters by one space. Only \r are definitely deleted.
+	 * </p>
+	 * 
+	 * @param message	Log message to normalize.
+	 * 
+	 * @return	The normalized log message.
+	 * 
+	 * @since 4.1
+	 */
+	protected String normalizeMessage(final String message){
+		if (message == null)
+			return null;
+		else
+			return message.replaceAll("[\n\t]", " ").replaceAll("\r", "");
+	}
+
 	/**
 	 * <p>Tells whether a message with the given error level can be logged or not.</p>
 	 * 
@@ -261,7 +282,7 @@ public class DefaultUWSLog implements UWSLog {
 
 	@Override
 	public void log(LogLevel level, final String context, final String message, final Throwable error){
-		log(level, context, null, null, message, error);
+		log(level, context, null, null, message, null, error);
 	}
 
 	/**
@@ -276,11 +297,12 @@ public class DefaultUWSLog implements UWSLog {
 	 * @param event		Context event during which this log is emitted. <i>MAY be NULL</i>
 	 * @param ID		ID of the job or HTTP request (it may also be an ID of anything else). <i>MAY BE NULL</i>
 	 * @param message	Message of the error. <i>MAY be NULL</i>
+	 * @param addColumn	Additional column to append after the message and before the stack trace.
 	 * @param error		Error at the origin of the log error/warning/fatal. <i>MAY be NULL</i>
 	 * 
 	 * @since 4.1
 	 */
-	protected final void log(LogLevel level, final String context, final String event, final String ID, final String message, final Throwable error){
+	protected final void log(LogLevel level, final String context, final String event, final String ID, final String message, final String addColumn, final Throwable error){
 		// If no message and no error is provided, nothing to log, so nothing to write:
 		if ((message == null || message.length() <= 0) && error == null)
 			return;
@@ -306,9 +328,12 @@ public class DefaultUWSLog implements UWSLog {
 		buf.append((ID == null) ? "" : ID).append('\t');
 		// Print the message:
 		if (message != null)
-			buf.append(message);
+			buf.append(normalizeMessage(message));
 		else if (error != null)
-			buf.append("[EXCEPTION ").append(error.getClass().getName()).append("] ").append(error.getMessage());
+			buf.append("[EXCEPTION ").append(error.getClass().getName()).append("] ").append(normalizeMessage(error.getMessage()));
+		// Print the additional column, if any:
+		if (addColumn != null)
+			buf.append('\t').append(normalizeMessage(addColumn));
 
 		// Write the whole log line:
 		PrintWriter out = getOutput(level, context);
@@ -409,11 +434,6 @@ public class DefaultUWSLog implements UWSLog {
 
 			StringBuffer str = new StringBuffer();
 
-			// Write the message (if any):
-			if (message != null)
-				str.append(message);
-			str.append('\t');
-
 			// Write the request type, content type and the URL:
 			str.append(request.getMethod());
 			str.append(" as ");
@@ -438,16 +458,16 @@ public class DefaultUWSLog implements UWSLog {
 			for(Entry<String,String> p : params.entrySet()){
 				if (++i > 0)
 					str.append('&');
-				str.append(p.getKey()).append('=').append((p.getValue() != null) ? p.getValue().replaceAll("[\t\n\r]", " ") : "");
+				str.append(p.getKey()).append('=').append((p.getValue() != null) ? p.getValue() : "");
 			}
 			str.append(')');
 
 			// Send the log message to the log file:
-			log(level, "HTTP", "REQUEST_RECEIVED", requestId, str.toString(), error);
+			log(level, "HTTP", "REQUEST_RECEIVED", requestId, message, str.toString(), error);
 		}
 		// OTHERWISE, just write the given message:
 		else
-			log(level, "HTTP", "REQUEST_RECEIVED", requestId, message, error);
+			log(level, "HTTP", "REQUEST_RECEIVED", requestId, message, null, error);
 	}
 
 	/**
@@ -470,11 +490,6 @@ public class DefaultUWSLog implements UWSLog {
 
 			StringBuffer str = new StringBuffer();
 
-			// Write the message (if any):
-			if (message != null)
-				str.append(message);
-			str.append('\t');
-
 			// Write the response status code:
 			str.append("HTTP-").append(response.getStatus());
 
@@ -493,11 +508,11 @@ public class DefaultUWSLog implements UWSLog {
 				str.append(" as ").append(response.getContentType());
 
 			// Send the log message to the log file:
-			log(level, "HTTP", "RESPONSE_SENT", requestId, str.toString(), error);
+			log(level, "HTTP", "RESPONSE_SENT", requestId, message, str.toString(), error);
 		}
 		// OTHERWISE, just write the given message:
 		else
-			log(level, "HTTP", "RESPONSE_SENT", requestId, message, error);
+			log(level, "HTTP", "RESPONSE_SENT", requestId, message, null, error);
 	}
 
 	/* ************ */
@@ -515,16 +530,17 @@ public class DefaultUWSLog implements UWSLog {
 			return;
 
 		// CASE "BACKUPED": Append to the message the backup report:
+		String report = null;
 		if (event != null && event.equalsIgnoreCase("BACKUPED") && obj != null && obj.getClass().getName().equals("[I")){
 			int[] backupReport = (int[])obj;
-			message += "\t(" + backupReport[0] + "/" + backupReport[1] + " jobs backuped ; " + backupReport[2] + "/" + backupReport[3] + " users backuped)";
+			report = "(" + backupReport[0] + "/" + backupReport[1] + " jobs backuped ; " + backupReport[2] + "/" + backupReport[3] + " users backuped)";
 		}else if (event != null && event.equalsIgnoreCase("RESTORED") && obj != null && obj.getClass().getName().equals("[I")){
 			int[] restoreReport = (int[])obj;
-			message += "\t(" + restoreReport[0] + "/" + restoreReport[1] + " jobs restored ; " + restoreReport[2] + "/" + restoreReport[3] + " users restored)";
+			report = "(" + restoreReport[0] + "/" + restoreReport[1] + " jobs restored ; " + restoreReport[2] + "/" + restoreReport[3] + " users restored)";
 		}
 
 		// Log the message
-		log(level, "UWS", event, null, message, error);
+		log(level, "UWS", event, null, message, report, error);
 	}
 
 	/* ************ */
@@ -533,7 +549,7 @@ public class DefaultUWSLog implements UWSLog {
 
 	@Override
 	public void logJob(LogLevel level, UWSJob job, String event, String message, Throwable error){
-		log(level, "JOB", event, (job == null) ? null : job.getJobId(), message, error);
+		log(level, "JOB", event, (job == null) ? null : job.getJobId(), message, null, error);
 	}
 
 	/* ********************** */
@@ -553,11 +569,6 @@ public class DefaultUWSLog implements UWSLog {
 
 			StringBuffer str = new StringBuffer();
 
-			// Write the message (if any):
-			if (message != null)
-				str.append(message);
-			str.append('\t');
-
 			// Write the thread name and ID:
 			str.append(thread.getName()).append(" (thread ID: ").append(thread.getId()).append(")");
 
@@ -570,10 +581,10 @@ public class DefaultUWSLog implements UWSLog {
 			// Write the number of active threads:
 			str.append(" where ").append(thread.getThreadGroup().activeCount()).append(" threads are active");
 
-			log(level, "THREAD", event, thread.getName(), str.toString(), error);
+			log(level, "THREAD", event, thread.getName(), message, str.toString(), error);
 
 		}else
-			log(level, "THREAD", event, null, message, error);
+			log(level, "THREAD", event, null, message, null, error);
 	}
 
 }