Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Cross-Platform Consistency: Swing Themes in Practice

Posted on May 15, 2011 By Luis Fernandez

Everyone building desktop apps right now is juggling the same thing: we promise a cross platform story with Java Swing, then ship, and the screenshots from Windows 7, Snow Leopard, and fresh Ubuntu 11.04 look like cousins that barely talk to each other. Oracle now stewards Java, Apple has its own Java 6 build, Ubuntu just dropped Unity, and teams are asking what to do with Swing themes to keep a single feel without poking the bear on each platform. This is a field note from the trenches on Swing Look and Feel choices, what bites, and what stays solid across machines.

Definitions that matter

Look and Feel in Swing is a pluggable theme system. It decides colors, borders, default sizes, painters, and UI delegates for core widgets like JButton or JTable. You switch it with UIManager, either to a system theme or a cross platform theme. Swing ships with a few: Metal, Nimbus, Windows, GTK, and the old Motif. There are third party options like Substance, Synthetica, JGoodies Looks, and Quaqua for a closer Mac feel.

System Look and Feel tries to mimic the host system. On Windows it maps to Windows, on GNOME or Unity it maps to GTK, and on Mac OS X it maps to Aqua through the Apple stack. It buys you familiarity but hands you three different worlds. Cross platform Look and Feel like Nimbus ignore the host and try to look the same everywhere. That buys you consistency and control, at the cost of not blending with the desktop chrome.

UIDefaults and UIResource are the knobs and plumbing. You can override fonts, paddings, painters, and colors by key. Synth is the theming engine under Nimbus with XML based configuration, so you can skin widgets without subclassing them.

Examples from real projects

Let us start with the bedrock. Set the Look and Feel before any component is created. Do it early in main, and do not mix and match later. Here is a compact boot block that picks Nimbus, falls back to system, and then to Metal as a last resort.

public final class Boot {
    public static void main(String[] args) {
        // Choose a consistent LAF early
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
            // If Nimbus was not present, try system
            if (!(UIManager.getLookAndFeel() instanceof javax.swing.plaf.nimbus.NimbusLookAndFeel)) {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }
        } catch (Exception nimbusMissed) {
            try {
                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
            } catch (Exception ignore) { /* last fallback already set by Swing */ }
        }

        // Always start on the EDT
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame f = new JFrame("Cross Platform Demo");
                f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                f.setContentPane(build());
                f.pack();
                f.setLocationByPlatform(true);
                f.setVisible(true);
            }
        });
    }

    private static JComponent build() {
        JPanel p = new JPanel(new BorderLayout(12, 12));
        p.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
        JButton ok = new JButton("OK");
        JButton cancel = new JButton("Cancel");
        JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        south.add(ok);
        south.add(cancel);

        JTable table = new JTable(10, 3);
        p.add(new JScrollPane(table), BorderLayout.CENTER);
        p.add(south, BorderLayout.SOUTH);
        return p;
    }
}

Now apply a few UIDefaults tweaks to unify spacing and font sizes across platforms. Instead of size math in every panel, push consistent values into the theme.

// Do this before creating any components
UIDefaults d = UIManager.getLookAndFeelDefaults();
d.put("defaultFont", new Font("SansSerif", Font.PLAIN, 13));
d.put("Button.contentMargins", new Insets(6, 12, 6, 12));
d.put("TextField.margin", new Insets(4, 6, 4, 6));
d.put("Table.rowHeight", 22);

// Example color tweak for Nimbus keys
d.put("nimbusBase", new Color(60, 120, 200));
d.put("nimbusBlueGrey", new Color(220, 225, 230));
d.put("control", new Color(245, 245, 245));

When you must match a brand color across platforms, do it through defaults. You get a predictable result, and the theme handles the state painters for hover and press. Resist custom painting in every widget unless there is no other way.

If you want a cross platform theme that is still close to each desktop, use JGoodies Looks. It tweaks Metal and Plastic for better spacing and fonts. If your app targets design teams or analysts who live in the app for hours, Substance has skins with strong contrast and good focus indicators. On Mac heavy shops, Quaqua aligns with Aqua controls and feels native on that platform. For a single feel everywhere with a modern touch, Nimbus is the safe pick and ships with recent Java 6 updates.

Here is a tiny Synth snippet to prove that skinning is not scary. You can do this to smooth out a single control without subclassing it everywhere.

// Load once before the UI is built
SynthLookAndFeel synth = new SynthLookAndFeel();
try (InputStream in = Boot.class.getResourceAsStream("/theme.xml")) {
    synth.load(in, Boot.class);
    UIManager.setLookAndFeel(synth);
} catch (Exception e) {
    e.printStackTrace();
}
<?xml version="1.0" encoding="UTF-8"?>
<synth>
  <style id="roundedButton">
    <insets top="6" left="12" bottom="6" right="12"/>
    <state value="ENABLED">
      <color type="BACKGROUND" value="#3C78C8"/>
      <color type="FOREGROUND" value="#FFFFFF"/>
    </state>
  </style>
  <bind style="roundedButton" type="region" key="Button"/>
</synth>

This is not for everything, but a scoped Synth file can align a few controls across Windows, Mac, and Linux without fights.

Counterexamples that keep biting

Setting the theme after creating components. Swing caches UI delegates. If you show a splash screen or construct a dialog before UIManager.setLookAndFeel, you end up with mixed controls in memory. Close the app and start again to see the mess vanish. The fix is simple. Set the theme first. Build later.

Hardcoding sizes. A panel that calls setPreferredSize(new Dimension(300, 200)) will be wrong on every machine that is not yours. Nimbus has different paddings than Windows. Fonts differ across platforms. Use layout managers and pack(). When you need a minimum, base it on content. For example measure string width with FontMetrics and add the Insets from defaults.

Hand painted gradients everywhere. People often override paintComponent on panels to draw a gradient, then watch it clash with native toolbars on one platform and with focus rings on another. Before custom painting, check the defaults. Nimbus already paints gradients with the correct state. Use a custom color key instead of a full paint override.

Icons that ignore the theme. Glossy icons on a flat theme look odd. Dark icons on dark toolbars vanish. Pick a neutral icon set with two tones and provide a light and dark variant if you support a dark skin in Substance or a darker Nimbus palette. Wire them through the theme so the switch happens in one place.

Mixing heavyweight and lightweight popups. AWT heavy menus on top of a Nimbus themed app will flicker on Linux and tear on Windows in some video drivers. Stick with Swing popups by default. Only reach for AWT file dialogs if you need native quirks, and test them on each platform.

Decision rubric for Swing Look and Feel in cross platform apps

  • Audience: If users live inside your app all day, choose a consistent cross platform theme like Nimbus or Substance. If your app should feel native to occasional users, pick system Look and Feel per platform.
  • Team capacity: If you cannot afford theme maintenance, pick Nimbus as is. If you have design time and must match a brand, plan a small theming layer through UIDefaults and Synth.
  • OS mix today: Windows first with some Mac and Linux suggests Windows LAF on Windows and Nimbus elsewhere. Heavy Mac base suggests Quaqua on Mac and a matching theme elsewhere. If distribution is unknown, Nimbus everywhere avoids surprises.
  • Accessibility: Favor themes with clear focus rings, readable contrast, and predictable keyboard focus. Nimbus and Substance do well here. Test with larger fonts and screen readers.
  • Third party components: Check that your charts, docking, and grids behave with your theme choice. Some vendors test mainly on Windows LAF. Run a sample matrix before you commit.
  • Packaging: If startup time or bundle size is tight, avoid large third party themes unless they earn their keep. Nimbus costs you zero jars and comes with the JRE.
  • Customization surface: If you only need colors and paddings, use UIDefaults. If you need custom states and painters, add Synth with a small XML file. Only subclass UI delegates if there is no other route.
  • Future bet: Swing is stable. JavaFX is making noise, but your app needs to ship now. Pick a theme you can live with for years and keep your styles close to defaults to ease future changes.

Practical tips you can apply this week

  • Set the theme in main and never after. Do it before touching any Swing widget.
  • Use layout managers and let components compute size. GridBagLayout, MigLayout, and GroupLayout all beat fixed sizes.
  • Centralize tweaks. One class called Theme that sets UIDefaults and loads Synth if needed. Everything else reads from defaults.
  • Pick one font strategy. Either go with system fonts or enforce a single readable family like SansSerif 13. Do not mix.
  • Test on three boxes: Windows 7, Mac OS X Snow Leopard, and Ubuntu 11.04 with GTK. Screenshots are not enough. Click every dialog, table cell, and popup menu.
  • Respect platform spacing. If you use system Look and Feel, avoid pulling paddings away from what the theme expects. Buttons should not be twice as wide on one platform.
  • Wire colors through keys. Change nimbusBase and friends, not every component background.

Lesson learned

Cross platform Swing does not need to look bland or fragile. The win comes from one early decision and a short list of rules you follow every day. Choose a theme you can support. Push every tweak through UIDefaults. Trust layout managers. Test on the three common desktops. When you need to stand out, add a measured layer of Synth, not a pile of custom paint code. The result is a product that feels the same wherever it runs, and one that your team can keep steady through JRE updates and new Linux spins.

If you need a quick start point, grab Nimbus everywhere and only override fonts, paddings, and a few palette keys. It ships with the JRE, it holds up on Windows, Mac, and Ubuntu, and it avoids the rabbit hole of per platform fixes. Your users care that the app behaves and reads well. Give them that, and Swing will carry you further than the memes suggest.

One last code nugget: a guard that forces your tweaks to apply even if another team engineer sets the theme too late. Not pretty, but useful during the cleanup phase.

public final class Theme {
    private static boolean applied;

    public static synchronized void apply() {
        if (applied) return;
        try {
            // Prefer Nimbus for a consistent cross platform feel
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception ignore) { /* fallback is fine */ }

        UIDefaults d = UIManager.getLookAndFeelDefaults();
        d.put("defaultFont", new Font("SansSerif", Font.PLAIN, 13));
        d.put("nimbusBase", new Color(60, 120, 200));
        d.put("nimbusBlueGrey", new Color(220, 225, 230));
        d.put("control", new Color(245, 245, 245));
        applied = true;
    }
}

// Use it at the very top of your app
public class Main {
    public static void main(String[] args) {
        Theme.apply();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // build UI
            }
        });
    }
}

That is the playbook. Pick the Look and Feel for your users, not your ego. Keep your tweaks in one place. Ship something consistent. Your support mailbox will thank you.

General Software Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes