diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java
index ccee023a839175f51337f6b4ab23856b0af9c106..cf3f7248774223213beabf2e250f5255c8f2fb74 100644
--- a/src/tap/formatter/VOTableFormat.java
+++ b/src/tap/formatter/VOTableFormat.java
@@ -2,21 +2,21 @@ package tap.formatter;
 
 /*
  * This file is part of TAPLibrary.
- * 
+ *
  * TAPLibrary 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.
- * 
+ *
  * TAPLibrary 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 TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ *
+ * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -29,6 +29,9 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 
+import adql.db.DBColumn;
+import adql.db.DBType;
+import adql.db.DBType.DBDatatype;
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
@@ -44,24 +47,22 @@ import uk.ac.starlink.table.ColumnInfo;
 import uk.ac.starlink.table.DescribedValue;
 import uk.ac.starlink.table.RowSequence;
 import uk.ac.starlink.table.StarTable;
+import uk.ac.starlink.table.StoragePolicy;
 import uk.ac.starlink.votable.DataFormat;
 import uk.ac.starlink.votable.VOSerializer;
 import uk.ac.starlink.votable.VOStarTable;
 import uk.ac.starlink.votable.VOTableVersion;
-import adql.db.DBColumn;
-import adql.db.DBType;
-import adql.db.DBType.DBDatatype;
 
 /**
  * <p>Format any given query (table) result into VOTable.</p>
- * 
+ *
  * <p>
  * 	Format and version of the resulting VOTable can be provided in parameters at the construction time.
  * 	This formatter is using STIL. So all formats and versions managed by STIL are also here.
  * 	Basically, you have the following formats: TABLEDATA, BINARY, BINARY2 (only when using VOTable v1.3) and FITS.
  * 	The versions are: 1.0, 1.1, 1.2 and 1.3.
  * </p>
- * 
+ *
  * <p>Note: The MIME type is automatically set in function of the given VOTable serialization:</p>
  * <ul>
  * 	<li><b>none or unknown</b>: equivalent to BINARY</li>
@@ -71,21 +72,21 @@ import adql.db.DBType.DBDatatype;
  * 	<li><b>FITS</b>:            "application/x-votable+xml;serialization=FITS" = "votable/fits"</li>
  * </ul>
  * <p>It is however possible to change these default values thanks to {@link #setMimeType(String, String)}.</p>
- * 
+ *
  * <p>In addition of the INFO elements for QUERY_STATUS="OK" and QUERY_STATUS="OVERFLOW", two additional INFO elements are written:</p>
  * <ul>
  * 	<li>PROVIDER = {@link ServiceConnection#getProviderName()} and {@link ServiceConnection#getProviderDescription()}</li>
  * 	<li>QUERY = the ADQL query at the origin of this result.</li>
  * </ul>
- * 
+ *
  * <p>
  * 	Furthermore, this formatter provides a function to format an error in VOTable: {@link #writeError(String, Map, PrintWriter)}.
  * 	This is useful for TAP which requires to return in VOTable any error that occurs while any operation.
  * 	<i>See {@link DefaultTAPErrorWriter} for more details.</i>
  * </p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (07/2017)
+ * @version 2.2 (03/2019)
  */
 public class VOTableFormat implements OutputFormat {
 
@@ -106,14 +107,14 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <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).
-	 * 
+	 *
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service) throws NullPointerException{
@@ -122,7 +123,7 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <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>
@@ -132,10 +133,10 @@ public class VOTableFormat implements OutputFormat {
 	 * 	<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).
-	 * 
+	 *
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException{
@@ -144,7 +145,7 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <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>
@@ -154,11 +155,11 @@ public class VOTableFormat implements OutputFormat {
 	 * 	<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.
-	 * 
+	 *
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
 	public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException{
@@ -202,9 +203,9 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <p>Set the MIME type associated with this format.</p>
-	 * 
+	 *
 	 * <p><i>Note: NULL means no modification of the current value:</i></p>
-	 * 
+	 *
 	 * @param mimeType	Full MIME type of this VOTable format.	<i>note: if NULL, the MIME type is not modified.</i>
 	 * @param shortForm	Short form of this MIME type. <i>note: if NULL, the short MIME type is not modified.</i>
 	 */
@@ -217,7 +218,7 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Get the set VOTable data serialization/format (e.g. BINARY, TABLEDATA).
-	 * 
+	 *
 	 * @return	The data format.
 	 */
 	public final DataFormat getVotSerialization(){
@@ -226,7 +227,7 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Get the set VOTable version.
-	 * 
+	 *
 	 * @return	The VOTable version.
 	 */
 	public final VOTableVersion getVotVersion(){
@@ -245,13 +246,13 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <p>Write the given error message as VOTable document.</p>
-	 * 
+	 *
 	 * <p><i>Note:
 	 * 	In the TAP protocol, all errors must be returned as VOTable. The class {@link DefaultTAPErrorWriter} is in charge of the management
 	 * 	and reporting of all errors. It is calling this function while the error message to display to the user is ready and
 	 * 	must be written in the HTTP response.
 	 * </i></p>
-	 * 
+	 *
 	 * <p>Here is the XML format of this VOTable error:</p>
 	 * <pre>
 	 * 	&lt;VOTABLE version="..." xmlns="..." &gt;
@@ -264,16 +265,16 @@ public class VOTableFormat implements OutputFormat {
 	 * 		&lt;/RESOURCE&gt;
 	 * 	&lt;/VOTABLE&gt;
 	 * </pre>
-	 * 
+	 *
 	 * @param message	Error message to display to the user.
 	 * @param otherInfo	List of other additional information to display. <i>optional</i>
 	 * @param writer	Stream in which the VOTable error must be written.
-	 * 
+	 *
 	 * @throws IOException	If any error occurs while writing in the given output.
-	 * 
+	 *
 	 * @since 2.0
 	 */
-	public void writeError(final String message, final Map<String,String> otherInfo, final PrintWriter writer) throws IOException{
+	public void writeError(final String message, final Map<String, String> otherInfo, final PrintWriter writer) throws IOException{
 		BufferedWriter out = new BufferedWriter(writer);
 
 		// Set the root VOTABLE node:
@@ -298,9 +299,9 @@ public class VOTableFormat implements OutputFormat {
 
 		// Append the ADQL query at the origin of this result:	[OPTIONAL]
 		if (otherInfo != null){
-			Iterator<Map.Entry<String,String>> it = otherInfo.entrySet().iterator();
+			Iterator<Map.Entry<String, String>> it = otherInfo.entrySet().iterator();
 			while(it.hasNext()){
-				Map.Entry<String,String> entry = it.next();
+				Map.Entry<String, String> entry = it.next();
 				if (entry.getValue() != null){
 					if (entry.getValue().startsWith("\n")){
 						int sep = entry.getValue().substring(1).indexOf('\n');
@@ -333,10 +334,18 @@ public class VOTableFormat implements OutputFormat {
 
 		/* Turns the result set into a table. */
 		LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec(), thread);
-		table.setName("result_"+execReport.jobID);
+		table.setName("result_" + execReport.jobID);
 
 		/* Prepares the object that will do the serialization work. */
-		VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
+		VOSerializer voser = null;
+		/* if FITS, copy the table on disk (or in memory if the table is short):
+		 * (note: this is needed because STIL needs at least 2 passes on this
+		 *        table to format it correctly in FITS format) */
+		if (votFormat == DataFormat.FITS)
+			voser = VOSerializer.makeSerializer(votFormat, votVersion, StoragePolicy.PREFER_DISK.copyTable(table));
+		// otherwise, just use the default VOTable serializer:
+		else
+			voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
 		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output));
 
 		/* Write header. */
@@ -370,11 +379,11 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * <p>Writes the first VOTable nodes/elements preceding the data: VOTABLE, RESOURCE and 3 INFOS (QUERY_STATUS, PROVIDER, QUERY).</p>
-	 * 
+	 *
 	 * @param votVersion	Target VOTable version.
 	 * @param execReport	The report of the query execution.
 	 * @param out			Writer in which the root node must be written.
-	 * 
+	 *
 	 * @throws IOException	If there is an error while writing the root node in the given Writer.
 	 * @throws TAPException	If there is any other error (by default: never happen).
 	 */
@@ -405,7 +414,7 @@ public class VOTableFormat implements OutputFormat {
 			out.write("<INFO name=\"QUERY\"" + VOSerializer.formatAttribute("value", adqlQuery) + "/>");
 			out.newLine();
 		}
-		
+
 		// Insert the definition of all used coordinate systems:
 		HashSet<String> insertedCoosys = new HashSet<String>(10);
 		for(DBColumn col : execReport.resultingColumns){
@@ -416,7 +425,7 @@ public class VOTableFormat implements OutputFormat {
 				// insert the coosys definition ONLY if not already done because of another column:
 				if (!insertedCoosys.contains(coosys.getId())){
 					// write the VOTable serialization of this coordinate system definition:
-					out.write("<COOSYS"+VOSerializer.formatAttribute("ID", coosys.getId()));
+					out.write("<COOSYS" + VOSerializer.formatAttribute("ID", coosys.getId()));
 					if (coosys.getSystem() != null)
 						out.write(VOSerializer.formatAttribute("system", coosys.getSystem()));
 					if (coosys.getEquinox() != null)
@@ -436,13 +445,13 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Writes fields' metadata of the given query result.
-	 * 
+	 *
 	 * @param result		The query result from whose fields' metadata must be written.
 	 * @param execReport	The report of the query execution.
 	 * @param thread		The thread which asked for the result writing.
-	 * 
+	 *
 	 * @return				Extracted field's metadata, or NULL if no metadata have been found (theoretically, it never happens).
-	 * 
+	 *
 	 * @throws IOException				If there is an error while writing the metadata.
 	 * @throws TAPException				If there is any other error.
 	 * @throws InterruptedException		If the given thread has been interrupted.
@@ -480,10 +489,10 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result.
-	 * 
+	 *
 	 * @param typeFromQuery		Metadata extracted/guessed from the ADQL query.
 	 * @param typeFromResult	Metadata extracted/guessed from the result.
-	 * 
+	 *
 	 * @return	The most appropriate metadata.
 	 */
 	protected static final TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){
@@ -503,9 +512,9 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Convert the given {@link TAPColumn} object into a {@link ColumnInfo} object.
-	 * 
+	 *
 	 * @param tapCol	{@link TAPColumn} to convert into {@link ColumnInfo}.
-	 * 
+	 *
 	 * @return	The corresponding {@link ColumnInfo}.
 	 */
 	protected static final ColumnInfo getColumnInfo(final TAPColumn tapCol){
@@ -529,7 +538,7 @@ public class VOTableFormat implements OutputFormat {
 		colInfo.setUnitString(tapCol.getUnit());
 		colInfo.setUCD(tapCol.getUcd());
 		colInfo.setUtype(tapCol.getUtype());
-		
+
 		// Set the coosys ref (if any):
 		if (tapCol.getCoosys() != null)
 			colInfo.setAuxDatum(new DescribedValue(VOStarTable.REF_INFO, tapCol.getCoosys().getId()));
@@ -539,17 +548,17 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Convert the VOTable datatype string into a corresponding {@link Class} object.
-	 * 
-	 * @param datatype	Value of the VOTable attribute "datatype". 
+	 *
+	 * @param datatype	Value of the VOTable attribute "datatype".
 	 * @param arraysize	Value of the VOTable attribute "arraysize".
-	 * 
+	 *
 	 * @return	The corresponding {@link Class} object.
 	 */
 	protected static final Class<?> getDatatypeClass(final VotDatatype datatype, final String arraysize){
 		// Determine whether this type is an array or not:
 		boolean isScalar = arraysize == null || (arraysize.length() == 1 && arraysize.equals("1"));
 
-		// Guess the corresponding Class object (see section "7.1.4 Data Types" of the STIL documentation): 
+		// Guess the corresponding Class object (see section "7.1.4 Data Types" of the STIL documentation):
 		switch(datatype){
 			case BIT:
 				return boolean[].class;
@@ -580,9 +589,9 @@ public class VOTableFormat implements OutputFormat {
 
 	/**
 	 * Convert the given VOTable arraysize into a {@link ColumnInfo} shape.
-	 * 
+	 *
 	 * @param arraysize	Value of the VOTable attribute "arraysize".
-	 * 
+	 *
 	 * @return	The corresponding {@link ColumnInfo} shape.
 	 */
 	protected static final int[] getShape(final String arraysize){
@@ -597,12 +606,12 @@ public class VOTableFormat implements OutputFormat {
 
 		// '*' or 'n*' => {-1}:
 		else if (arraysize.charAt(arraysize.length() - 1) == '*')
-			return new int[]{-1};
+			return new int[]{ -1 };
 
 		// 'n' => {n}:
 		else{
 			try{
-				return new int[]{Integer.parseInt(arraysize)};
+				return new int[]{ Integer.parseInt(arraysize) };
 			}catch(NumberFormatException nfe){
 				// if the given arraysize is incorrect (theoretically, never happens), it is like no arraysize has been provided:
 				return new int[0];
@@ -615,7 +624,7 @@ public class VOTableFormat implements OutputFormat {
 	 * 	Special {@link StarTable} able to read a fixed maximum number of rows {@link TableIterator}.
 	 * 	However, if no limit is provided, all rows are read.
 	 * </p>
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 	 * @version 2.1 (11/2015)
 	 * @since 2.0
@@ -649,7 +658,7 @@ public class VOTableFormat implements OutputFormat {
 
 		/**
 		 * Build this special {@link StarTable}.
-		 * 
+		 *
 		 * @param tableIt	Data on which to iterate using this special {@link StarTable}.
 		 * @param colInfos	Information about all columns.
 		 * @param maxrec	Limit on the number of rows to read. <i>(if negative, there will be no limit)</i>
@@ -703,7 +712,7 @@ public class VOTableFormat implements OutputFormat {
 			overflow = false;
 			row = new Object[nbCol];
 
-			return new RowSequence(){
+			return new RowSequence() {
 				long irow = -1;
 
 				@Override
@@ -743,7 +752,8 @@ public class VOTableFormat implements OutputFormat {
 				}
 
 				@Override
-				public void close() throws IOException{}
+				public void close() throws IOException{
+				}
 			};
 		}
 	}