While you could add a component on top of a JTextField, I think it’s easier just to draw the placeholder text directly:
public class PlaceholderTextField
extends JTextField {
private static final long serialVersionUID = 1;
private String placeholderText = "";
public PlaceholderTextField() {
// Deliberately empty.
}
public PlaceholderTextField(int columns) {
super(columns);
}
public PlaceholderTextField(String text) {
super(text);
}
public PlaceholderTextField(String text,
int columns) {
super(text, columns);
}
public PlaceholderTextField(Document doc,
String text,
int columns) {
super(doc, text, columns);
}
public String getPlaceholderText() {
return placeholderText;
}
public void setPlaceholderText(String text) {
this.placeholderText =
Objects.requireNonNull(text, "Text cannot be null.");
}
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (getDocument().getLength() > 0 || placeholderText.isEmpty()) {
return;
}
Rectangle2D textStart;
try {
textStart = modelToView2D(0);
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
Graphics2D g = (Graphics2D) graphics;
Shape oldClip = g.getClip();
Composite oldComposite = g.getComposite();
Insets insets = getInsets();
g.clipRect(insets.left, insets.top,
getWidth() - insets.right - insets.left,
getHeight() - insets.bottom - insets.top);
g.setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
float x = (float) textStart.getMinX();
float y = (float) textStart.getMaxY() - g.getFontMetrics().getDescent();
g.drawString(placeholderText, x, y);
g.setComposite(oldComposite);
g.setClip(oldClip);
}
}