The default Swing JOptionPane does not implement backgound color for the actual text. There is UIManager.put("OptionPane.background", Color.WHITE); But that does not do the job, because it only changes color of underlying panel.The actual text still stays on grey background. This can be verified by viewing JOptionpane code. Also if you fire google search for "Joptionpane background" and switch to image search you see that every single result has grey backgound under actual text. So the question is what would be alternative UI component for JOptionPane so that colors can be fully designed?
2 Answers
Answer to the question is this class which let's you to decide the colors for background and text and for the button itself. Other design point is to optimize it to be typed extremely easily in debugging context - reason for breaking the camel case syntax which I never do. I don't feel bad about it. You can change that one letter...i guess. Text of the used JTextPane length can be anything and scroll bar gets exited if the text grows long. It is easily callable alternative utility class for JOptionPane. One thing worth of notice is that the info method smells it is not part of GUI Event Dispatch Thread(EDT) execution.If not it assigns itself as EDT task. You can choose if it stops(blocking function) execution or not. No EDT violation happens in any case. Basic usage is msgbox.info("hello") If you want to print some of the top of the stack trace bundled is one utility function named msgbox.stackTop(numStackelements)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class msgbox implements ActionListener {
//**********************************************
JButton okButton;
JFrame frame;
public msgbox(String msg) {
frame = new JFrame("");
//buffered image replaces white JAVA icon with nothing
Image icon = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
frame.setIconImage(icon);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
Container contPane = frame.getContentPane();
contPane.setLayout(new BorderLayout());
JTextPane info = new JTextPane();
JScrollPane scroll = initJTextPane(info, msg);
contPane.add(scroll, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel(); //Flow layout by default
buttonPanel.setBackground(new Color(0, 0, 0));
buttonPanel.setForeground(new Color(107, 216, 61));
okButton = new JButton("OK");
okButton.addActionListener(this);
okButton.setBackground(new Color(0, 0, 0));
okButton.setForeground(new Color(107, 216, 61));
buttonPanel.add(okButton);
contPane.add(buttonPanel, BorderLayout.SOUTH);
//screen size investigation
// in case we need to put msg to a particular place
//this is REAL screen size like GPU sets it for monitor
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int width = gd.getDisplayMode().getWidth();//3840 i.e GPU setting
int height = gd.getDisplayMode().getHeight();//2160 GPU
//next is what java uses when display in Windows desktop scaling setting is set to for example 250%
//pixels get stretched....
int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;//1536 when desktop is scaled 250%
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;//864 when desktop is scaled to 250%
//put message in the middle of the screen
frame.setLocation(screenWidth / 2, screenHeight / 2);
frame.pack();//calls addNotify....
frame.setVisible(true);
frame.setAlwaysOnTop(true);
frame.toFront();
frame.requestFocus();
frame.repaint();
}
//*************************************************
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
String butCommand = okButton.getActionCommand();
if (actionCommand.equalsIgnoreCase(butCommand))
frame.dispose();
}
//*********************************************
public JScrollPane initJTextPane(JTextPane infoPan, String msg) {
infoPan.setBackground(new Color(25, 25, 39));
infoPan.setForeground(new Color(95, 164, 90));
infoPan.setFont(new Font("Calibri", Font.PLAIN, 14));
infoPan.setText(msg);
infoPan.setVisible(true);
JScrollPane scrollPane = new JScrollPane(infoPan);
return scrollPane;
}
//********************************************
public static void info(String msg) {
try {
if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
//started from plain java main, so assign to EDT
Runnable task = () -> new msgbox(msg);
try {
SwingUtilities.invokeLater(task);//non blocking
//SwingUtilities.invokeAndWait(task);//blocking
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
//we are initially in EDT, called from dispatch thread
new msgbox(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//***********************************************
public static void main(String[] args) throws Exception {
stackTop("base message!\n", 15);
}
//***********************************************
//UTILS
//stacktop prints given number of current thread stack items including
//this stacktop call.Message can be attached as first printed element
public static void stackTop(String rootMsg, int elemLimit) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int howMany = elemLimit;
int available = stack.length;
if (howMany > available)
howMany = available;
for (int i = 1; i < howMany; ++i) {
rootMsg += stack[i] + "\n";
}
info(rootMsg);
}
}

- 127
- 9
There are multiple ways in which you might achieve this. You could, for instance, create you own custom Look and Feel delegate ... although to be honest, I wouldn't recommend it.
Another solution would be to build your own custom dialog, which would give you control over these types of decisions. This is where you begin to understand just how powerful and complex JOptionPane
actually is.
This is a basic concept I just whipped, based on builder pattern...
public class MessageDialog {
public enum Action {
OKAY, CANCEL;
}
public enum Type {
ERROR(UIManager.getLookAndFeel().getDefaults().getIcon("OptionPane.errorIcon")),
INFO(UIManager.getLookAndFeel().getDefaults().getIcon("OptionPane.informationIcon")),
WARNING(UIManager.getLookAndFeel().getDefaults().getIcon("OptionPane.warningIcon")),
QUESTION(UIManager.getLookAndFeel().getDefaults().getIcon("OptionPane.questionIcon"));
private Icon icon;
private Type(Icon icon) {
this.icon = icon;
}
public Icon getIcon() {
return icon;
}
}
private String title;
private Object message;
private Icon icon;
private List<Action> actions;
private Action value;
private JDialog dialog;
public MessageDialog() {
actions = new ArrayList<>(8);
}
public MessageDialog withTitle(String title) {
this.title = title;
return this;
}
public MessageDialog withMessage(String message) {
this.message = message;
return this;
}
public MessageDialog withType(Type type) {
return withIcon(type.getIcon());
}
public MessageDialog withIcon(Icon icon) {
this.icon = icon;
return this;
}
public MessageDialog withIcon(ImageIcon image) {
return withIcon((Icon) image);
}
public MessageDialog withIcon(BufferedImage image) {
return withIcon(new ImageIcon(image));
}
public MessageDialog withAction(Action action) {
if (!actions.contains(action)) {
actions.add(action);
}
return this;
}
public String getTitle() {
return title;
}
public Object getMessage() {
return message;
}
public Icon getIcon() {
return icon;
}
public List<Action> getActions() {
return actions;
}
protected JDialog makeDialog(Component owner, String title) {
Window window = owner == null ? null : SwingUtilities.windowForComponent(owner);
dialog = new JDialog(window, title);
dialog.setModal(true);
return dialog;
}
protected JComponent makeMessageComponent() {
Object message = getMessage();
if (message instanceof JComponent) {
return (JComponent) message;
}
return new JLabel(message.toString());
}
protected JComponent makeIconComponent() {
Icon icon = getIcon();
if (icon == null) {
return null;
}
return new JLabel(icon);
}
protected JComponent makeActionsComponent() {
JPanel pane = new JPanel(new GridBagLayout());
pane.setOpaque(false);
List<Action> actions = getActions();
if (!actions.isEmpty()) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.insets = new Insets(4, 4, 4, 4);
gbc.anchor = GridBagConstraints.LINE_END;
for (Action action : actions) {
String text = "";
switch (action) {
case OKAY:
text = "Ok";
break;
case CANCEL:
text = "Cancel";
break;
}
if (text.isBlank()) {
continue;
}
JButton btn = new JButton(text);
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setValue(action);
}
});
pane.add(btn, gbc);
gbc.gridx++;
gbc.weightx = 0;
}
}
return pane;
}
protected JDialog getDialog() {
return dialog;
}
protected void setValue(Action action) {
this.value = action;
JDialog dialog = getDialog();
if (dialog != null) {
dialog.dispose();
}
}
public Action getValue() {
return value;
}
protected JComponent makeContentComponent() {
JPanel pane = new JPanel(new BorderLayout());
pane.setOpaque(false);
pane.setBorder(new EmptyBorder(16, 16, 8, 16));
return pane;
}
public Action show(Component owner, String title) {
JDialog dialog = makeDialog(owner, title);
JPanel bodyPane = new JPanel(new BorderLayout(8, 8));
bodyPane.setOpaque(false);
bodyPane.add(makeIconComponent(), BorderLayout.LINE_START);
bodyPane.add(makeMessageComponent());
JComponent content = makeContentComponent();
content.setLayout(new BorderLayout(16, 16));
content.add(bodyPane);
content.add(makeActionsComponent(), BorderLayout.SOUTH);
dialog.add(content);
dialog.pack();
dialog.setLocationRelativeTo(owner);
dialog.setVisible(true);
return getValue();
}
}
which you would use something like...
MessageDialog.Action action = new MessageDialog()
.withType(MessageDialog.Type.INFO)
.withMessage("This is a message")
.withAction(MessageDialog.Action.OKAY)
.show(null, "What's up");
and would present something like...
Now please beware, there are some limitations with this implementation, like what to do if the user closes the dialog. You'd need to decide which is the default action or maybe even return a custom "closed" (the user made no choice) action instead.
But how does this help you?
Well, you could customise the way in which the class works, for example...
public class CustomMessageDialog extends MessageDialog {
@Override
protected JDialog makeDialog(Component owner, String title) {
JDialog dialog = super.makeDialog(owner, title);
dialog.getContentPane().setBackground(Color.RED);
return dialog;
}
}
When used with...
MessageDialog.Action action = new CustomMessageDialog()
.withType(MessageDialog.Type.INFO)
.withMessage("This is a message")
.withAction(MessageDialog.Action.OKAY)
.show(null, "What's up");
will present...
You could even take the time to design a concept of "configuration" which could be used to customise various aspects of the dialog and it's content, but we're drifting over into the look and feel delegate's territory.
My gut feeling is, if you're creating a "custom" UI, you'd be better off looking at the UI defaults for the current look and feel or actually creating your own custom look and feel.
If you're looking to just "jazz" up the UI a little, then you're going to need to look at designing some of your own features to fill the gaps - IMHO
I'd even consider having a look and some pre-existing libraries, like SwingX which already have some pre-built functionality along these lines
I have white allergy(i.e. whites limit the length of the programming day and incite epileptic condition) so there is a strong personal need to make UIs soft high contrast
Then I would consider starting either with a complete custom look and feel or, at the very least, modifying the core look and feel defaults, as a very crude example...
try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
UIManager.put("OptionPane.background", Color.RED);
UIManager.put("Panel.background", Color.RED);
UIManager.put("Button.background", Color.GREEN);
JOptionPane.showConfirmDialog(null, "This is a test", "Testing", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
It's important to note that I specifically set the look and feel to the "cross platform" (AKA metal) look and feel. This is deliberate, as on macOS, the look and feel has a habit of ignoring some of these properties.

- 343,457
- 22
- 230
- 366
-
Thanks for input! I forgot to specify in the question that the solution should not include any third party library. Did you try my answer it should be run out of the box as one pastwed class ti IDE? – Tonecops Jun 07 '23 at 02:00
-
I got 23 errors from your class so I need ocheck if it is runnable out of the box which i wish for an answer. It is just me so please don't take it as offending. – Tonecops Jun 07 '23 at 02:03
-
Ok it turned out to be IDE being late to add correct imports. Still one thing left:type java.awt.List does not take parameters – Tonecops Jun 07 '23 at 02:08
-
-
As to "no third party libraries" - this shouldn't mean that you can't look at how other libraries work, I've "borrowed" plenty of ideas from things like SwingX – MadProgrammer Jun 07 '23 at 02:11
-
IDE was adding wrong list import. The added dependency complexity of 3rds hits me later like 3-5 years and makes life difficult. It depends on the nature of the task. I work on java audio plugin host project and one of the main design constraint is to keep it to be immediately runnable with any vanilla JDK. I had this very bad experience (around 2011) having forced to install Tomcat and all the dependencies just to run simple websocket server. That triggered me to write my own websocket server and it was really a revelation like how on earth we need to use such monster just for this tiny task? – Tonecops Jun 07 '23 at 02:44
-
@Tonecops Needs drive musts - as I said, sometime it's useful to "borrow" functionality from open source projects, so you don't "need" the whole library. The point is, as a group of developers is probably going to do something better then I could do alone - just saying – MadProgrammer Jun 07 '23 at 02:50
-
@Tonecops Remember, this is just one way to approach the problem, depending on your overall needs, there might be other ways to achieve what you're trying to do – MadProgrammer Jun 07 '23 at 02:52
-
I agree about learning. I forgot to mention underlying reason I'm asking the whole thing: I have white allergy(i.e. whites limit the length of the programming day and incite epileptic condition) so there is a strong personal need to make UIs soft high contrast i.e. dark bg but absolutely without white text. Text will be greenish pastel #4c9f8d on bg close to black. Few things in your provided image are not yet quite in my specs: Jave icon is mostly white. The dialog bar is still bright and OK button is still bright. Are those three features stone carved or can they be tuned too? – Tonecops Jun 07 '23 at 03:12
-
In that case, I would consider designing a whole custom look and feel delegate plugin - but that's me (for [example](https://www.formdev.com/flatlaf/)) – MadProgrammer Jun 07 '23 at 03:27
-
My very own LAF sounds interesting. How would you start about it? I got your solution working. A question arises about java language. Why that CustomMessageDialog class cannot be an inner class of the MessageDialog? I got "this cannot be called from static context" and I could not explain. I moved CustomMessageDialog to its own .java (and moved main too) and it works. – Tonecops Jun 07 '23 at 03:43
-
`CustomMessageDialog` can't be an inner class of `MessageDialog` because of the way extension works. *"This cannot be called from static context"* is why I always get out main the `static main` as fast as I can and into an actual object instance. As for a custom look and feel, that's a very complex subject and this is why I might be tempted to see how some other L&Fs are created. Nimbus is "reasonable" simpler to modifier, but it's not without it's issues – MadProgrammer Jun 07 '23 at 03:56
-
-
In this context, that's generally your responsibility, but, since the dialog is only realised on the screen when you call `pack`, it's otherwise relatively safe, having said that, I would, personally, code in such away to always call it (and even `JOptionPane`) from within the context of the EDT – MadProgrammer Jun 07 '23 at 04:11