JFrame
uses BorderLayout
by default, which means only one image is managed at the default/CENTER
position.
See How to Use Borders for more details.
The solution is actually difficult, because you need to be able to make guarantees about the size of the layout, the images and the board
You might be able to use a GridLayout
or GridBagLayout
, but you'd need to have "empty" filler components to allow the layout to correctly expand, which could be come troublesome
Maybe a better solution might be to use a custom painting approach, which give you control over where the images are placed.
Painting in AWT and Swing and Performing Custom Painting for more detals
The following example simply makes use of GridBagLayout
, which allows you to overlay components (in a variety of different ways). This example makes the boardLabel
the container for all the other pieces, but, because the way GridBagLayout
works, this isn't an absolute requirement

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Chess {
public static void main(String[] args) {
new Chess();
}
public Chess() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
BufferedImage board = ImageIO.read(getClass().getResource("/board.png"));
BufferedImage knight = ImageIO.read(getClass().getResource("/Knight.png"));
setLayout(new BorderLayout());
JLabel boardLabel = new JLabel(new ImageIcon(board));
add(boardLabel);
boardLabel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int row = 0; row < 8; row++) {
gbc.gridy = row;
for (int col = 0; col < 8; col++) {
gbc.gridx = col;
boardLabel.add(filler(), gbc);
}
}
JLabel knightLabel = new JLabel(new ImageIcon(knight));
gbc.gridx = 3;
gbc.gridy = 4;
boardLabel.add(knightLabel, gbc);
}
protected JComponent filler() {
JLabel filler = new JLabel() {
@Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
};
return filler;
}
}
}
With a setup like this, you can simply use GridBagConstraint
's gridx
and gridy
values as direct cell address (no need to calculate the pixel position).
Because of the way the layout manager works, you are required to provide an empty cell component as well