~/blog/2026/servicetracker-modified-service
A discussion about ServiceTrackerCustomizer.modifiedService
Discussed with Gemini for a deep dive of the ServiceTrackerCustomizer’s modifiedService method.
Gemini Summary of the discussion
| Topic | Key Takeaway |
|---|---|
| Object Identity | The ServiceReference passed to modifiedService is the exact same instance as the one in addingService. Only the properties inside it have changed. |
| Tracking State | If your tracked object (tuple, config, etc.) is built using specific properties, it will not update automatically. You must refresh it manually in modifiedService. |
| Lifecycle Safety | Never call addingService or removedService manually. It bypasses the tracker’s internal map and messes up OSGi’s internal service use-counts. |
| Updating the Tracker | Since modifiedService returns void, use a Wrapper (like AtomicReference) or a Mutable Object as your tracked type so you can swap out the internal logic while the tracker holds the same container. |
| Reference Counting | If you re-fetch the service instance using context.getService() during an update, you must call context.ungetService() on the old one to keep the framework’s “use counts” balanced. |
Principles:
- When creating
ServiceTracker, create a Filter for the relevant properties; this way, theServiceTrackerCustomizer.modifiedServicemethod is only notified when these relevant properties are changed. - Before implementing
modifiedService, ask yourself, are the properties in this tracker’s scope really able to change? If not, leavemodifiedServiceempty. - If the tracked object returned by the
addingServicemethod is built using specific properties, themodifiedServicemethod must be able to refresh it when the properties change.- Since this method returns
void, it can’t directly modify theServiceTracker’s internal state. Consider returning a wrapper (likeAtomicReference) or a mutable object as the tracked object, which is updatable inmodifiedService.
- Since this method returns
- It’s generally a bad practice to just simply delegate to
removedServiceandaddingService. In a design perspective, they should only be called by the framework (ServiceTracker) as they are event callbacks. Extract common logics for tracked object’s constuction/destruction into separate methods.- Mind Reference Counting.
BundleContext.getService()andBundleContext.ungetServicemust be called in pair to keep the framework’s use counts balanced.
- Mind Reference Counting.
An example:
@Override
public void modifiedService(ServiceReference<S> sr, AtomicReference<MyLogic> wrapper) {
MyLogic oldLogic = wrapper.get();
if (oldLogic != null) {
// clean up
oldLogic.stop();
context.ungetService(sr);
}
S serviceInstance = context.getService(sr);
MyLogic newLogic = new MyLogic(serviceInstance, sr);
newLogic.start();
wrapper.set(newLogic);
}