I am trying to write a table sorter that will -always- sort null values to the bottom. So I have written a "wrapper" class that implements Comparable:
public class WrappedBigDecimal implements Comparable<WrappedBigDecimal> {
    public final BigDecimal value;
    public WrappedBigDecimal(BigDecimal value) {
        this.value = value;
    }
    @Override
    public int compareTo(WrappedBigDecimal o) {
        if (value == null && (o == null || o.value == null)) { // both are null, thus equal
            return 0;
        } else if (value == null && (o != null && o.value != null)) { // value is null and compared value isn't
            return -1;
        } else if (value != null && (o == null || o.value == null)) {
            return 1;
        } else {
            return value.compareTo(o.value);
        }
    }
    @Override
    public String toString() {
        return String.valueOf(value);
    }
}
You'll notice that the compareTo method only does null checks and then defers to the compareTo method of the wrapped value's class.
Then I wrote a RowSorter whose Comparator checks for the SortOrder
public class WrappedNumberSorter extends TableRowSorter<TableModel> {
    public WrappedNumberSorter(TableModel model) {
        super(model);
    }
    @Override
    public Comparator<?> getComparator(final int column) {
        Comparator c = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                boolean ascending = getSortKeys().get(0).getSortOrder() == SortOrder.ASCENDING;
                if (o1 instanceof WrappedBigDecimal && ((WrappedBigDecimal)o1).value == null) {
                    if(ascending)
                        return 1;
                    else
                        return -1;
                } else if (o2 instanceof WrappedBigDecimal && ((WrappedBigDecimal)o2).value == null) {
                    if(ascending)
                        return -1;
                    else
                        return 1;
                } else {
                    return ((Comparable<Object>) o1).compareTo(o2);
                }
            }
        };
        return c;
    }
}  
But this throws an error (can't figure out why, can't reproduce -- read on):
java.lang.IllegalArgumentException: Comparison method violates its general contract!
Luckily, the error doesn't seem to affect anything because everything is working as intended.
I saw this question (java.lang.IllegalArgumentException: Comparison method violates its general contract) and I THINK my problem is that my comparison is not transitive. Although I'm not sure because if A == B and B == C then I think mine would also return A == C but I'm getting spun around trying to think about it.
Anyways, my questions are:
- Is there a potential danger to intentionally making the row sorter sort nulls to the bottom in this way?
 - This SEEMS transitive to me. Am I, indeed, violating the transitive contract of 
compareTohere? Or is there another contract that it must adhere to and I'm unaware and that's what throws the error? 
I wrote a method to test my row sorter and I am unable to reproduce the error:
public static void main(String[] args) {
    JFrame frame = new JFrame();
    JTable table = new JTable();
    JScrollPane jsp = new JScrollPane(table);
    String[] headers = new String[] {"h1", "h2"};
    Object[][] data = new Object[10][2];
    for(int i = 0; i < 7; i++) {
        data[i][0] = new WrappedBigDecimal(BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i))));
    }
    data[7][0] = new WrappedBigDecimal(null);
    data[8][0] = new WrappedBigDecimal(null);
    data[9][0] = new WrappedBigDecimal(null);
    for(int i = 10; i < 17; i++) {
        data[i-10][1] = new WrappedBigDecimal(BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i))));
    }
    data[7][1] = new WrappedBigDecimal(null);
    data[8][1] = new WrappedBigDecimal(null);
    data[9][1] = new WrappedBigDecimal(null);
    table.setModel(new DefaultTableModel(data, headers) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return BigDecimal.class;
        }
    });
    table.setRowSorter(new WrappedNumberSorter(table.getModel()));
    frame.add(jsp);
    frame.setSize(200,400);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
Everything seems to be working smoothly.
What am I missing?
EDIT (7/11/18): Attempting to get rid of the WrappedBigDecimal solution and implement Matt McHenry's solution presents another problem. I simplified the RowSorter to this:
static class NullsLastSorter extends TableRowSorter<TableModel> {
    public NullsLastSorter(TableModel model) {
        super(model);
    }
    @Override
    public Comparator<?> getComparator(int column) {
        return Comparator.<Optional<BigDecimal>, Boolean>comparing(Optional::isPresent).reversed().thenComparing(o -> o.orElse(BigDecimal.ONE));
    }
}
And tested it with:
public static void main(String[] args) {
    JFrame frame = new JFrame();
    JTable table = new JTable();
    JScrollPane jsp = new JScrollPane(table);
    String[] headers = new String[] {"h1", "h2"};
    Object[][] data = new Object[10][2];
    for(int i = 0; i < 7; i++) {
        data[i][0] = BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i)));
    }
    data[7][0] = null;
    data[8][0] = null;
    data[9][0] = null;
    for(int i = 10; i < 17; i++) {
        data[i-10][1] = BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i)));
    }
    data[7][1] = null;
    data[8][1] = null;
    data[9][1] = null;
    table.setModel(new DefaultTableModel(data, headers) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return BigDecimal.class;
        }
    });
    table.setRowSorter(new NullsLastSorter(table.getModel()));
    frame.add(jsp);
    frame.setSize(200,400);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
And now when I try to sort I get an error:
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.util.Optional
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.util.Collections$ReverseComparator2.compare(Collections.java:5178)
at java.util.Comparator.lambda$thenComparing$36697e65$1(Comparator.java:216)
at javax.swing.DefaultRowSorter.compare(DefaultRowSorter.java:968)
at javax.swing.DefaultRowSorter.access$100(DefaultRowSorter.java:112)
at javax.swing.DefaultRowSorter$Row.compareTo(DefaultRowSorter.java:1376)
at javax.swing.DefaultRowSorter$Row.compareTo(DefaultRowSorter.java:1366)
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
at java.util.Arrays.sort(Arrays.java:1246)
at javax.swing.DefaultRowSorter.sort(DefaultRowSorter.java:607)
at javax.swing.DefaultRowSorter.setSortKeys(DefaultRowSorter.java:319)
at javax.swing.DefaultRowSorter.toggleSortOrder(DefaultRowSorter.java:480)
at javax.swing.plaf.basic.BasicTableHeaderUI$MouseInputHandler.mouseClicked(BasicTableHeaderUI.java:112)
at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:270)
at java.awt.Component.processMouseEvent(Component.java:6536)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6298)
at java.awt.Container.processEvent(Container.java:2236)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2294)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4534)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466)
at java.awt.Container.dispatchEventImpl(Container.java:2280)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.awt.EventQueue$4.run(EventQueue.java:729)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
