There's two things causing the misalignment:
The default anchor location of a tooltip is CONTENT_TOP_LEFT.
The default application user-agent stylesheet (modena.css) gives tooltips a drop shadow with a non-zero y-offset.
Neither of those two points are documented, as far as I can tell.
To properly center the tooltip you need to set the anchor location to WINDOW_TOP_LEFT and you need to account for the y-offset of the drop shadow. Here's a proof-of-concept:
import java.util.function.Function;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.PopupWindow.AnchorLocation;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
var button = new Button("Test");
var tooltip = new Tooltip("Test button's tooltip.");
tooltip.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
tooltip.setOnShown(
_ -> {
var bounds = button.localToScreen(button.getBoundsInLocal());
tooltip.setAnchorX(bounds.getMaxX() + 5);
// '3' accounts for the y-offset of the tooltip's drop shadow
tooltip.setAnchorY(bounds.getCenterY() - tooltip.getHeight() / 2 + 3);
});
button.setTooltip(tooltip);
// these lines only exist to help see the alignment
var topLine = createLine(Color.RED, button, Bounds::getMinY);
var centerLine = createLine(Color.GREEN, button, Bounds::getCenterY);
var bottomLine = createLine(Color.RED, button, Bounds::getMaxY);
var root = new StackPane(topLine, centerLine, bottomLine, button);
var scene = new Scene(root, 300, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
private Line createLine(Color stroke, Button button, Function<Bounds, Double> mapper) {
var line = new Line();
line.endXProperty().bind(button.sceneProperty().map(Scene::getWidth));
line.startYProperty().bind(button.boundsInParentProperty().map(mapper));
line.endYProperty().bind(line.startYProperty());
line.setStroke(stroke);
line.setStrokeWidth(2);
line.setManaged(false);
return line;
}
}
Output:

This code adds 3 to the y-anchor of the tooltip to account for the y-offset of the drop shadow. The value of 3 was taken from modena.css. It may be different across different versions of JavaFX. I tested on JavaFX 24.0.1. A more robust approach would probably be to get the effect from the tooltip. Note it's the label within the tooltip that actually has the drop shadow effect. Of course, there's no guarantee the effect will remain a drop shadow in future versions of JavaFX.
You could also customize the styling of the tooltip. That way you know exactly what you need to account for. For instance, you could remove the effect entirely.
Or you could use your own Popup or PopupControl. That might make more sense semantically. Plus, Tooltip comes with functionality you may not need.
Popupis better component to achieve what I want. It does not cause this problem. Here is the relevant information.