I have a Java web service built using PlayFramework 2.6.5 and Guice DI (libraryDependencies += guice
), just in time injection mode. All dependencies are injected via constructor, using @Inject
and @ImplementedBy
, and the Guice Module
is empty.
Due to transient errors, some dependencies can throw an exception in the constructor. When this happens the service fails with a ProvisionException
(which is ok, clients are expected to retry).
I found that these exceptions are cached, and even when the root cause of the exception is solved, either Play or Guice never retry instantiating these classes, and keep trowing the same exception until the web service is restarted.
Consider for example the following class Clock
with a constructor that fails if it's midnight (00:xx). As soon as the system clock reaches midnight, the service fails to instantiate the class. When clock reaches 1am, the same exception keeps being thrown. Also, the exception message is always the same (in the example the exception message is the time of the first time an exception occurred)
@ImplementedBy(OddClock.class)
public interface IClock {
//...
}
public class OddClock implements IClock {
@Inject
public OddClock() throws Exception {
if (DateTime.now().hourOfDay().get() == 0) {
throw new Exception(DateTime.now().toString());
}
}
}
public class TimeController {
@Inject
public TimeController(IClock clock) {
this.clock = clock;
}
}
btw, the same codebase is used also in a console application, which doesn't suffer from this issue, so I'm thinking there's something special in Play+Guice integration. Any suggestion to turn off the exception caching?
I found a solution to make PF's behavior more explicit and predictable. The Routes generated by PF by default caches controllers even when their instances are broken, assuming singleton controllers are what a user wants.
As described here, the default behavior can be changed adding a @
in front of each action in the routes configuration.
e.g., before:
GET /test webservice.TestController.test
after:
GET /test @webservice.TestController.test
With this syntax, controllers are not singleton-by-default, and one can still use @Singleton
where needed. Also, singleton controllers are not cached in case of exceptions, allowing transient errors to recover without restarting the service.
A copy of the code is available here.