Search code examples
javaswing

java: package com.sun.java.swing.plaf.windows does not exist


I switched from Java 8 to Java 17. The code now doesn't compile

Specifically, imports from the com.sun.java.swing.plaf.windows package are now invalid.

java: package com.sun.java.swing.plaf.windows does not exist

However, it does exist. It's just the new compiler being unreasonable. It now refuses to acknowledge its existence for some reason.

compilation error, com.sun.java.swing.plaf.windows does no exist

These com.sun.java.swing packages were never intended to be used outside of Java itself, and shouldn't have been used directly. Java now enforces that.

Mark Rotteveel

Alright, but here we are, we reference the package anyway. Java claims to be backwards-compatible, so anything that's legal in an older version should be legal in a newer one

So what do we do?

// MRE. Compilation fails. Amazon Corretto 17

package demos.button;

import com.sun.java.swing.plaf.windows.WindowsLookAndFeel;

import javax.swing.*;
import java.awt.*;

public class CheckBoxDemo {
    public static void main(String[] args) throws UnsupportedLookAndFeelException, ClassNotFoundException,
            InstantiationException, IllegalAccessException {
        // you don't expect us to pass strings directly, do you?
        UIManager.setLookAndFeel(WindowsLookAndFeel.class.getName());

        JFrame frame = new JFrame("Check Box demo");
        JPanel mainPanel = createMainPanel();
        frame.setContentPane(mainPanel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);
    }

    private static JPanel createMainPanel() {
        FlowLayout layout = new FlowLayout();
        layout.setAlignment(FlowLayout.CENTER);
        JPanel mainPanel = new JPanel(layout);
        mainPanel.add(createCheckBox());
        return mainPanel;
    }

    private static JCheckBox createCheckBox() {
        JCheckBox checkBox = new JCheckBox();
        return checkBox;
    }
}

Even if I disable the --release option, the code doesn't compile anyway. For example, because ComboPopup's getList() used to look like this

public JList getList();

and now it looks like this

public JList<Object> getList();

So if your ComboPopup implementation used any type arguments, tough luck

// no longer compiles
@Override
public JList<T> getList() {
    return itemList;
}

It doesn't look like backwards-compatibility at all. Isn't it (along with cross-platform support) the whole point of Java?


Solution

  • Since Java 9, Java is modular, and things you shouldn't be using are not exported from those modules, including public classes that are not part of the API defined by the Java specification. The classes in com.sun.* in most cases are specific to the Sun/Oracle implementation of Java, and sometimes even platform-specific (as is the case for this look-and-feel), and not part of the Java specification.

    As such, they are generally considered an implementation detail and not part of any API, and thus not subject to the backwards-compatibility promises of Java. If you'd used a Java implementation of a different vendor, you would likely not have access to them either (because they wouldn't exist).

    So, since Java 9, when modules were implemented, you can no longer access these classes, even if they still exist (the package com.sun.java.swing.plaf.windows is located in the java.desktop module, at least in Windows builds). In theory you can still access them if you add the necessary exports (e.g. with --add-exports on the commandline, or with Add-Exports in them manifest of the application JAR).

    However, it is far easier to replace your code with something which will simply work without exports, in a way that Sun and now Oracle have always recommended (e.g. see the Swing tutorial How to Set the Look and Feel which is based on Java 8).

    You have multiple options:

    1. Explicitly set the look and feel by class name:

      UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
      

      I wouldn't recommend this, as it still ties you to Oracle/OpenJDK variants of Java.

    2. Use the name of the look and feel instead of the class name (introduced in Java 9):

      UIManager.setLookAndFeel(UIManager.createLookAndFeel("Windows");
      

      Note that the Java 8 tutorial I linked mentions the names Windows XP and Windows Vista, but the names are actually Windows Classic and Windows. I can only assume that when this feature was introduced in Java 9, they considered the names mentioned in the Java 8 tutorial not right for this.

      If the Java implementation or platform doesn't actually have the specified look and feel name, this will result in an UnsupportedLookAndFeelException.

    3. Make it dynamic based on the actual system:

      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());