Tyler Technologies
Working on the Time & Attendance integrations that connect school districts to the platforms they already run on.

The Role
What I did
Tyler Technologies builds the software the public sector runs on. I joined the Time & Attendance Integrations team as one of eight people in an Agile squad, contributing daily to an enterprise Java and Spring Boot codebase that keeps school districts' time and attendance data in sync with the third-party platforms they already use. I joined full time for the summer and have continued with the team part time since.
Day to day that meant authoring and reviewing pull requests, tracing data flows across backend services and JSP front-end components, and validating what I found against Hibernate-backed schemas with SQL and HQL. Standups and ticket grooming in Jira set the rhythm.
What I worked in
- Backend: Java, Spring Boot, Hibernate/JPA
- Front end: JSP components on the existing stack
- Data: SQL and HQL against Hibernate-mapped schemas
- Testing: JUnit unit tests and Cypress end-to-end tests
- Process: Git, Jira, Agile ceremonies in an 8-person squad
This one is still in progress. I'm currently on the team part time, so the write-up stays at the level of what the work was rather than what the product looks like from the inside.
The Constraint
Twenty years of code
The codebase predates me by about two decades. That is the first real thing to adjust to: nothing about it is a blank page, and almost every file you open was written for a reason you cannot see yet.
20 yearsof accumulated code to get productive in
What worked was reading before writing. I followed a single request the whole way down before touching anything:
- Controllerwhere the request lands
- Servicethe rules that actually decide things
- Hibernate mappingentity to column
- Tablewhat is really stored, verified in SQL
One trip through that taught me more than any single explanation could, and it meant the changes I proposed fit the conventions already in place instead of importing my own. Code review went both directions here: having my pull requests read by people who had lived with these decisions was the fastest feedback loop I had.
Project 01
ERP Exception Handling
School districts push integration data in from their ERP systems, and that data is not always valid. When it wasn't, the API returned a generic failure. It was technically correct, and almost useless to whoever had to work out which record was wrong and why.
{
"status": "error",
"message": "Import failed."
}I designed custom exception handling for invalid ERP integration data, replacing those responses with diagnostics that name the record, the field, and the reason, turning a support conversation into something the person on the other end can resolve directly.
{
"status": "error",
"message": "Import failed.",
"errors": [
{
"record": 412,
"field": "employeeId",
"reason": "required field is missing"
},
{
"record": 903,
"field": "shiftStart",
"value": "2026-13-02T09:00",
"reason": "not a valid ISO-8601 timestamp"
}
]
}The constraint that made it interesting is visible in those two blocks: the envelope is unchanged. Downstream consumers already depended on the existing REST API response contract, so the diagnostics had to be additive: a new field inside the same shape, not a new shape. Better errors could not come at the cost of breaking the integrations that were already working.
Project 02
Cypress & JUnit Coverage
Integrations fail in the seams. The paths between Time & Attendance and the connected third-party platforms were exactly where a regression could slip through without any one service looking broken.
I wrote Cypress end-to-end tests covering those paths, driving the real flow the way a district's data actually travels it, alongside JUnit unit tests on the logic underneath. Both went in as part of the normal pull request cycle, so the coverage arrived with the change rather than after it. The end-to-end tests asserted on the diagnostics above: that the right record and field come back, not just that the request failed:
it('names the offending record when a shift timestamp is invalid', () => {
cy.intercept('POST', '/api/integrations/roster').as('import');
cy.visit('/integrations/roster');
cy.get('[data-test=file]').selectFile('rosters/bad-timestamp.csv');
cy.get('[data-test=submit]').click();
cy.wait('@import').its('response.statusCode').should('eq', 422);
cy.contains('shiftStart').should('be.visible');
cy.contains('not a valid ISO-8601 timestamp').should('be.visible');
});The payoff was catching regressions before release instead of in a customer's environment. On a twenty-year-old system with a lot of downstream dependents, a test that fails in CI is enormously cheaper than the same bug found by a district mid-payroll.
Reflection
What I took away
The thing I did not expect to learn was how much of engineering on a mature product is about compatibility. My instinct coming in was to improve the shape of things; the more valuable skill turned out to be improving them without moving anything other people were standing on. The exception handling work is the clearest example. The interesting part wasn't the better error, it was the better error that broke nothing.
The second thing: on a codebase this old, reading is the work. Being able to trace an unfamiliar path end to end, and to ask a precise question about it, mattered more than how quickly I could produce code.
Team
Thanks to the team!
Thank you to my manager and to the Time & Attendance Integrations team for the reviews, the context behind decisions made long before I arrived, and the patience to let an intern ask why. Being handed real tickets on a system this consequential, and being trusted to change how it reports failures, is the reason I've gotten as much out of this role as I have.