Quick-Start Guide
OptoGantt — Vaadin
Follow these steps to add an interactive Gantt chart to your Vaadin application. By the end you will have the chart shown below running in your own project, ready to connect to your own data.
Live example — what you will build
This chart is fully interactive — use the filter pane to adjust the date range and period. The stripe on the bar crossing today's date is the built-in "now" indicator.
Part 1 — Display-only Gantt chart
Start here. Steps 1–5 give you a working Gantt chart with no user interaction. If you only need to display data, you can stop after Step 5.
Download optogantt-1.0.0.zip from Gumroad and unzip it.
Install both JARs to your local Maven repository by running the
following command once for each, substituting the filename:
mvn install:install-file \ -Dfile=gantt-core-1.0.0.jar \ -DgroupId=com.optomus.gantt \ -DartifactId=gantt-core \ -Dversion=1.0.0 \ -Dpackaging=jar
Repeat for gantt-vaadin-1.0.0.jar (artifactId: gantt-vaadin).
The remaining JARs are for other frameworks and are not needed for this guide.
Add the Vaadin BOM to dependencyManagement and declare
vaadin-spring-boot-starter and gantt-vaadin
as dependencies:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>24.3.11</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.optomus.gantt</groupId>
<artifactId>gantt-vaadin</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
Also add the vaadin-maven-plugin to your build so Vaadin's
frontend toolchain bundles the CSS from the gantt-vaadin JAR:
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>24.3.11</version>
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
<goal>build-frontend</goal>
</goals>
</execution>
</executions>
</plugin>
gantt-core is a transitive dependency
of gantt-vaadin and is resolved automatically. You do not
need to declare it separately.
Create your bar data using GanttBar. Each bar requires a
task name, row name, start datetime, end datetime, text colour, bar
background colour, hover text, and an optional hyperlink
(null if not needed).
import com.optomus.gantt.core.model.GanttBar;
import com.optomus.gantt.core.model.GanttColour;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
LocalDate today = LocalDate.now();
List<GanttBar> bars = List.of(
new GanttBar(
"Project Kickoff", // task name
"Alice", // row name
LocalDateTime.of(today.minusDays(5), LocalTime.of(9, 0)),
LocalDateTime.of(today.minusDays(1), LocalTime.of(17, 0)),
GanttColour.WHITE, // text colour
new GanttColour("#2a4a7a"), // bar colour
"Project Kickoff | Alice | Completed", // hover text
null // hyperlink
),
new GanttBar(
"Development Sprint",
"Bob",
LocalDateTime.of(today.minusDays(3), LocalTime.of(9, 0)),
LocalDateTime.of(today.plusDays(4), LocalTime.of(17, 0)),
GanttColour.WHITE,
new GanttColour("#1a6b5a"),
"Development Sprint | Bob | In progress",
null
),
new GanttBar(
"Testing & QA",
"Carol",
LocalDateTime.of(today.plusDays(2), LocalTime.of(9, 0)),
LocalDateTime.of(today.plusDays(6), LocalTime.of(17, 0)),
GanttColour.WHITE,
new GanttColour("#5b3a6e"),
"Testing & QA | Carol | Upcoming",
null
)
);
explicitRowNames() on the Builder.
GanttChart uses a Builder pattern. Construct it directly
in your Vaadin view's constructor — no Spring injection or
build() call is needed.
import com.optomus.gantt.vaadin.components.GanttChart;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
LocalDateTime chartStart = LocalDateTime.of(
LocalDate.now().minusDays(6), LocalTime.MIDNIGHT);
GanttChart ganttChart = new GanttChart.Builder()
.startDate(chartStart)
.columnDuration(Duration.ofDays(1)) // one column per day
.numColumns(14) // 14-day window
.ganttBars(bars)
.explicitRowNames(List.of("Alice", "Bob", "Carol"))
.build();
GanttChart extends Vaadin's Div. Add it to
your view with a single add() call — no Thymeleaf
fragments, no CSS link tags, no template files.
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
@Route("schedule")
public class ScheduleView extends VerticalLayout {
public ScheduleView() {
List<GanttBar> bars = buildBars();
GanttChart ganttChart = new GanttChart.Builder()
.startDate(LocalDateTime.of(
LocalDate.now().minusDays(6), LocalTime.MIDNIGHT))
.columnDuration(Duration.ofDays(1))
.numColumns(14)
.ganttBars(bars)
.explicitRowNames(List.of("Alice", "Bob", "Carol"))
.build();
add(ganttChart);
}
}
gantt-vaadin uses
Vaadin's @CssImport mechanism. The stylesheet is bundled
by the Vaadin frontend toolchain at build time — no manual CSS wiring
is required.
Part 2 — Interactive filter pane
Add the filter pane so visitors can adjust the chart's date range and period. Unlike the Spring edition, there are no REST endpoints, no JSON, and no JavaScript to write — Vaadin handles all client-server communication automatically via WebSocket.
Construct GanttFilterPane with a GanttFilterListener
implementation. The listener receives fully resolved chart parameters —
start date, column duration, and column count — whenever the visitor
applies or clears the filter. Call syncWithView() to
initialise the pane's fields with the current chart state.
import com.optomus.gantt.core.GanttFilterListener;
import com.optomus.gantt.core.model.GanttColumnDuration;
import com.optomus.gantt.vaadin.components.GanttChart;
import com.optomus.gantt.vaadin.components.GanttFilterPane;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import java.time.*;
import java.util.List;
@Route("schedule")
public class ScheduleView extends VerticalLayout {
private GanttChart ganttChart;
private GanttFilterPane filterPane;
private LocalDateTime chartStart = defaultStart();
private Duration columnDuration = Duration.ofDays(1);
private int numColumns = 14;
public ScheduleView() {
List<GanttBar> bars = buildBars();
ganttChart = buildChart(bars);
filterPane = new GanttFilterPane(new GanttFilterListener() {
@Override
public void onFilter(LocalDateTime start,
Duration duration, Integer cols) {
chartStart = start;
columnDuration = duration;
numColumns = cols;
replaceChart(buildChart(bars));
filterPane.syncWithView(chartStart,
GanttColumnDuration.FOURTEEN_DAYS);
}
@Override
public void onClearFilter() {
chartStart = defaultStart();
columnDuration = Duration.ofDays(1);
numColumns = 14;
replaceChart(buildChart(bars));
filterPane.syncWithView(chartStart,
GanttColumnDuration.FOURTEEN_DAYS);
}
});
filterPane.syncWithView(chartStart, GanttColumnDuration.FOURTEEN_DAYS);
add(filterPane, ganttChart);
}
private GanttChart buildChart(List<GanttBar> bars) {
return new GanttChart.Builder()
.startDate(chartStart)
.columnDuration(columnDuration)
.numColumns(numColumns)
.ganttBars(bars)
.explicitRowNames(List.of("Alice", "Bob", "Carol"))
.build();
}
private void replaceChart(GanttChart newChart) {
if (ganttChart != null) replace(ganttChart, newChart);
ganttChart = newChart;
}
private static LocalDateTime defaultStart() {
return LocalDateTime.of(
LocalDate.now().minusDays(6), LocalTime.MIDNIGHT);
}
}
GanttFilterListener fires server-side — update your chart
state and call replaceChart() to push the new chart to
the browser automatically.
Vaadin requires @Push to be declared on a class implementing
AppShellConfigurator rather than on individual views. Create
this class once in your application — it enables WebSocket server push
globally for all Vaadin views.
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.Push;
@Push
public class AppShell implements AppShellConfigurator {
}
@Push directly on a view
class causes an InvalidApplicationConfigurationException
at startup. The AppShellConfigurator class is the single
correct location for application-level Vaadin configuration.
Part 3 — Optional customisation
These steps are independent of each other — apply any combination that suits your use case.
Override the default heading, border, and alternate row colours via
the Builder. All colours accept CSS hex strings via GanttColour.
new GanttChart.Builder()
.startDate(chartStart)
.columnDuration(columnDuration)
.numColumns(numColumns)
.ganttBars(bars)
.headingTextColour(new GanttColour("#ffffff"))
.headingBackgroundColour(new GanttColour("#3d2b1f"))
.borderColour(new GanttColour("#6b4c35"))
.alternateRowsBackgroundColour(new GanttColour("#faf7f3"))
.build();
If only headingBackgroundColour() is set, border and
alternate row colours are derived automatically as darker and lighter
shades respectively.
new GanttChart.Builder()
// ... required parameters ...
.nameColumnWidth("180px") // any valid CSS width
.rowHeightInPixels(40)
.build();
All filter pane labels are loaded from GanttMessages.properties
inside the gantt-core JAR via Java's ResourceBundle.
To override any label, place your own GanttMessages.properties
earlier on the classpath — for example in
src/main/resources/com/optomus/gantt/core/.
# Filter pane labels label.startDate=Start label.columnDuration=Period button.apply=Apply Filter link.clear=Clear filter # Period dropdown options duration.FOURTEEN_DAYS=14 Days duration.ONE_MONTH=1 Month duration.THREE_MONTHS=3 Months duration.SIX_MONTHS=6 Months duration.ONE_YEAR=1 Year
ResourceBundle directly.
Locale-specific files such as GanttMessages_fr.properties
are picked up automatically based on the JVM default locale.
Pass a URL as the final constructor argument to GanttBar.
Clicking the bar navigates to that URL. Pass null if no
link is needed.
new GanttBar(
"Sprint Review",
"Alice",
start, end,
GanttColour.WHITE,
new GanttColour("#2a4a7a"),
"Sprint Review | Alice | Click for details",
"https://your-project-tracker.example.com/sprint/42"
)
Troubleshooting
Common issues and their solutions.
@Push has been placed on a view class rather than on an
AppShellConfigurator class. Create an AppShell
class as shown in Step 7 and remove @Push from your view.
The vaadin-maven-plugin's prepare-frontend
and build-frontend goals must run during the build.
Confirm the plugin is declared in pom.xml and that you
are building with mvn package or mvn install
rather than just mvn compile. On first run, Vaadin downloads
its frontend toolchain — allow a few extra minutes.
Confirm that AppShell implementing AppShellConfigurator
with @Push exists on the classpath (Step 7). Without server
push, UI updates triggered by GanttFilterListener do not
propagate to the browser. Also confirm that replaceChart()
is called inside onFilter().
Vaadin scans for @Route classes in the same package as
your @SpringBootApplication class and its sub-packages.
If your view is in a different package, add it to scanBasePackages:
@SpringBootApplication(scanBasePackages = {
"com.example.yourapp",
"com.example.yourapp.views"
})
public class YourApplication { ... }
By default Vaadin's servlet maps to /* and intercepts all
requests. If your application mixes Vaadin views with Spring MVC
controllers, restrict Vaadin to its own path prefix in
application.properties:
vaadin.url-mapping=/vaadin/*
Vaadin routes are then accessible under /vaadin/schedule
rather than /schedule.
Next steps
Visit gantt.nz to explore the full interactive demo — add your own bars, adjust the filter pane, and see all features in action.
Questions or unexpected behaviour? Email support@gantt.nz. We aim to respond within 24 hours on New Zealand business days.