7

In some languages it is possible to get the column number of a line in a stacktrace, however in Java we only have line numbers.

To give you an example, in another language we can have:

Error
    at <anonymous>:2:2
    at Object.InjectedScript._evaluateOn (<anonymous>:641:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:580:52)
    at Object.InjectedScript.evaluate (<anonymous>:495:21)"

Although this might be a bad example as I'm causing the error from the browser console, you can see the column numbers which is really helpful in solving errors.

To give you the example in Java ( Yes, names were changed ) :

Caused by: java.lang.IllegalArgumentException: path was null
    at org.jboss.resteasy.specimpl.ResteasyUriBuilder.path(ResteasyUriBuilder.java:362)
    at enterprise.money.service(AbstractSomething.java:88)

This leads to line 88 which contains

URI uri = uriInfo.getBaseUriBuilder().path(objectA).path(objectB).build();

With the stacktrace I have I can't check which .path call caused the exception. So my question is, are there any solutions that allow me to get the reference of the column?

( To guard from some possible alternative answers, we need a solution to get the column numbers, other answers like how to step through the debugger or refactoring every builder pattern, etc., will not answer the question )

Philipp Gayret
  • 4,870
  • 1
  • 28
  • 34
  • The standard line debug information of a class file does not contain the column number. The easiest solution to your problem is to insert line breaks between the chained calls. – Holger Apr 30 '14 at 07:46
  • I assumed it can't be done, are there really no tools that do this automatically? Like for example I've used EclEmma for code coverage that more or less insert sort of hidden markers to see where code reached - it should be possible shouldn't it? – Philipp Gayret Apr 30 '14 at 07:49
  • 1
    I guess you could write your own Java compiler. Other than that though, I think you're out of luck. – Dawood ibn Kareem Apr 30 '14 at 07:51
  • Well, there are tools for automated source code formatting and they could be configured to do the task. But searching for a tool for you is not the scope of `stackoverflow`. – Holger Apr 30 '14 at 07:51
  • I've been dreaming of a better alternative to line numbers for a long time. IDK, maybe some token or expression index. Besides solving this issue, once line and column numbers became irrelevant, we could save source code in some canonical formatting, and with IDE support, each developer could use her/his preferred formatting style. – EndlosSchleife Apr 25 '20 at 17:10

3 Answers3

5

This is not possible.

You can bypass it, by formatting your code in that way:

URI uri = uriInfo
           .getBaseUriBuilder()
           .path(objectA)
           .path(objectB)
           .build();

In Oracle Java 8 you can write

public static void main(String... ignored) {
    List<String> s = new ArrayList<>();
    s.add("hello");

    Predicate<? super String> predicate1 = (t) -> t.length() > 0;
    Predicate<? super String> predicate2 = (t) -> t.length() < 8;
    Predicate<? super String> predicate3 = null;

    List<String> collect = s.stream()
            .filter(predicate1) // 16
            .filter(predicate2) // 17
            .filter(predicate3) // 18
            .collect(Collectors.toList());
}

and you get

Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:161)
    at Example.main(Example.java:18)

For the Oracle JDK, this appears to be only in Java 8, not Java 7.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
Udo Klimaschewski
  • 5,150
  • 1
  • 28
  • 41
  • 5
    I am not sure this always works. Often I see the first line being blamed. – Peter Lawrey Apr 30 '14 at 07:51
  • I've just tested this and indeed this points to the first line ( Using Oracle Java 7 here ) – Philipp Gayret Apr 30 '14 at 07:53
  • 3
    That’s compiler-depended, i.e. `javac` generates code which will blame the first line while eclipse does it as intended. – Holger Apr 30 '14 at 07:53
  • IMHO, reporting the actual line would be better, so I remember being disappointed. ;) – Peter Lawrey Apr 30 '14 at 07:58
  • @Holger Does this mean we can tell `javac` to preserve source information? ( I assume Eclipse doesn't come with its own java compiler ) If so, do you know how it tells `javac` to preserve the line information? – Philipp Gayret Apr 30 '14 at 08:09
  • I hope you don't mind me adding a working example from Oracle Java 8. – Peter Lawrey Apr 30 '14 at 08:16
  • @PeterLawrey Nice! - thanks. Do you know if this is a difference between Oracle Java 7 and 8 or a difference between compiling our code? – Philipp Gayret Apr 30 '14 at 08:19
  • 2
    @user1066946: Eclipse **does** come with its own java compiler – Holger Apr 30 '14 at 08:20
  • @Holger Eclipse's compiler has some of its own features which can be good, but it can also have differences which can be confounding. e.g. it calculates serial version ids differently, some times. – Peter Lawrey Apr 30 '14 at 08:21
  • I've accepted this, it should be easy to have an IDE add newlines for builder pattern style methods, I compiled with Java 7 via maven. Java 8 would solve that ( without using Eclipse ) - many thanks all – Philipp Gayret Apr 30 '14 at 08:27
  • @Peter Lawrey: Eclipse does not calculate a `serialVersionUID` at all. It is a design mistake of the `Serialization` developers to calculate the default id based on *all* members, including `private` and even synthetic methods whose names where not covered by the specification (and do not affect the semantics of the serial format). Eclipse used it’s freedom to use its own naming scheme for inner class related synthetic methods in the past and the problem re-appears for the synthetic members created for lambda implementations. But it’s not Eclipse to blame for that problem. – Holger Apr 30 '14 at 08:30
3

Reverse engineering this for Java would be pretty painful. In theory you could analyse the original source and work out which expression(s) could throw such an exception.

If you have this as an issue, it is highly likely your lines are too complicated/dense. Most IDEs make it really easy to refactor expressions to extract portions of code. This will give you more resolution of where an exception was thrown.


Another solution to this specific problem is to have methods with @NotNull and the IDE can detect which arguments could be null, but should never be.

URI uri = uriInfo.getBaseUriBuilder().path(objectA)
                                     .path(/*highlighted*/objectB).build();

The IDE can warn you that you are passing a variable to a method which can't take a null If you do this, you will pick up these bugs much earlier and it make it easier to fix. (The IDE comes with some quick fixes)

Note: generally speaking a incorrectly passed null argument the API should throw a NullPointerException IMHO, however often an IllegalArgumentException is thrown somewhat inconsistently.

As I see it, an argument for IllegalArgumentException is that;

  • IllegalArgumentException is something you might expect the application to catch. Catching NullPointerException really smells.
  • The handling of a invalid null is expected to be the same as other incorrect arguments such as a length of -1

IMHO passing null to a method which doesn't accept a null is a programming bug, not a matter of happening to provide an incorrect input.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • *generally speaking a incorrectly passed null argument the API should throw a NullPointerException IMHO* -- I totally disagree. If `null` is illegal as an argument, then `IllegalArgumentException` makes perfect sense and should be used. – Joffrey Apr 30 '14 at 08:07
  • @Joffrey I agree. NPEs always raise a "he/she didn't handle that case correctly, it's missing some guards" flag in my reviewer head. IAE or ISE should always be used when the contract implies guards against null (ISE can be valid to use if the validity of some parameter depends on previously made changes to that object, aka state). – hiergiltdiestfu Apr 30 '14 at 08:10
  • @hiergiltdiestfu Exactly. Also, `IllegalArgumentException` with a proper message would help solve the OP's problem without having to change his code. – Joffrey Apr 30 '14 at 08:11
  • *IllegalArgumentException is something you might expect the application to catch* -- I disagree. If an API is used properly, one should **never** have to catch IAE. If you get an IAE, it means you are incorrectly using the API. **It is not an error case to handle. In fact, it means you should change the calling code in order to respect the API contract.** – Joffrey Apr 30 '14 at 08:17
  • @Joffrey when you call two different methods, the method name tells you what you want to know. If the same method is called twice, it the method cannot say which call you did which caused the problem. – Peter Lawrey Apr 30 '14 at 08:17
  • @Joffrey In terms of IAE, you might have an interface which has a defined range of valid inputs, however a specific implementation might a different range of valid inputs. Similarly, this is a concern for collections e.g. some Maps allow null keys and values but others do not. Some throw IAE, some NPE. – Peter Lawrey Apr 30 '14 at 08:20
  • @PeterLawrey It depends. The error message could contain state information that could help find the problem source and maybe identify which call crashed. But I agree, you will often not be able to tell when the problem is a `null`. – Joffrey Apr 30 '14 at 08:20
  • @PeterLawrey *however a specific implementation might [expect] a different range of valid inputs* -- then that implementation breaks the contract of the interface. But anyway, if the interface does not specify it cleary, and the implementation is free, then IAE informs you that this specific implementation does not accept that. What's the problem? – Joffrey Apr 30 '14 at 08:21
  • Please don't get too far into discussing how to Java properly - the question is not restricted to just IAE / NPE but more or less builder patterns in general – Philipp Gayret Apr 30 '14 at 08:22
  • 1
    Both, `IllegalArgumentException` and `NullPointerException` indicate a programming error. In case an argument is `null`, the jre classes prefer NPE and this behavior is advertised by [`Objects.requireNonNull(…)`](http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull(T)) so I would recommend to follow that pattern. – Holger Apr 30 '14 at 08:23
  • @user1066946 Right, my bad. – Joffrey Apr 30 '14 at 08:23
  • 2
    @user1066946 In short, use multiple lines and Java 8 or eclipse should work. – Peter Lawrey Apr 30 '14 at 08:24
  • 2
    @Holger *both indicate a programming error* -- I agree, but IMHO, NPE is a programming error in the called code, while IAE is a programming error in the calling code. I agree that the JRE often throws NPE in place of IAE, but I disagree with this decision. Well, I guess all of this is just a matter of opinion and shouldn't be discussed here, but there: http://stackoverflow.com/questions/3881/illegalargumentexception-or-nullpointerexception-for-a-null-parameter – Joffrey Apr 30 '14 at 08:26
3

You can't get the columns from the stack trace.

What you can do is using intermediate variables, so that you have each method call on a different line:

UriBuilder builder = uriInfo.getBaseUriBuilder();
builder = builder.path(objectA);
builder = builder.path(objectB);
URI uri = builder.build();

This is painful, but it can temporarily help finding the problem, then you can put it back as it was.

Joffrey
  • 32,348
  • 6
  • 68
  • 100
  • 1
    Occam's razor at its best. – hiergiltdiestfu Apr 30 '14 at 08:06
  • @hiergiltdiestfu I agree :) Well, it is just a way of finding out the problem. Then the code can be put back in its original form. NPE is a programming mistake anyway, proper `IllegalArgumentException`s wouldn't need such painful workarounds. – Joffrey Apr 30 '14 at 08:09