This seems like the kind of issue that's for your support vendor if you are stuck. I will remind people (when ever needed) that this is the list for discussing fixes.
It is not a support channel. We have no obligation or cycles for that.
I know you are hoping to get lucky but work wins out over lucky a lot.

I know the words Drag and Drop but that's it so the rest take how you will.
- I think only you can debug what happens on your machine.
- I think (my guess really) is the problem is more likely on the receiving (browser) end than the sending end, but that's not a certainty.

Speaking generically, there's a negotiation of sorts over what can be accepted and perhaps you can use your own instrumented JDK build to understand what is going on.


-phil.


On 5/6/26 5:45 AM, David Alayachew wrote:
Hello @[email protected] <[email protected]>,

Me and a couple of Swing/AWT experts on StackOverflow have been stumped by
the following behaviour. Here is the StackOverflow post --
https://stackoverflow.com/questions/79934904/

Long story short, I am trying to modify the generated content when
copy-pasting from a JTable, but the resulting copied content appears
malformed on my end, but works fine on the other StackOverflow commentors
machines.

Consider the following code.

// Source - https://stackoverflow.com/q/79934904
// Posted by davidalayachew
// Retrieved 2026-05-06, License - CC BY-SA 4.0

import module java.base;
import module java.desktop;

void main()
{

     SwingUtilities
         .invokeLater
         (
             () ->
             {

                 JOptionPane
                     .showMessageDialog
                     (
                         null,
                         new JTable
                         (
                             new Object[][]
                             {
                                 new Object[]{1, 2, 3},
                                 new Object[]{4, 5, 6},
                                 new Object[]{7777, 88888, 99999}
                             },
                             new Object[]{"abc", "xyz", "tuv"}

                         )
                     )
                     ;

             }
         )
         ;

}

It's a simple JTable, and copy-pasting from it will simply generate a
rendered HTML Table that will show up properly on the appropriate editor,
whether Gmail, Google Docs, etc. Of course, it will show up as
tab-separated text if pasted anywhere else.

Now, consider the following code instead.

// Source - https://stackoverflow.com/a/79935559
// Posted by VGR
// Retrieved 2026-05-06, License - CC BY-SA 4.0

import java.io.Serial;

import java.util.Formatter;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.IntStream;

import java.awt.EventQueue;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;

import javax.swing.TransferHandler;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.table.TableModel;

public class CustomHTMLTransferHandler
extends TransferHandler {
     @Serial
     private static final long serialVersionUID = 1;

     @Override
     public boolean canImport(TransferSupport support) {
         return false;
     }

     @Override
     public void exportToClipboard(JComponent c,
                                   Clipboard clipboard,
                                   int action) {

         if (action != COPY && action != COPY_OR_MOVE) {
             return;
         }

         clipboard.setContents(createTransferable(c), null);
     }

     @Override
     public Transferable createTransferable(JComponent c) {
         if (!(c instanceof JTable)) {
             throw new IllegalArgumentException(
                 "This class can only handle export of data from a JTable.");
         }

         return new Transferable() {
             @Override
             public boolean isDataFlavorSupported(DataFlavor flavor) {
                 return flavor.equals(DataFlavor.stringFlavor) ||
                        flavor.equals(DataFlavor.fragmentHtmlFlavor) ||
                        flavor.equals(DataFlavor.allHtmlFlavor);
             }

             @Override
             public DataFlavor[] getTransferDataFlavors() {
                 return new DataFlavor[] {
                     DataFlavor.stringFlavor,
                     DataFlavor.fragmentHtmlFlavor,
                     DataFlavor.allHtmlFlavor
                 };
             }

             @Override
             public Object getTransferData(DataFlavor flavor)
             throws UnsupportedFlavorException {
                 JTable table = (JTable) c;
                 TableModel model = table.getModel();
                 int columns = model.getColumnCount();
                 IntStream rows;
                 int[] selectedRows = table.getSelectedRows();
                 if (selectedRows.length > 0) {
                     Arrays.sort(selectedRows);
                     rows = Arrays.stream(selectedRows)
                         .map(table::convertRowIndexToModel);
                 } else {
                     rows = IntStream.range(0, model.getRowCount());
                 }

                 if (flavor.equals(DataFlavor.stringFlavor)) {
                     StringBuilder tsv = new StringBuilder();
                     rows.forEach(r -> {
                         for (int c = 0; c < columns; c++) {
                             if (c > 0) {
                                 tsv.append('\t');
                             }
                             Object value = model.getValueAt(r, c);
                             String text = Objects.toString(value, "");
                             tsv.append(text.replace('\t', ' '));
                         }
                         tsv.append(System.lineSeparator());
                     });
                     return tsv.toString();
                 } else if (flavor.equals(DataFlavor.fragmentHtmlFlavor) ||
                            flavor.equals(DataFlavor.allHtmlFlavor)) {

                     Formatter html = new Formatter();
                     html.format("<table frame='border' rules='all'>\n");
                     rows.forEach(r -> {
                         html.format("<tr>");
                         for (int c = 0; c < columns; c++) {
                             html.format("<td>");
                             Object value = model.getValueAt(r, c);
                             String text = Objects.toString(value, "");
                             text.codePoints().forEach(p -> {
                                 html.format(
                                     p >= 32 && p < 127 && p != '<' && p !=
'&'
                                     ? "%c" : "&#%d;", p);
                             });
                         }
                         html.format("\n");
                     });
                     html.format("</table>");
                     return html.toString();
                 } else {
                     throw new UnsupportedFlavorException(flavor);
                 }
             }
         };
     }

     public static void main(String[] args) {
         EventQueue.invokeLater(() -> {
             JTable table = new JTable(
                 new Object[][] {
                     {1, 2, 3},
                     {4, 5, 6},
                     {7777, 88888, 99999}
                 },
                 new Object[] {"abc", "xyz", "tuv"}
             );
             table.setTransferHandler(new CustomHTMLTransferHandler());
             JOptionPane.showMessageDialog(null, new JScrollPane(table));
         });
     }
}

This code should allow us to modify the copied contents of the JTable to be
what we want. And it seems to do exactly that for everyone else except me.
And again, I downloaded a fresh JDK 26.0.1, ran the code, and still it
doesn't work. I am on Windows 11 and my browser is Firefox 150.0.1.

When I paste the above code into something like Google Docs or anything
else that should be able to receive a  rendered HTML table, it instead
demotes to a basic tab-separated list of rows. Basically, demoting from
text/html to text/plain.

For the life of me, we can't figure out why. And this seems to be only on
my machine and no one else's. I am doing nothing weird here, so I am lost
on what exactly might be wrong here on my side. I am starting to wonder if
this is some portability issue? We are running the exact same Java code but
getting different results.

Thank you for your time and consideration.
David Alayachew

Reply via email to