Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
24eb254
Throw start up exception with custom message and stop server
TobyCyan Jun 15, 2026
460f1d1
Create dev server error handling system
TobyCyan Jun 15, 2026
ac3cff9
Rename package
TobyCyan Jun 15, 2026
8f9b533
Rename error handler list and make private
TobyCyan Jun 15, 2026
5e4037d
Merge branch 'master' into more-friendly-schema-validation-error
TobyCyan Jun 22, 2026
e4f4b8a
Handle exception instead of throwable
TobyCyan Jun 22, 2026
793df49
Merge branch 'master' into more-friendly-schema-validation-error
TobyCyan Jun 25, 2026
5f36b11
Commit suggestions
TobyCyan Jun 25, 2026
e0a02dc
Simplify error handling
TobyCyan Jun 25, 2026
10a0583
Remove package from testng
TobyCyan Jun 25, 2026
addee80
Fail fast based on liquibase status
TobyCyan Jun 29, 2026
e7718f1
Move packages into main
TobyCyan Jun 29, 2026
ce35882
Use liquibase API
TobyCyan Jun 29, 2026
f3b2c98
Move status checker to liquibase package
TobyCyan Jun 29, 2026
229170d
Use instanceof to determine exception
TobyCyan Jun 29, 2026
908a88f
Fix pmd
TobyCyan Jun 29, 2026
0f78125
Merge branch 'master' into more-friendly-schema-validation-error
TobyCyan Jun 29, 2026
231501d
Revert package comment
TobyCyan Jun 29, 2026
885715e
Merge branch 'master' into more-friendly-schema-validation-error
TobyCyan Jul 7, 2026
d3bf228
Upgrade liquibase version
TobyCyan Jul 7, 2026
553090d
Normalize changeset file paths
TobyCyan Jul 7, 2026
fafd51e
Remove liquibase core testImplementation dependency
TobyCyan Jul 7, 2026
fc63779
Extract transform start up exception
TobyCyan Jul 7, 2026
8d7190e
Merge branch 'master' into more-friendly-schema-validation-error
TobyCyan Jul 8, 2026
947dabf
Revert liquibase core version
TobyCyan Jul 8, 2026
717f6e6
Force commons-lang3 version
TobyCyan Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/main/java/teammates/main/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import teammates.common.util.Config;
import teammates.common.util.Logger;
import teammates.ui.errorhandlers.DevServerStartupErrorHandler;
import teammates.ui.servlets.DevServerLoginServlet;

/**
Expand Down Expand Up @@ -75,12 +76,26 @@ public void lifeCycleStopped(LifeCycle event) {
server.setHandler(webapp);
server.setStopAtShutdown(true);
server.addEventListener(customLifeCycleListener);
webapp.setThrowUnavailableOnStartupException(true);

server.start();
try {
server.start();
} catch (Exception e) {
stopServer(server);
throw Config.IS_DEV_SERVER ? DevServerStartupErrorHandler.transform(e) : e;
}

// By using the server.join() the server thread will join with the current thread.
// See https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join-- for more details.
server.join();
}

private static void stopServer(Server server) {
try {
server.stop();
} catch (Exception e) {
log.severe("Failed to stop server after web application startup failure.", e);
}
}
Comment thread
TobyCyan marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package teammates.ui.errorhandlers;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import org.hibernate.tool.schema.spi.SchemaManagementException;

import teammates.ui.exception.DevServerStartupException;

/**
* Handles startup errors with dev-server-friendly messages where possible.
*/
public final class DevServerStartupErrorHandler {

private static final String SCHEMA_VALIDATION_PREFIX = "Schema-validation:";
private static final String SCHEMA_VALIDATION_MESSAGE = String.join(System.lineSeparator(),
"",
"============================================================",
"Database schema validation failed:",
"%s",
"",
"Your local database schema is out of date.",
"Run: ./gradlew liquibaseUpdate",
"Then restart the local server.",
"",
"For more details, see docs/how-to/schema-migration.md",
"============================================================",
"");

/**
* Add new common startup errors here in order of specificity.
*/
private static final List<Function<Throwable, Optional<String>>> ERROR_MESSAGE_PROVIDERS = List.of(
DevServerStartupErrorHandler::buildSchemaValidationMessage);

private DevServerStartupErrorHandler() {
// Utility class
}

/**
* Transforms a recognized startup error into a dev-server-friendly exception.
* Returns the original exception when no provider recognizes it.
*/
public static Exception transform(Exception e) {
return ERROR_MESSAGE_PROVIDERS.stream()
.map(provider -> provider.apply(e))
.flatMap(Optional::stream)
.findFirst()
.<Exception>map(message -> new DevServerStartupException(message))
.orElse(e);
}

private static Optional<String> buildSchemaValidationMessage(Throwable error) {
Throwable schemaValidationFailure = findSchemaValidationFailure(error);
if (schemaValidationFailure == null) {
return Optional.empty();
}

String details = Optional.ofNullable(schemaValidationFailure.getMessage())
.map(String::trim)
.filter(message -> !message.isEmpty())
.orElse("Hibernate reported a schema validation error.");
Comment thread
TobyCyan marked this conversation as resolved.
Outdated
return Optional.of(String.format(SCHEMA_VALIDATION_MESSAGE, details));
}

private static Throwable findSchemaValidationFailure(Throwable error) {
Throwable current = error;
while (current != null) {
String message = current.getMessage();
if (current instanceof SchemaManagementException
|| message != null && message.trim().startsWith(SCHEMA_VALIDATION_PREFIX)) {
return current;
}
Comment thread
TobyCyan marked this conversation as resolved.
Outdated
current = current.getCause();
}
return null;
}

}
4 changes: 4 additions & 0 deletions src/main/java/teammates/ui/errorhandlers/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Contains handlers for errors that occur during dev server startup, and the factory to retrieve them.
*/
package teammates.ui.errorhandlers;
Comment thread
TobyCyan marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package teammates.ui.exception;

/**
* Exception thrown when an error occurs during dev server startup.
*/
public class DevServerStartupException extends RuntimeException {
public DevServerStartupException(String message) {
// Null cause to avoid printing the stack trace twice.
super(message, null, true, false);
}
}
Comment thread
TobyCyan marked this conversation as resolved.
Loading