Skip to content

Instantly share code, notes, and snippets.

@gesellix
Last active September 26, 2025 18:48
Show Gist options
  • Save gesellix/3e052c430e6198637945a1634c5f855e to your computer and use it in GitHub Desktop.
Save gesellix/3e052c430e6198637945a1634c5f855e to your computer and use it in GitHub Desktop.
Java 8 and macOS 26

Swing/AWT with Java 8 on macOS 26 has some issues regarding

  1. blurry fonts and icons
  2. hidden custom title bar when maximized

This Gist makes the issues easily reproducible.

test_fonts.sh and FontRenderingTest.java show font rendering issues, CustomTitleBarFrame.java shows the behaviour of custom title bars for maximized frames.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class CustomTitleBarFrame extends JFrame {
private JPanel customTitleBar;
private JLabel titleLabel;
private boolean isMaximized = false;
private Rectangle normalBounds;
public CustomTitleBarFrame() {
setTitle("Custom Title Bar Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setUndecorated(true); // Remove default title bar
// Initialize before setting up UI
initializeWindowBounds();
setupCustomTitleBar();
setupMainContent();
setupWindowListeners();
// Set initial size and position
setSize(800, 600);
setLocationRelativeTo(null);
normalBounds = getBounds();
// Important: Set maximized bounds properly for macOS
updateMaximizedBounds();
}
private void initializeWindowBounds() {
// This is crucial for macOS - set maximized bounds before showing window
updateMaximizedBounds();
}
private void updateMaximizedBounds() {
GraphicsConfiguration gc = getGraphicsConfiguration();
if (gc == null) {
// Fallback to default screen device
gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
}
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
// Calculate bounds that respect macOS menu bar and dock
int x = screenBounds.x + screenInsets.left;
int y = screenBounds.y + screenInsets.top; // This keeps us below menu bar
int width = screenBounds.width - screenInsets.left - screenInsets.right;
int height = screenBounds.height - screenInsets.top - screenInsets.bottom;
Rectangle maxBounds = new Rectangle(x, y, width, height);
setMaximizedBounds(maxBounds);
System.out.println("Screen bounds: " + screenBounds);
System.out.println("Screen insets: " + screenInsets);
System.out.println("Maximized bounds set to: " + maxBounds);
}
private void setupCustomTitleBar() {
customTitleBar = new JPanel(new BorderLayout());
customTitleBar.setBackground(new Color(240, 240, 240));
customTitleBar.setPreferredSize(new Dimension(0, 30));
customTitleBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
// Title label
titleLabel = new JLabel("Custom Title Bar Application");
titleLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
// Window control buttons panel
JPanel controlsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
controlsPanel.setOpaque(false);
// Minimize button
JButton minimizeBtn = createControlButton("−");
minimizeBtn.addActionListener(e -> setState(Frame.ICONIFIED));
// Maximize/Restore button
JButton maximizeBtn = createControlButton("□");
maximizeBtn.addActionListener(e -> toggleMaximize());
// Close button
JButton closeBtn = createControlButton("×");
closeBtn.addActionListener(e -> {
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
});
controlsPanel.add(minimizeBtn);
controlsPanel.add(maximizeBtn);
controlsPanel.add(closeBtn);
customTitleBar.add(titleLabel, BorderLayout.CENTER);
customTitleBar.add(controlsPanel, BorderLayout.EAST);
// Make title bar draggable
makeTitleBarDraggable();
add(customTitleBar, BorderLayout.NORTH);
}
private JButton createControlButton(String text) {
JButton button = new JButton(text);
button.setPreferredSize(new Dimension(25, 20));
button.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// Hover effects
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
button.setContentAreaFilled(true);
button.setBackground(new Color(220, 220, 220));
}
@Override
public void mouseExited(MouseEvent e) {
button.setContentAreaFilled(false);
}
});
return button;
}
private void makeTitleBarDraggable() {
final Point[] dragOffset = new Point[1];
MouseListener mouseListener = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
dragOffset[0] = e.getPoint();
}
}
@Override
public void mouseClicked(MouseEvent e) {
// Double-click to toggle maximize
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
toggleMaximize();
}
}
};
MouseMotionListener motionListener = new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (dragOffset[0] != null && !isMaximized) {
Point current = e.getLocationOnScreen();
setLocation(current.x - dragOffset[0].x, current.y - dragOffset[0].y);
}
}
};
customTitleBar.addMouseListener(mouseListener);
customTitleBar.addMouseMotionListener(motionListener);
titleLabel.addMouseListener(mouseListener);
titleLabel.addMouseMotionListener(motionListener);
}
private void toggleMaximize() {
if (isMaximized) {
restore();
} else {
maximize();
}
}
private void maximize() {
if (!isMaximized) {
normalBounds = getBounds();
// Update maximized bounds before maximizing (important for multi-monitor)
updateMaximizedBounds();
// Use the properly calculated maximized bounds
Rectangle maxBounds = getMaximizedBounds();
if (maxBounds != null) {
setBounds(maxBounds);
} else {
// Fallback calculation
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
setBounds(
screenBounds.x + screenInsets.left,
screenBounds.y + screenInsets.top,
screenBounds.width - screenInsets.left - screenInsets.right,
screenBounds.height - screenInsets.top - screenInsets.bottom
);
}
isMaximized = true;
titleLabel.setText("Custom Title Bar Application (Maximized)");
}
}
private void restore() {
if (isMaximized && normalBounds != null) {
setBounds(normalBounds);
isMaximized = false;
titleLabel.setText("Custom Title Bar Application");
}
}
private void setupMainContent() {
JPanel contentPanel = new JPanel(new BorderLayout());
// Main content area
JTextArea textArea = new JTextArea();
textArea.setText("This is the main content area.\n\n" +
"The custom title bar should:\n" +
"1. Stay below the macOS menu bar when maximized\n" +
"2. Be draggable when not maximized\n" +
"3. Support double-click to maximize/restore\n" +
"4. Have working minimize/maximize/close buttons\n\n" +
"Try maximizing and check that the title bar remains accessible!");
textArea.setMargin(new Insets(10, 10, 10, 10));
textArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 14));
JScrollPane scrollPane = new JScrollPane(textArea);
contentPanel.add(scrollPane, BorderLayout.CENTER);
// Status bar
JPanel statusBar = new JPanel(new BorderLayout());
statusBar.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));
statusBar.setPreferredSize(new Dimension(0, 25));
JLabel statusLabel = new JLabel("Ready");
statusLabel.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
statusBar.add(statusLabel, BorderLayout.WEST);
contentPanel.add(statusBar, BorderLayout.SOUTH);
add(contentPanel, BorderLayout.CENTER);
}
private void setupWindowListeners() {
// Listen for display changes (monitor changes, resolution changes, etc.)
addPropertyChangeListener("graphicsConfiguration", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
SwingUtilities.invokeLater(() -> updateMaximizedBounds());
}
});
// Component listener for resize events
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
// Update maximized bounds when window is resized
if (!isMaximized) {
updateMaximizedBounds();
}
}
@Override
public void componentMoved(ComponentEvent e) {
// Update maximized bounds when window is moved (e.g., to different monitor)
if (!isMaximized) {
updateMaximizedBounds();
}
}
});
// Window state listener
addWindowStateListener(new WindowStateListener() {
@Override
public void windowStateChanged(WindowEvent e) {
System.out.println("Window state changed: " + e.getNewState());
// Handle external maximize/restore (e.g., from dock)
if ((e.getNewState() & Frame.MAXIMIZED_BOTH) != 0) {
if (!isMaximized) {
normalBounds = e.getOldState() == Frame.NORMAL ? getBounds() : normalBounds;
isMaximized = true;
titleLabel.setText("Custom Title Bar Application (Maximized)");
}
} else {
if (isMaximized) {
isMaximized = false;
titleLabel.setText("Custom Title Bar Application");
}
}
}
});
}
public static void main(String[] args) {
// Set macOS-specific properties
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("apple.awt.application.name", "Custom Title Bar Test");
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Custom Title Bar Test");
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
CustomTitleBarFrame frame = new CustomTitleBarFrame();
frame.setVisible(true);
});
}
}
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import javax.swing.*;
public class FontRenderingTest extends JFrame {
private JPanel mainPanel;
private JTextArea infoArea;
public FontRenderingTest() {
setTitle(
"Font Rendering Test - Java " + System.getProperty("java.version")
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
setLocationRelativeTo(null);
initComponents();
displaySystemInfo();
}
private void initComponents() {
mainPanel = new JPanel(new BorderLayout());
// Create header with title
JLabel titleLabel = new JLabel(
"Font Rendering Test Application",
JLabel.CENTER
);
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 24));
titleLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Create font samples panel
JPanel samplesPanel = createFontSamplesPanel();
// Create info panel
infoArea = new JTextArea();
infoArea.setEditable(false);
infoArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
infoArea.setBackground(new Color(248, 248, 248));
infoArea.setBorder(
BorderFactory.createTitledBorder("System Information")
);
JScrollPane scrollPane = new JScrollPane(infoArea);
scrollPane.setPreferredSize(new Dimension(800, 200));
// Create control panel
JPanel controlPanel = createControlPanel();
mainPanel.add(titleLabel, BorderLayout.NORTH);
mainPanel.add(samplesPanel, BorderLayout.CENTER);
mainPanel.add(scrollPane, BorderLayout.SOUTH);
mainPanel.add(controlPanel, BorderLayout.EAST);
add(mainPanel);
}
private JPanel createFontSamplesPanel() {
JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Font Samples"));
// Different font sizes and styles
String sampleText =
"The quick brown fox jumps over the lazy dog 1234567890";
Font[] fonts = {
new Font(Font.SANS_SERIF, Font.PLAIN, 10),
new Font(Font.SANS_SERIF, Font.PLAIN, 12),
new Font(Font.SANS_SERIF, Font.PLAIN, 14),
new Font(Font.SANS_SERIF, Font.BOLD, 14),
new Font(Font.SERIF, Font.PLAIN, 14),
new Font(Font.SERIF, Font.ITALIC, 14),
new Font(Font.MONOSPACED, Font.PLAIN, 12),
new Font("Arial", Font.PLAIN, 14),
new Font("Helvetica", Font.PLAIN, 14),
new Font("Times New Roman", Font.PLAIN, 14),
};
for (Font font : fonts) {
JLabel label = new JLabel(
sampleText +
" (" +
font.getFontName() +
", " +
font.getSize() +
"pt)"
);
label.setFont(font);
label.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
panel.add(label);
}
return panel;
}
private JPanel createControlPanel() {
JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Controls"));
panel.setPreferredSize(new Dimension(200, 0));
// Refresh button
JButton refreshButton = new JButton("Refresh Info");
refreshButton.addActionListener(e -> displaySystemInfo());
// Test rendering hints button
JButton hintsButton = new JButton("Test Rendering Hints");
hintsButton.addActionListener(e -> showRenderingHintsDialog());
// Font smoothing test
JButton smoothingButton = new JButton("Font Smoothing Test");
smoothingButton.addActionListener(e -> showFontSmoothingTest());
panel.add(refreshButton);
panel.add(hintsButton);
panel.add(smoothingButton);
return panel;
}
private void displaySystemInfo() {
StringBuilder info = new StringBuilder();
// Java version info
info
.append("Java Version: ")
.append(System.getProperty("java.version"))
.append("\n");
info
.append("Java Vendor: ")
.append(System.getProperty("java.vendor"))
.append("\n");
info
.append("Java Home: ")
.append(System.getProperty("java.home"))
.append("\n\n");
// OS info
info.append("OS: ").append(System.getProperty("os.name")).append(" ");
info.append(System.getProperty("os.version")).append("\n");
info
.append("Architecture: ")
.append(System.getProperty("os.arch"))
.append("\n\n");
// Display info
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
DisplayMode dm = gd.getDisplayMode();
info
.append("Display Resolution: ")
.append(dm.getWidth())
.append("x")
.append(dm.getHeight())
.append("\n");
info.append("Color Depth: ").append(dm.getBitDepth()).append(" bits\n");
info
.append("Refresh Rate: ")
.append(dm.getRefreshRate())
.append(" Hz\n\n");
// Font rendering hints
info.append("Current AWT Desktop Hints:\n");
Toolkit toolkit = Toolkit.getDefaultToolkit();
Map<?, ?> desktopHints = (Map<?, ?>) toolkit.getDesktopProperty(
"awt.font.desktophints"
);
if (desktopHints != null) {
for (Map.Entry<?, ?> entry : desktopHints.entrySet()) {
info
.append(" ")
.append(entry.getKey())
.append(" = ")
.append(entry.getValue())
.append("\n");
}
} else {
info.append(" No desktop hints available\n");
}
// JVM arguments related to font rendering
info.append("\nRelevant System Properties:\n");
String[] properties = {
"apple.awt.graphics.UseQuartz",
"apple.awt.textantialiasing",
"sun.java2d.uiScale",
"sun.java2d.uiScale.enabled",
"awt.useSystemAAFontSettings",
"swing.aatext",
"sun.java2d.opengl",
"sun.java2d.metal",
};
for (String prop : properties) {
String value = System.getProperty(prop);
info
.append(" ")
.append(prop)
.append(" = ")
.append(value != null ? value : "not set")
.append("\n");
}
infoArea.setText(info.toString());
}
private void showRenderingHintsDialog() {
JDialog dialog = new JDialog(this, "Rendering Hints Test", true);
dialog.setSize(600, 400);
dialog.setLocationRelativeTo(this);
JPanel panel = new JPanel(new GridLayout(0, 1));
String testText = "Sample text for rendering hints testing";
// Different rendering hint combinations
RenderingHints[] hintsArray = {
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF
),
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON
),
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT
),
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP
),
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB
),
new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR
),
};
String[] descriptions = {
"TEXT_ANTIALIAS_OFF",
"TEXT_ANTIALIAS_ON",
"TEXT_ANTIALIAS_DEFAULT",
"TEXT_ANTIALIAS_GASP",
"TEXT_ANTIALIAS_LCD_HRGB",
"TEXT_ANTIALIAS_LCD_HBGR",
};
for (int i = 0; i < hintsArray.length; i++) {
JPanel textPanel = new CustomRenderingPanel(
testText,
hintsArray[i],
descriptions[i]
);
panel.add(textPanel);
}
dialog.add(new JScrollPane(panel));
dialog.setVisible(true);
}
private void showFontSmoothingTest() {
JOptionPane.showMessageDialog(
this,
"Check the terminal for font smoothing commands.\n" +
"Current AppleFontSmoothing setting can be checked with:\n" +
"defaults read -g AppleFontSmoothing\n\n" +
"Try values 1, 2, or 3:\n" +
"defaults write -g AppleFontSmoothing -int 2",
"Font Smoothing Test",
JOptionPane.INFORMATION_MESSAGE
);
}
// Custom panel for testing different rendering hints
private static class CustomRenderingPanel extends JPanel {
private String text;
private RenderingHints hints;
private String description;
public CustomRenderingPanel(
String text,
RenderingHints hints,
String description
) {
this.text = text;
this.hints = hints;
this.description = description;
setPreferredSize(new Dimension(500, 40));
setBorder(BorderFactory.createTitledBorder(description));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHints(hints);
g2d.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 14));
g2d.drawString(text, 10, 25);
}
}
public static void main(String[] args) {
// Print current JVM arguments for reference
System.out.println("Current JVM arguments (font-related):");
String[] properties = {
"apple.awt.graphics.UseQuartz",
"apple.awt.textantialiasing",
"sun.java2d.uiScale",
"awt.useSystemAAFontSettings",
"swing.aatext",
};
for (String prop : properties) {
System.out.println(" " + prop + " = " + System.getProperty(prop));
}
System.out.println();
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName()
);
} catch (Exception e) {
e.printStackTrace();
}
new FontRenderingTest().setVisible(true);
});
}
}
#!/bin/bash
# Font Rendering Test Script for Java 8 on macOS
# This script helps test different JVM arguments to fix blurry fonts
echo "Font Rendering Test Script for Java 8 on macOS"
echo "=============================================="
# Check if Java is available
if ! command -v java &> /dev/null; then
echo "Error: Java not found in PATH"
exit 1
fi
# Show current Java version
echo "Java version:"
java -version
echo
# Check if the test class exists
if [ ! -f "FontRenderingTest.java" ]; then
echo "Error: FontRenderingTest.java not found"
echo "Please save the Java code as FontRenderingTest.java first"
exit 1
fi
# Compile the test class
echo "Compiling FontRenderingTest.java..."
javac FontRenderingTest.java
if [ $? -ne 0 ]; then
echo "Error: Compilation failed"
exit 1
fi
echo "Compilation successful!"
echo
# Function to run test with specific arguments
run_test() {
local test_name="$1"
shift
local args="$@"
echo "=========================================="
echo "Running test: $test_name"
echo "JVM Args: $args"
echo "=========================================="
echo "Close the window to continue to next test..."
java $args FontRenderingTest
echo
}
# Test 1: Baseline (no special arguments)
run_test "Baseline Test" ""
# Test 2: Basic macOS font improvements
run_test "Basic macOS Font Improvements" \
"-Dapple.awt.graphics.UseQuartz=true" \
"-Dapple.awt.textantialiasing=on" \
"-Dapple.laf.useScreenMenuBar=true"
# Test 3: HiDPI scaling
run_test "HiDPI Scaling (Auto)" \
"-Dsun.java2d.uiScale.enabled=true" \
"-Dapple.awt.graphics.UseQuartz=true" \
"-Dapple.awt.textantialiasing=on"
# Test 4: Manual HiDPI scaling
run_test "HiDPI Scaling (Manual 2x)" \
"-Dsun.java2d.uiScale=2.0" \
"-Dapple.awt.graphics.UseQuartz=true" \
"-Dapple.awt.textantialiasing=on"
# Test 5: Font antialiasing settings
run_test "Font Antialiasing (LCD)" \
"-Dawt.useSystemAAFontSettings=lcd" \
"-Dswing.aatext=true" \
"-Dapple.awt.graphics.UseQuartz=true"
# Test 6: Disable hardware acceleration
run_test "Software Rendering Only" \
"-Dsun.java2d.opengl=false" \
"-Dsun.java2d.metal=false" \
"-Dapple.awt.graphics.UseQuartz=true" \
"-Dapple.awt.textantialiasing=on"
# Test 7: Comprehensive fix attempt
run_test "Comprehensive Fix" \
"-Dapple.awt.graphics.UseQuartz=true" \
"-Dapple.awt.textantialiasing=on" \
"-Dapple.laf.useScreenMenuBar=true" \
"-Dsun.java2d.uiScale.enabled=true" \
"-Dawt.useSystemAAFontSettings=lcd" \
"-Dswing.aatext=true" \
"-Dapple.awt.UIElement=true"
echo "=========================================="
echo "All tests completed!"
echo "=========================================="
echo
echo "Additional manual tests you can try:"
echo
echo "1. Check current font smoothing setting:"
echo " defaults read -g AppleFontSmoothing"
echo
echo "2. Try different font smoothing values (1-3):"
echo " defaults write -g AppleFontSmoothing -int 2"
echo " (Restart test app after changing this)"
echo
echo "3. Clear font caches:"
echo " sudo atsutil databases -remove"
echo " sudo atsutil server -shutdown"
echo " sudo atsutil server -ping"
echo
echo "4. Test with different Look and Feel:"
echo " Add -Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel"
echo
echo "5. Manual test command template:"
echo " java [YOUR_ARGS_HERE] FontRenderingTest"
echo
# Clean up
echo "Cleaning up compiled files..."
rm -f FontRenderingTest.class FontRenderingTest\$CustomRenderingPanel.class
echo "Done!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment