This is one of those questions that makes me glad I purchased Robinson & Vorobiev's book on Swing.
Anything that accesses the state of a java.awt.Component
should be run inside the EDT, with three exceptions: anything specifically documented as thread-safe, such as repaint()
, revalidate()
, and invalidate()
; any Component in a UI that has not yet been realized; and any Component in an Applet before that Applet's start()
has been called.
Methods specially made thread-safe are so uncommon that it's often sufficient to simply remember the ones that are; you can also usually get away with assuming there are no such methods (it's perfectly safe to wrap a repaint call in a SwingWorker, for example).
Realized means that the Component is either a top-level container (like JFrame) on which any of setVisible(true)
, show()
, or pack()
has been called, or it has been added to a realized Component. This means it's perfectly fine to build your UI in the main() method, as many tutorial examples do, since they don't call setVisible(true)
on the top-level container until every Component has been added to it, fonts and borders configured, etc.
For similar reasons, it's perfectly safe to build your applet UI in its init()
method, and then call start()
after it's all built.
Wrapping subsequent Component changes in Runnables to send to invokeLater()
becomes easy to get right after doing it only a few times. The one thing I find annoying is reading the state of a Component (say, someTextField.getText()
) from another thread. Technically, this has to be wrapped in invokeLater()
, too; in practice, it can make the code ugly fast, and I often don't bother, or I'm careful to grab that information at initial event handling time (typically the right time to do it in most cases anyway).