Skip to content

Build1 publisher3 min readPublished

Half a migrated app's endpoints returned HTTP 500 the first time anyone called them

A generator that turns Oracle ADF applications into Spring Boot projects had a suite proving 263 projects compiled and that Hibernate validated every mapping against a live schema. It could not say whether one endpoint returned the right rows.

The Engineer · Build desk

What happened

  • A tool that converts Oracle ADF applications into Spring Boot projects had a suite of 192 unit tests and 263 real applications whose generated projects all compiled, with mappings validated against a live Oracle schema.
  • After someone asked whether he had called the endpoints, the author scripted a sweep of every endpoint of one real application, and roughly half of them returned HTTP 500.
  • The cause was SQL with named bind variables copied faithfully out of ADF view objects into native queries whose parameters were never passed, so each call threw QueryParameterException.
  • Two quieter bugs returned 200 OK: entity resolution by simple class name sent a query to a same-named table in another package, and a dropped view-level WHERE clause returned every row instead of a filtered subset.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Schema validation can only reach things expressed as mappings. Any behaviour a generator emits as a string sits outside every gate a compile-and-boot pipeline has, however many gates you add.
  • exposure A migration that quietly widens a filtered query to a full-table read hands out rows the replaced application would not serve. It does that behind a 200 and valid JSON. No status-code check sees it.
  • decision Grading a denial as correct requires the source application's authorisation model. A migration team needs the original's grants in hand as a test oracle to tell a preserved 403 from a broken endpoint.
  • cost An honest sweep converts a near-green suite into a mostly-red one. That work lands on whoever owns the generator before any downstream migration ships, not on the team debugging in production.

Hibernate's `ddl-auto: validate` compares every column, type and key against the live database and refuses to start when a single mapping disagrees with it [6]. It reads mappings, and a native query is invisible to it [12]. So the generator's faithful copy of `select * from orders where region_id = :regionId and status = :status` crossed over as a string [9][10]. That string compiled [11]. The unit tests asserted that the generated code contained the right SQL, and it did [13]. The bind call was never made, and the failure only existed once something sent a request [10].

The suite's strongest figure was 3,311 of 3,312 ADF attributes either mapped or explained by a diagnostic [5], which is 99.97 percent of the attributes [25]. It counts what the translator emitted. For a number like that to predict runtime behaviour, every construct the source application depends on would have to be represented in something the schema validator reads, and SQL held in a view object is not.

The 500s were the cheap ones, because they announced themselves. One application had several pairs of entities sharing a simple class name across different packages, `com.example.billing.Customer` and `com.example.crm.Customer`, and the generator resolved entities by simple name, so a query written for one table ran against the other [14]. That endpoint returned 200 OK, well-formed JSON and real rows from the wrong table [15]. The author wrote that "only a person who knew the data would have known" [16]. A separate case dropped a WHERE clause attached to an ADF view object, so the generated endpoint returned every row in the table where the original returned a filtered subset [17]. In a system with row-level access rules, he wrote, that is "not a bug, it's a data leak" [18].

The trigger was a question from someone else: "Did you actually call the endpoints?" [7] He had not. The replacement gate boots the generated application, calls every endpoint it publishes, and reports what served, what was correctly denied and what failed [19].

In ADF, a REST resource with no grant in `jazn-data.xml` is unreachable, so the generated application has to deny it too. A 403 there is the correct answer [21]. Publishing it open would be, in the author's words, "a security change disguised as a migration" [22]. Grading the sweep therefore needs two inputs, not one: the running generated application, and the original's grant policy to check the denials against [23].

The sweep covered one application, not the 263-project corpus [26]. For roughly half to be the number on someone else's migration, that application would have to use view-object bind variables and view-level filters at a similar density. The post gives the first honest run in words and no counts: many failures, many correct denials, a minority serving data [20]. "That was uncomfortable to read. It was also the first number I had produced that meant anything," he wrote [24].

What to watch

  • Whether the author runs the endpoint sweep across the full 263-application corpus and publishes served, denied and failed counts per application.
  • Whether the generator switches to fully qualified entity resolution and closes the duplicate simple-name case.
  • Whether the correctly-denied column gets graded automatically against jazn-data.xml grants instead of by hand.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories