diff --git a/src/uws/service/file/LocalUWSFileManager.java b/src/uws/service/file/LocalUWSFileManager.java
index 080b80dcbda775e92c36b00c64849b804475ef27..de2e93685ab078e8912e55fd30b74fe6ce74e66c 100644
--- a/src/uws/service/file/LocalUWSFileManager.java
+++ b/src/uws/service/file/LocalUWSFileManager.java
@@ -2,20 +2,20 @@ package uws.service.file;
 
 /*
  * 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 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
@@ -47,30 +47,32 @@ import uws.job.ErrorSummary;
 import uws.job.Result;
 import uws.job.UWSJob;
 import uws.job.user.JobOwner;
+import uws.service.file.io.OutputStreamWithCloseAction;
+import uws.service.file.io.RotateFileAction;
 import uws.service.log.UWSLog.LogLevel;
 import uws.service.request.UploadFile;
 
 /**
  * <p>All UWS files are stored in the local machine into the specified directory.</p>
- * 
+ *
  * <p>
  * 	The name of the log file, the result files and the backup files may be customized by overriding the following functions:
  * 	{@link #getLogFileName(uws.service.log.UWSLog.LogLevel, String)}, {@link #getResultFileName(Result, UWSJob)}, {@link #getBackupFileName(JobOwner)} and {@link #getBackupFileName()}.
  * </p>
- * 
+ *
  * <p>
  * 	By default, results and backups are grouped by owner/user and owners/users are grouped thanks to {@link DefaultOwnerGroupIdentifier}.
  * 	By using the appropriate constructor, you can change these default behaviors.
  * </p>
- * 
+ *
  * <p>
  * 	A log file rotation is set by default so that avoiding a too big log file after several months/years of use.
  * 	By default the rotation is done every month on the 1st at 6am. This frequency can be changed easily thanks to the function
  * 	{@link #setLogRotationFreq(String)}.
  * </p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.2 (09/2017)
+ * @version 4.4 (07/2018)
  */
 public class LocalUWSFileManager implements UWSFileManager {
 
@@ -106,12 +108,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * 	There will be one directory for each owner ID and owner directories will be grouped
 	 * 	thanks to {@link DefaultOwnerGroupIdentifier}.
 	 * </p>
-	 * 
+	 *
 	 * @param root				UWS root directory.
 	 *
 	 * @throws NullPointerException	If the given root directory is <i>null</i>.
 	 * @throws UWSException			If the given file is not a directory or has not the READ and WRITE permissions.
-	 * 
+	 *
 	 * @see #LocalUWSFileManager(File, boolean, boolean, OwnerGroupIdentifier)
 	 */
 	public LocalUWSFileManager(final File root) throws UWSException{
@@ -124,7 +126,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * 	If, according to the third parameter, the owner directories must be grouped,
 	 * 	the {@link DefaultOwnerGroupIdentifier} will be used.
 	 * </p>
-	 * 
+	 *
 	 * @param root						UWS root directory.
 	 * @param oneDirectoryForEachUser	<i>true</i> to create one directory for each owner ID, <i>false</i> otherwise.
 	 * @param groupUserDirectories		<i>true</i> to group user directories, <i>false</i> otherwise.
@@ -132,7 +134,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 *
 	 * @throws NullPointerException	If the given root directory is <i>null</i>.
 	 * @throws UWSException			If the given file is not a directory or has not the READ and WRITE permissions.
-	 * 
+	 *
 	 * @see #LocalUWSFileManager(File, boolean, boolean, OwnerGroupIdentifier)
 	 */
 	public LocalUWSFileManager(final File root, final boolean oneDirectoryForEachUser, final boolean groupUserDirectories) throws UWSException{
@@ -141,7 +143,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Builds a {@link UWSFileManager} which manages all UWS files in the given directory.
-	 * 
+	 *
 	 * @param root						UWS root directory.
 	 * @param oneDirectoryForEachUser	<i>true</i> to create one directory for each owner ID, <i>false</i> otherwise.
 	 * @param groupUserDirectories		<i>true</i> to group user directories, <i>false</i> otherwise.
@@ -189,7 +191,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Gets the directory of the given owner.
-	 * 
+	 *
 	 * @param owner	A job owner.
 	 * @return		Its directory.
 	 */
@@ -210,9 +212,9 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Removes the owner directory if there is no more file in it (except the backup file which is no more required).
-	 * 
+	 *
 	 * @param owner			The user whose the directory must be removed.
-	 * 
+	 *
 	 * @throws IOException	If there is an error while removing the owner directory.
 	 */
 	protected void cleanOwnerDirectory(final JobOwner owner) throws IOException{
@@ -248,7 +250,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * Get the frequency of the log file rotation
 	 * in a human readable way.
-	 * 
+	 *
 	 * @return	A human readable frequency of the log file rotation.
 	 */
 	public final String getLogRotationFreq(){
@@ -257,12 +259,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * <p>Set the frequency at which a rotation of the log file must be done.</p>
-	 * 
+	 *
 	 * <p>
 	 * 	"rotation" means here, to close the currently used log file, to rename it so that suffixing it
 	 * 	with the date at which the first log has been written in it, and to create a new log file.
 	 * </p>
-	 * 
+	 *
 	 * <p>The frequency string must respect the following syntax:</p>
 	 * <ul>
 	 * 	<li>'D' hh mm : daily schedule at hh:mm</li>
@@ -273,12 +275,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * </ul>
 	 * <p><i>Where: hh = integer between 0 and 23, mm = integer between 0 and 59, dd (for 'W') = integer between 1 and 7 (1:sunday, 2:monday, ..., 7:saturday),
 	 * dd (for 'M') = integer between 1 and 31.</i></p>
-	 * 
+	 *
 	 * <p><i><b>Warning:</b>
 	 * 	The frequency type is case sensitive! Then you should particularly pay attention at the case
 	 * 	when using the frequency types 'M' (monthly) and 'm' (every minute).
 	 * </p>
-	 * 
+	 *
 	 * <p>
 	 * 	Parsing errors are not thrown but "resolved" silently. The "solution" depends of the error.
 	 * 	2 cases of errors are considered:
@@ -287,7 +289,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * 	<li><b>Frequency type mismatch:</b> It happens when the first character is not one of the expected (D, W, M, h, m).
 	 * 	                                    That means: bad case (i.e. 'd' rather than 'D'), another character.
 	 * 	                                    In this case, the frequency will be: <b>daily at 00:00</b>.</li>
-	 * 
+	 *
 	 * 	<li><b>Parameter(s) missing or incorrect:</b> With the "daily" frequency ('D'), at least 2 parameters must be provided ;
 	 * 	                                             3 for "weekly" ('W') and "monthly" ('M') ; only 1 for "hourly" ('h') ; none for "every minute" ('m').
 	 * 	                                             This number of parameters is a minimum: only the n first parameters will be considered while
@@ -296,7 +298,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * 	                                             <b>all parameters will be set to their default value</b>
 	 * 	                                             (which is 0 for all parameter except dd for which it is 1).</li>
 	 * </ul>
-	 * 
+	 *
 	 * <p>Examples:</p>
 	 * <ul>
 	 * 	<li><i>"" or NULL</i> = every day at 00:00</li>
@@ -309,7 +311,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * 	<li><i>"M 32 6 30"</i> = every month on the 1st at 00:00, because with 'M' dd must respect the rule: 1 &le; dd &le; 31</li>
 	 * 	<li><i>"M 5 6 30 12"</i> = every month on the 5th at 06:30, because at least 3 parameters are expected and so considered: "12" and eventual other parameters are ignored</li>
 	 * </ul>
-	 * 
+	 *
 	 * @param interval	Interval between two log rotations.
 	 */
 	public final void setLogRotationFreq(final String interval){
@@ -318,12 +320,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * <p>Gets the name of the UWS log file.</p>
-	 * 
+	 *
 	 * <p>By default: {@link #DEFAULT_LOG_FILE_NAME}.</p>
-	 * 
+	 *
 	 * @param level		Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
 	 * @param context	Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
-	 * 
+	 *
 	 * @return	The name of the UWS log file.
 	 */
 	protected String getLogFileName(final LogLevel level, final String context){
@@ -332,12 +334,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Gets the UWS log file.
-	 * 
+	 *
 	 * @param level		Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
 	 * @param context	Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
-	 * 
+	 *
 	 * @return	The UWS log file.
-	 * 
+	 *
 	 * @see #getLogFileName(uws.service.log.UWSLog.LogLevel, String)
 	 */
 	protected File getLogFile(final LogLevel level, final String context){
@@ -419,11 +421,11 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * Create a File instance from the given upload file description.
 	 * This function is able to deal with location as URI and as file path.
-	 * 
+	 *
 	 * @param upload	Description of an uploaded file.
-	 * 
+	 *
 	 * @return	The corresponding File object.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected final File getFile(final UploadFile upload){
@@ -510,18 +512,21 @@ public class LocalUWSFileManager implements UWSFileManager {
 			if (output != null){
 				try{
 					output.close();
-				}catch(IOException ioe){}
+				}catch(IOException ioe){
+				}
 			}
 			if (input != null){
 				try{
 					input.close();
-				}catch(IOException ioe){}
+				}catch(IOException ioe){
+				}
 			}
 			// In case of problem, the copy must be deleted:
 			if (!done && copy.exists()){
 				try{
 					copy.delete();
-				}catch(SecurityException ioe){}
+				}catch(SecurityException ioe){
+				}
 			}
 		}
 	}
@@ -533,12 +538,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 	 * <p>Gets the name of the file in which the given result is/must be written.</p>
 	 * <p>By default: jobID + "_" + resultID + "." + {@link UWSToolBox#getFileExtension(String) getFileExtension(resultMIMEType)}</p>
 	 * <p><i><u>note:</u> there is no file extension if the MIME type of the result is unknown !</i></p>
-	 * 
+	 *
 	 * @param result	The result whose the file name is asked.
 	 * @param job		The job which owns the given result.
-	 * 
+	 *
 	 * @return			Name of the file corresponding to the given result.
-	 * 
+	 *
 	 * @see UWSToolBox#getFileExtension(String)
 	 */
 	protected String getResultFileName(final Result result, final UWSJob job){
@@ -558,12 +563,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Gets the file corresponding to the given result.
-	 * 
+	 *
 	 * @param result	The result whose the file is asked.
 	 * @param job		The job which owns the given result.
-	 * 
+	 *
 	 * @return			The file corresponding to the given result.
-	 * 
+	 *
 	 * @see #getOwnerDirectory(JobOwner)
 	 * @see #getResultFileName(Result, UWSJob)
 	 */
@@ -610,10 +615,10 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * <p>Gets the name of the file in which the described error is/must be written.</p>
 	 * <p>By default: jobID + "_ERROR.log"</p>
-	 * 
+	 *
 	 * @param error		The description of the error whose the file name is asked.
 	 * @param job		The job which owns the given error.
-	 * 
+	 *
 	 * @return			Name of the file corresponding to the described error.
 	 */
 	protected String getErrorFileName(final ErrorSummary error, final UWSJob job){
@@ -622,12 +627,12 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Gets the file corresponding to the described error.
-	 * 
+	 *
 	 * @param error		The error whose the file is asked.
 	 * @param job		The job which owns the given error.
-	 * 
+	 *
 	 * @return			The file corresponding to the described error.
-	 * 
+	 *
 	 * @see #getOwnerDirectory(JobOwner)
 	 * @see #getErrorFileName(ErrorSummary, UWSJob)
 	 */
@@ -674,11 +679,11 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * <p>Gets the name of the backup file of the given job owner (~ UWS user).</p>
 	 * <p>By default: ownerID + ".backup"</p>
-	 * 
+	 *
 	 * @param owner	The job owner whose the name of the backup file is asked.
-	 * 
+	 *
 	 * @return		The name of the backup file of the given owner.
-	 * 
+	 *
 	 * @throws IllegalArgumentException	If the given owner is <i>null</i> or an empty string.
 	 */
 	protected String getBackupFileName(final JobOwner owner) throws IllegalArgumentException{
@@ -708,7 +713,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * <p>Gets the name of the UWS general backup file.</p>
 	 * <p>By default: {@link #DEFAULT_BACKUP_FILE_NAME}</p>
-	 * 
+	 *
 	 * @return		The name of the UWS general backup file.
 	 */
 	protected String getBackupFileName(){
@@ -724,8 +729,9 @@ public class LocalUWSFileManager implements UWSFileManager {
 	@Override
 	public OutputStream getBackupOutput() throws IOException{
 		File backupFile = new File(rootDirectory, getBackupFileName());
+		File tempBackupFile = new File(rootDirectory, getBackupFileName() + ".temp-" + System.currentTimeMillis());
 		createParentDir(backupFile);
-		return new FileOutputStream(backupFile);
+		return new OutputStreamWithCloseAction(new FileOutputStream(tempBackupFile), new RotateFileAction(tempBackupFile, backupFile));
 	}
 
 	/* ************** */
@@ -734,9 +740,9 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Creates the parent directory(ies) if it(they) does/do not exist.
-	 * 
+	 *
 	 * @param f	The file whose the parent directory must exist after the call of this function.
-	 * 
+	 *
 	 * @return	<i>true</i> if the parent directory now exists, <i>false</i> otherwise.
 	 */
 	protected boolean createParentDir(final File f){
@@ -749,7 +755,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 	/**
 	 * Lets iterating on all user backup files.
 	 * The {@link #next()} function creates and returns the {@link InputStream} for the next backup file.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS)
 	 * @version 05/2012
 	 */
@@ -807,7 +813,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 		/**
 		 * If the file whose the input stream must be created and returned does not exist
 		 * or has not the READ permission, <i>null</i> will be returned.
-		 * 
+		 *
 		 * @see java.util.Iterator#next()
 		 */
 		@Override
@@ -836,7 +842,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Filter which lets returning only the directories.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS)
 	 * @version 05/2012
 	 */
@@ -849,7 +855,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 
 	/**
 	 * Filter which lets returning only the backup file(s) of the specified user/owner.
-	 * 
+	 *
 	 * @author Gr&ecaute;gory Mantelet (CDS)
 	 * @version 05/2012
 	 */
@@ -859,7 +865,7 @@ public class LocalUWSFileManager implements UWSFileManager {
 		/**
 		 * Sets the ID of the user whose the backup file must be returned.
 		 * If <i>null</i>, all the found backup files will be returned EXCEPT the backup file for the whole UWS.
-		 * 
+		 *
 		 * @param ownerID	ID of the user whose the backup file must be returned. (MAY BE NULL)
 		 */
 		public void setOwnerID(final String ownerID){
diff --git a/src/uws/service/file/io/CloseAction.java b/src/uws/service/file/io/CloseAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..fca47862c6f0325d73bc7cf3a2803be4092e162f
--- /dev/null
+++ b/src/uws/service/file/io/CloseAction.java
@@ -0,0 +1,44 @@
+package uws.service.file.io;
+
+/*
+ * 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 2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Action(s) executed when the function {@link OutputStreamWithCloseAction#close() close()}
+ * is called on an {@link OutputStreamWithCloseAction} instance, after the
+ * wrapped {@link OutputStream} has been successfully closed.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 4.4 (07/2018)
+ * @since 4.4
+ */
+public interface CloseAction {
+
+	/**
+	 * This function is executed after the wrapped {@link OutputStream} of
+	 * an {@link OutputStreamWithCloseAction} has been successfully closed.
+	 *
+	 * @throws IOException	If any error prevents this {@link CloseAction} to
+	 *                    	run.
+	 */
+	public void run() throws IOException;
+}
\ No newline at end of file
diff --git a/src/uws/service/file/io/OutputStreamWithCloseAction.java b/src/uws/service/file/io/OutputStreamWithCloseAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7e4a084913a35637b224392b840b1e358fea768
--- /dev/null
+++ b/src/uws/service/file/io/OutputStreamWithCloseAction.java
@@ -0,0 +1,102 @@
+package uws.service.file.io;
+
+/*
+ * 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 2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This {@link OutputStream} wraps another {@link OutputStream}. It forwards all
+ * write requests to this inner {@link OutputStream}. The only difference lies
+ * in its {@link #close()} function which runs a given {@link CloseAction} just
+ * after having called {@link OutputStream#close() close()} on the inner
+ * {@link OutputStream} successfully.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 4.4 (07/2018)
+ * @since 4.4
+ *
+ * @see CloseAction
+ */
+public class OutputStreamWithCloseAction extends OutputStream {
+
+	/** Wrapped {@link OutputStream}. */
+	private final OutputStream output;
+
+	/** Action(s) to run after the wrapped {@link OutputStream} has been
+	 * successfully closed.
+	 *
+	 * <p><i>
+	 * 	It can be <code>null</code>. In such case, the wrapped {@link OutputStream}
+	 * 	will be closed and nothing else will be done.
+	 * </i></p> **/
+	private final CloseAction closeAction;
+
+	/**
+	 * Create an {@link OutputStreamWithCloseAction} instance.
+	 *
+	 * @param output	The {@link OutputStream} to wrap.
+	 *              	<i>MANDATORY</i>
+	 * @param action	The action(s) to run after the given {@link OutputStream}
+	 *              	has been successfully closed.
+	 *              	<i>OPTIONAL</i>
+	 *
+	 * @throws NullPointerException	If the given {@link OutputStream} is missing.
+	 */
+	public OutputStreamWithCloseAction(final OutputStream output, final CloseAction action) throws NullPointerException{
+		if (output == null)
+			throw new NullPointerException("Missing OutputStream to wrap!");
+		else
+			this.output = output;
+
+		this.closeAction = action;
+	}
+
+	@Override
+	public void write(final byte[] b) throws IOException{
+		output.write(b);
+	}
+
+	@Override
+	public void write(final byte[] b, final int off, final int len) throws IOException{
+		output.write(b, off, len);
+	}
+
+	@Override
+	public void write(final int b) throws IOException{
+		output.write(b);
+	}
+
+	@Override
+	public void flush() throws IOException{
+		output.flush();
+	}
+
+	@Override
+	public void close() throws IOException{
+		// Close the output stream:
+		output.close();
+
+		// Once close, run the close action:
+		if (closeAction != null)
+			closeAction.run();
+	}
+
+}
\ No newline at end of file
diff --git a/src/uws/service/file/io/RotateFileAction.java b/src/uws/service/file/io/RotateFileAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..1283037831cb6f4bdb6318326b9677af53aefaec
--- /dev/null
+++ b/src/uws/service/file/io/RotateFileAction.java
@@ -0,0 +1,90 @@
+package uws.service.file.io;
+
+/*
+ * 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 2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * This close action rename a given file into another one. The target file is
+ * deleted if it already exists.
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 4.4 (07/2018)
+ * @since 4.4
+ */
+public class RotateFileAction implements CloseAction {
+
+	/** File to rotate (i.e. rename). */
+	private final File sourceFile;
+
+	/** File into which {@link #sourceFile} must be renamed. */
+	private final File targetFile;
+
+	/**
+	 * Create the rotate action.
+	 *
+	 * @param sourceFile	File to rotate.
+	 * @param targetFile	File into which sourceFile must be renamed.
+	 *
+	 * @throws NullPointerException		If any of the given file is missing.
+	 * @throws IllegalArgumentException	If the source file does not exist,
+	 *                                 	or if it is not a regular file,
+	 *                                 	or if the target file exists but is not
+	 *                                 	  a regular file.
+	 */
+	public RotateFileAction(final File sourceFile, final File targetFile) throws NullPointerException, IllegalArgumentException{
+		// Ensure the source file exists and is a regular file:
+		if (sourceFile == null)
+			throw new NullPointerException("Missing source file!");
+		else if (!sourceFile.exists())
+			throw new IllegalArgumentException("The source file \"" + sourceFile.getAbsolutePath() + "\" does not exist!");
+		else if (sourceFile.isDirectory())
+			throw new IllegalArgumentException("The source file \"" + sourceFile.getAbsolutePath() + "\" is a directory instead of a regular file!");
+
+		// Check that if the target file exists, it is a regular file:
+		if (targetFile == null)
+			throw new NullPointerException("Missing target file!");
+		else if (targetFile.exists() && targetFile.isDirectory())
+			throw new IllegalArgumentException("The target file \"" + targetFile.getAbsolutePath() + "\" is a directory instead of a regular file!");
+
+		this.sourceFile = sourceFile;
+		this.targetFile = targetFile;
+	}
+
+	@Override
+	public void run() throws IOException{
+		// Delete the target file if it already exists:
+		try{
+			Files.deleteIfExists(targetFile.toPath());
+		}catch(IOException ioe){
+			throw new IOException("Impossible to perform the file rotation! Cause: the former file can not be deleted.", ioe);
+		}
+
+		// Finally rename the source file into the given target file:
+		try{
+			Files.move(sourceFile.toPath(), targetFile.toPath());
+		}catch(IOException ioe){
+			throw new IOException("Impossible to perform the file rotation! Cause: [" + ioe.getClass() + "] " + ioe.getMessage(), ioe);
+		}
+	}
+
+}
diff --git a/test/uws/service/file/io/TestOutputStreamWithCloseAction.java b/test/uws/service/file/io/TestOutputStreamWithCloseAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba1a1570ec275f86c3e987419481a6904829c4e0
--- /dev/null
+++ b/test/uws/service/file/io/TestOutputStreamWithCloseAction.java
@@ -0,0 +1,103 @@
+package uws.service.file.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestOutputStreamWithCloseAction {
+	final String TMP_DIR = System.getProperty("java.io.tmpdir");
+
+	File srcFile;
+	File destFile;
+
+	@Before
+	public void setUp() throws Exception{
+		final String SUFFIX = "" + System.currentTimeMillis();
+		srcFile = new File(TMP_DIR, "taptest_stream_src_" + SUFFIX);
+		destFile = new File(TMP_DIR, "taptest_stream_dest_" + SUFFIX);
+	}
+
+	@After
+	public void tearDown() throws Exception{
+		if (srcFile != null){
+			srcFile.delete();
+			srcFile = null;
+		}
+		if (destFile != null){
+			destFile.delete();
+			destFile = null;
+		}
+	}
+
+	@Test
+	public void testOutputStreamWithCloseAction(){
+		/* CASE: Missing OutputStream to wrap => ERROR */
+		OutputStreamWithCloseAction out = null;
+		try{
+			out = new OutputStreamWithCloseAction(null, null);
+			fail("This construction should have failed because no OutputStream has been provided!");
+		}catch(Exception ex){
+			assertEquals(NullPointerException.class, ex.getClass());
+			assertEquals("Missing OutputStream to wrap!", ex.getMessage());
+		}finally{
+			if (out != null){
+				try{
+					out.close();
+				}catch(IOException ioe){
+				}
+			}
+		}
+	}
+
+	@Test
+	public void testClose(){
+		OutputStreamWithCloseAction out = null;
+		try{
+			// Open the stream toward the temp file:
+			out = new OutputStreamWithCloseAction(new FileOutputStream(srcFile), new RotateFileAction(srcFile, destFile));
+
+			out.write("File\n".getBytes());
+			out.flush();
+			out.write("successfully written!".getBytes());
+
+			// Close the stream, and so, rotate the files:
+			out.close();
+
+			// Check the files have been rotated:
+			assertFalse(srcFile.exists());
+			assertTrue(destFile.exists());
+
+			// Check the content of the dest file:
+			try(InputStream input = new FileInputStream(destFile)){
+				byte[] buffer = new byte[256];
+				int nbRead = 0;
+				nbRead = input.read(buffer);
+				assertEquals(26, nbRead);
+				assertEquals("File\nsuccessfully written!", new String(buffer, 0, nbRead));
+			}
+
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("Unexpected error! (see the console for more details)");
+		}finally{
+			if (out != null){
+				try{
+					out.close();
+				}catch(IOException ioe){
+				}
+			}
+		}
+	}
+
+}
diff --git a/test/uws/service/file/io/TestRotateFileAction.java b/test/uws/service/file/io/TestRotateFileAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..abec8ca3bea9140c7e05d97bb801ebffc38598fb
--- /dev/null
+++ b/test/uws/service/file/io/TestRotateFileAction.java
@@ -0,0 +1,161 @@
+package uws.service.file.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestRotateFileAction {
+
+	final String TMP_DIR = System.getProperty("java.io.tmpdir");
+
+	File srcFile;
+	File destFile;
+
+	@Before
+	public void setUp() throws Exception{
+		final String SUFFIX = "" + System.currentTimeMillis();
+		srcFile = new File(TMP_DIR, "taptest_rotate_src_" + SUFFIX);
+		destFile = new File(TMP_DIR, "taptest_rotate_dest_" + SUFFIX);
+	}
+
+	@After
+	public void tearDown() throws Exception{
+		if (srcFile != null){
+			srcFile.delete();
+			srcFile = null;
+		}
+		if (destFile != null){
+			destFile.delete();
+			destFile = null;
+		}
+	}
+
+	@Test
+	public void testConstructor(){
+		/* CASE: No source file => ERROR */
+		try{
+			new RotateFileAction(null, null);
+			fail("This construction should have failed because no source file has been provided!");
+		}catch(Exception ex){
+			assertEquals(NullPointerException.class, ex.getClass());
+			assertEquals("Missing source file!", ex.getMessage());
+		}
+		/* CASE: The source file does not exist => ERROR */
+		try{
+			assertFalse(srcFile.exists());
+			new RotateFileAction(srcFile, null);
+			fail("This construction should have failed because the source file does not exist.");
+		}catch(Exception ex){
+			assertEquals(IllegalArgumentException.class, ex.getClass());
+			assertEquals("The source file \"" + srcFile.getAbsolutePath() + "\" does not exist!", ex.getMessage());
+		}
+		/* CASE: The source file is a directory => ERROR */
+		try{
+			assertTrue(srcFile.mkdir());
+			new RotateFileAction(srcFile, null);
+			fail("This construction should have failed because the source file is a directory!");
+		}catch(Exception ex){
+			assertEquals(IllegalArgumentException.class, ex.getClass());
+			assertEquals("The source file \"" + srcFile.getAbsolutePath() + "\" is a directory instead of a regular file!", ex.getMessage());
+		}finally{
+			srcFile.delete();
+		}
+		/* CASE: No dest file => ERROR */
+		try{
+			assertTrue(srcFile.createNewFile());
+			new RotateFileAction(srcFile, null);
+			fail("This construction should have failed because the dest file is missing!");
+		}catch(Exception ex){
+			assertEquals(NullPointerException.class, ex.getClass());
+			assertEquals("Missing target file!", ex.getMessage());
+		}finally{
+			srcFile.delete();
+		}
+		/* CASE: the dest file exists and is a directory => ERROR */
+		try{
+			assertTrue(srcFile.createNewFile());
+			assertTrue(destFile.mkdir());
+			new RotateFileAction(srcFile, destFile);
+			fail("This construction should have failed because the dest file is a directory!");
+		}catch(Exception ex){
+			assertEquals(IllegalArgumentException.class, ex.getClass());
+			assertEquals("The target file \"" + destFile.getAbsolutePath() + "\" is a directory instead of a regular file!", ex.getMessage());
+		}finally{
+			srcFile.delete();
+			destFile.delete();
+		}
+		/* CASE: Everything is OK! */
+		try{
+			assertTrue(srcFile.createNewFile());
+			assertFalse(destFile.exists());
+			new RotateFileAction(srcFile, destFile);
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("Unexpected error while construction! (see console for more details)");
+		}
+	}
+
+	@Test
+	public void testRun(){
+		/* CASE: Destination does not exist yet */
+		try{
+			// Create a source file:
+			assertTrue(srcFile.createNewFile());
+
+			// Ensure the dest file does exist yet:
+			assertFalse(destFile.exists());
+
+			// Run the action:
+			CloseAction action = new RotateFileAction(srcFile, destFile);
+			action.run();
+
+			/* Check the source file does not exist anymore
+			 * BUT the dest one yes: */
+			assertFalse(srcFile.exists());
+			assertTrue(destFile.exists());
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("Unexpected IO error! (see console for more details)");
+		}finally{
+			srcFile.delete();
+			destFile.delete();
+		}
+
+		/* CASE: Destination already exists */
+		try{
+			// Create a source file:
+			assertTrue(srcFile.createNewFile());
+
+			// Sleep for 1 second (so that the last modified date can be different):
+			Thread.sleep(1000);
+
+			// Already create the dest:
+			assertTrue(destFile.createNewFile());
+			long lastModified = destFile.lastModified();
+
+			// Run the action:
+			CloseAction action = new RotateFileAction(srcFile, destFile);
+			action.run();
+
+			/* Check the source file does not exist anymore
+			 * AND the destination file does still exist
+			 * BUT with a different lastModified date: */
+			assertFalse(srcFile.exists());
+			assertTrue(destFile.exists());
+			assertNotEquals(lastModified, destFile.lastModified());
+
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("Unexpected IO error! (see console for more details)");
+		}
+	}
+
+}